From 20610bb024c44dec300ff9c686efad06cc626b40 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Mon, 8 Feb 2021 14:58:15 +0800 Subject: [PATCH 001/150] Update some config for echarts V5 --- pyecharts/charts/basic_charts/calendar.py | 5 +++- pyecharts/charts/basic_charts/gauge.py | 7 +++-- pyecharts/charts/basic_charts/map.py | 2 +- pyecharts/charts/basic_charts/pie.py | 4 ++- pyecharts/charts/basic_charts/radar.py | 21 ++++++++++++-- pyecharts/charts/basic_charts/sankey.py | 6 ++-- pyecharts/charts/chart.py | 35 ++++++++++++++++++----- pyecharts/options/charts_options.py | 12 ++++++-- pyecharts/options/global_options.py | 8 ++++-- pyecharts/options/series_options.py | 2 +- test/test_sankey.py | 2 -- 11 files changed, 79 insertions(+), 25 deletions(-) diff --git a/pyecharts/charts/basic_charts/calendar.py b/pyecharts/charts/basic_charts/calendar.py index eff3d7b54..c4b1c97fd 100644 --- a/pyecharts/charts/basic_charts/calendar.py +++ b/pyecharts/charts/basic_charts/calendar.py @@ -22,7 +22,10 @@ def add( yaxis_data: types.Sequence, *, is_selected: bool = True, - label_opts: types.Label = opts.LabelOpts(), + label_opts: types.Label = opts.LabelOpts( + is_show=False, + position="inside", + ), calendar_opts: types.Calendar = None, tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, diff --git a/pyecharts/charts/basic_charts/gauge.py b/pyecharts/charts/basic_charts/gauge.py index ab80597a7..d9d176f8f 100644 --- a/pyecharts/charts/basic_charts/gauge.py +++ b/pyecharts/charts/basic_charts/gauge.py @@ -24,9 +24,12 @@ def add( start_angle: types.Numeric = 225, end_angle: types.Numeric = -45, is_clock_wise: bool = True, - title_label_opts: types.GaugeTitle = opts.GaugeTitleOpts(), + title_label_opts: types.GaugeTitle = opts.GaugeTitleOpts( + offset_center=["0%", "20%"], + ), detail_label_opts: types.GaugeDetail = opts.GaugeDetailOpts( - formatter="{value}%" + formatter="{value}%", + offset_center=["0%", "40%"], ), pointer: types.GaugePointer = opts.GaugePointerOpts(), tooltip_opts: types.Tooltip = None, diff --git a/pyecharts/charts/basic_charts/map.py b/pyecharts/charts/basic_charts/map.py index 3fa175f68..98b192542 100644 --- a/pyecharts/charts/basic_charts/map.py +++ b/pyecharts/charts/basic_charts/map.py @@ -60,7 +60,7 @@ def add( "name": series_name, "symbol": symbol, "label": label_opts, - "mapType": maptype, + "map": maptype, "data": data, "roam": is_roam, "aspectScale": aspect_scale, diff --git a/pyecharts/charts/basic_charts/pie.py b/pyecharts/charts/basic_charts/pie.py index e6de03f64..49cfe30fd 100644 --- a/pyecharts/charts/basic_charts/pie.py +++ b/pyecharts/charts/basic_charts/pie.py @@ -23,8 +23,9 @@ def add( center: types.Optional[types.Sequence] = None, rosetype: types.Optional[str] = None, is_clockwise: bool = True, + start_angle: types.Numeric = 90, label_opts: types.Label = opts.LabelOpts(), - label_line_opts: types.PieLabelLine = None, + label_line_opts: types.PieLabelLine = opts.PieLabelLineOpts(), tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, encode: types.Union[types.JSFunc, dict, None] = None, @@ -58,6 +59,7 @@ def add( "type": ChartType.PIE, "name": series_name, "clockwise": is_clockwise, + "startAngle": start_angle, "data": data, "radius": radius, "center": center, diff --git a/pyecharts/charts/basic_charts/radar.py b/pyecharts/charts/basic_charts/radar.py index e687fa1aa..916525e0e 100644 --- a/pyecharts/charts/basic_charts/radar.py +++ b/pyecharts/charts/basic_charts/radar.py @@ -36,8 +36,11 @@ def add_schema( s = s.opts indicators.append(s) - self.options.update( - radar={ + if self.options.get("radar") is None: + self.options.update(radar=[]) + + self.options.get("radar").append( + { "indicator": indicators, "shape": shape, "center": center, @@ -48,6 +51,18 @@ def add_schema( "axisLine": axisline_opt, } ) + # self.options.update( + # radar={ + # "indicator": indicators, + # "shape": shape, + # "center": center, + # "radius": radius, + # "name": {"textStyle": textstyle_opts}, + # "splitLine": splitline_opt, + # "splitArea": splitarea_opt, + # "axisLine": axisline_opt, + # } + # ) return self def add( @@ -59,6 +74,7 @@ def add( symbol: types.Optional[str] = None, color: types.Optional[str] = None, label_opts: opts.LabelOpts = opts.LabelOpts(), + radar_index: types.Numeric = None, linestyle_opts: opts.LineStyleOpts = opts.LineStyleOpts(), areastyle_opts: opts.AreaStyleOpts = opts.AreaStyleOpts(), tooltip_opts: types.Tooltip = None, @@ -75,6 +91,7 @@ def add( "data": data, "symbol": symbol, "label": label_opts, + "radarIndex": radar_index, "itemStyle": {"normal": {"color": color}}, "lineStyle": linestyle_opts, "areaStyle": areastyle_opts, diff --git a/pyecharts/charts/basic_charts/sankey.py b/pyecharts/charts/basic_charts/sankey.py index 03edf80ea..efff4f859 100644 --- a/pyecharts/charts/basic_charts/sankey.py +++ b/pyecharts/charts/basic_charts/sankey.py @@ -30,7 +30,7 @@ def add( layout_iterations: types.Numeric = 32, orient: str = "horizontal", is_draggable: bool = True, - focus_node_adjacency: types.Union[bool, str] = False, + focus_node_mode: str = 'none', levels: types.SankeyLevel = None, label_opts: types.Label = opts.LabelOpts(), linestyle_opt: types.LineStyle = opts.LineStyleOpts(), @@ -57,7 +57,9 @@ def add( "layoutIteration": layout_iterations, "orient": orient, "draggable": is_draggable, - "focusNodeAdjacency": focus_node_adjacency, + "emphasis": { + "focus": focus_node_mode, + }, "levels": levels, "label": label_opts, "lineStyle": linestyle_opt, diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index fb156a677..185d72507 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -12,10 +12,10 @@ def __init__(self, init_opts: types.Init = opts.InitOpts()): temp_opts.update(**init_opts) init_opts = temp_opts super().__init__(init_opts=init_opts) + # Change to Echarts V5 default color list self.colors = ( - "#c23531 #2f4554 #61a0a8 #d48265 #749f83 #ca8622 #bda29a #6e7074 " - "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa " - "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597" + "#5470c6 #91cc75 #fac858 #ee6666 #73c0de #3ba272 #fc8452 #9a60b4 " + "#ea7ccc" ).split() if init_opts.opts.get("theme") == ThemeType.WHITE: self.options.update(color=self.colors) @@ -26,6 +26,25 @@ def __init__(self, init_opts: types.Init = opts.InitOpts()): ) 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 @@ -86,7 +105,8 @@ def set_series_opts( def _append_legend(self, name, is_selected): self.options.get("legend")[0].get("data").append(name) - self.options.get("legend")[0].get("selected").update({name: is_selected}) + if self.options.get("legend")[0].get("selected") is not None: + self.options.get("legend")[0].get("selected").update({name: is_selected}) def _append_color(self, color: Optional[str]): if color: @@ -194,9 +214,10 @@ def overlap(self, chart: Base): self.options.get("legend")[0].get("data").extend( chart.options.get("legend")[0].get("data") ) - self.options.get("legend")[0].get("selected").update( - chart.options.get("legend")[0].get("selected") - ) + 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")) return self diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index cae6d369a..638f91b0f 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -1145,7 +1145,7 @@ def __init__( border_color: str = "transparent", offset_center: Sequence = None, formatter: Optional[JSFunc] = None, - color: str = "auto", + color: str = "#464646", font_style: str = "normal", font_weight: str = "normal", font_family: str = "sans-serif", @@ -1194,17 +1194,23 @@ class PieLabelLineOpts(BasicOpts): def __init__( self, is_show: bool = True, - length: Numeric = None, - length_2: Numeric = None, + 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, } diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 0cf8015f8..699796bbc 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -337,10 +337,10 @@ def __init__( self, title: Optional[str] = None, title_link: Optional[str] = None, - title_target: Optional[str] = None, + title_target: Optional[str] = "blank", subtitle: Optional[str] = None, subtitle_link: Optional[str] = None, - subtitle_target: Optional[str] = None, + subtitle_target: Optional[str] = "blank", pos_left: Optional[str] = None, pos_right: Optional[str] = None, pos_top: Optional[str] = None, @@ -415,6 +415,7 @@ 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, @@ -433,6 +434,7 @@ def __init__( self.opts: dict = { "type": type_, "selectedMode": selected_mode, + "selected": selected_map, "show": is_show, "left": pos_left, "right": pos_right, @@ -649,7 +651,7 @@ def __init__( 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(), + 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, ): diff --git a/pyecharts/options/series_options.py b/pyecharts/options/series_options.py index b6c5b6e5c..a57c368a2 100644 --- a/pyecharts/options/series_options.py +++ b/pyecharts/options/series_options.py @@ -351,7 +351,7 @@ def __init__( class AreaStyleOpts(BasicOpts): - def __init__(self, opacity: Optional[Numeric] = 0, color: Optional[str] = None): + def __init__(self, opacity: Optional[Numeric] = 0, color: Optional[JSFunc] = None): self.opts: dict = {"opacity": opacity, "color": color} diff --git a/test/test_sankey.py b/test/test_sankey.py index a5dabb8ba..90a1c92fb 100644 --- a/test/test_sankey.py +++ b/test/test_sankey.py @@ -50,7 +50,6 @@ def test_sankey_new_opts(fake_writer): nodes, links, pos_bottom="10%", - focus_node_adjacency="allEdges", orient="vertical", levels=[ opts.SankeyLevelsOpts( @@ -67,4 +66,3 @@ def test_sankey_new_opts(fake_writer): assert_in("bottom", content) assert_in("orient", content) assert_in("levels", content) - assert_in("focusNodeAdjacency", content) From dc38504df9970a22725f4f67b4e0c5f9a7ed699c Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Mon, 8 Feb 2021 15:14:06 +0800 Subject: [PATCH 002/150] fix toolbox bug --- pyecharts/options/global_options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 699796bbc..7150ddb37 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -145,8 +145,8 @@ def __init__( back_title: str = "区域缩放还原", zoom_icon: Optional[JSFunc] = None, back_icon: Optional[JSFunc] = None, - xaxis_index: Union[Numeric, Sequence, bool] = False, - yaxis_index: Union[Numeric, Sequence, bool] = False, + xaxis_index: Union[Numeric, Sequence, bool] = None, + yaxis_index: Union[Numeric, Sequence, bool] = None, filter_mode: str = "filter", ): self.opts: dict = { @@ -248,7 +248,7 @@ def __init__( magic_type: Union[ ToolBoxFeatureMagicTypeOpts, dict ] = ToolBoxFeatureMagicTypeOpts(), - brush: Union[ToolBoxFeatureBrushOpts, dict] = ToolBoxFeatureBrushOpts(), + brush: Union[ToolBoxFeatureBrushOpts, dict] = None, ): self.opts: dict = { "saveAsImage": save_as_image, From 93a27d22c320e018a7eb0484fab81cc4e6ed36ef Mon Sep 17 00:00:00 2001 From: "Zhongxiang.Wang" Date: Thu, 6 May 2021 19:35:35 +0800 Subject: [PATCH 003/150] doc: remove incubating and incubator from Apache ECharts in README (zh) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5574abcd4..83ab0e953 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ ## 📣 简介 -[Apache ECharts (incubating)](https://github.com/apache/incubator-echarts) 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时,[pyecharts](https://github.com/pyecharts/pyecharts) 诞生了。 +[Apache ECharts](https://github.com/apache/echarts) 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时,[pyecharts](https://github.com/pyecharts/pyecharts) 诞生了。 ## ✨ 特性 From 32c5017de583a23eb37e2c3937adbba78141e63c Mon Sep 17 00:00:00 2001 From: "Zhongxiang.Wang" Date: Thu, 6 May 2021 19:37:25 +0800 Subject: [PATCH 004/150] doc: remove incubating and incubator from Apache ECharts in README (en) --- README.en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.en.md b/README.en.md index 69c5495fd..5bd7063b1 100644 --- a/README.en.md +++ b/README.en.md @@ -36,7 +36,7 @@ ## 📣 Introduction -[Apache ECharts (incubating)](https://github.com/apache/incubator-echarts) is easy-to-use, highly interactive and highly performant javascript visualization library under Apache license. Since its first public release in 2013, it now dominates over 74% of Chinese web front-end market. Yet Python is an expressive language and is loved by data science community. Combining the strength of both technologies, [pyecharts](https://github.com/pyecharts/pyecharts) is born. +[Apache ECharts](https://github.com/apache/echarts) is easy-to-use, highly interactive and highly performant javascript visualization library under Apache license. Since its first public release in 2013, it now dominates over 74% of Chinese web front-end market. Yet Python is an expressive language and is loved by data science community. Combining the strength of both technologies, [pyecharts](https://github.com/pyecharts/pyecharts) is born. ## ✨ Feature highlights From f8510b7983da1677adbea23d96f0a5c5a2c996ed Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 10 Jun 2021 15:10:45 +0800 Subject: [PATCH 005/150] make pyecharts support echarts 5, fix some issues and bugs --- pyecharts/_version.py | 2 +- pyecharts/charts/basic_charts/boxplot.py | 7 +++++ pyecharts/charts/composite_charts/timeline.py | 2 ++ pyecharts/globals.py | 2 +- pyecharts/options/global_options.py | 26 +++++++++++++++++++ test/test_bar.py | 4 +-- test/test_page.py | 4 +-- test/test_tab.py | 2 +- 8 files changed, 42 insertions(+), 7 deletions(-) diff --git a/pyecharts/_version.py b/pyecharts/_version.py index 957cb16d7..660f57b8d 100644 --- a/pyecharts/_version.py +++ b/pyecharts/_version.py @@ -1,2 +1,2 @@ -__version__ = "1.9.0" +__version__ = "2.0.0" __author__ = "chenjiandongx" diff --git a/pyecharts/charts/basic_charts/boxplot.py b/pyecharts/charts/basic_charts/boxplot.py index b53d242c2..82676d619 100644 --- a/pyecharts/charts/basic_charts/boxplot.py +++ b/pyecharts/charts/basic_charts/boxplot.py @@ -21,12 +21,17 @@ def add_yaxis( is_selected: bool = True, xaxis_index: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, + box_width: types.Optional[types.Sequence] = None, + selected_mode: types.Union[bool, str] = False, label_opts: types.Label = opts.LabelOpts(), markpoint_opts: types.MarkPoint = opts.MarkPointOpts(), markline_opts: types.MarkLine = opts.MarkLineOpts(), tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, ): + if box_width is None: + box_width = [7, 50] + self._append_legend(series_name, is_selected) self.options.get("series").append( { @@ -34,6 +39,8 @@ def add_yaxis( "name": series_name, "xAxisIndex": xaxis_index, "yAxisIndex": yaxis_index, + "boxWidth": box_width, + "selected_mode": selected_mode, "data": y_axis, "label": label_opts, "markPoint": markpoint_opts, diff --git a/pyecharts/charts/composite_charts/timeline.py b/pyecharts/charts/composite_charts/timeline.py index d83a93572..1188be91e 100644 --- a/pyecharts/charts/composite_charts/timeline.py +++ b/pyecharts/charts/composite_charts/timeline.py @@ -17,6 +17,7 @@ def __init__(self, init_opts: types.Init = opts.InitOpts()): 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, @@ -43,6 +44,7 @@ def add_schema( self.options.get("baseOption").get("timeline").update( { "axisType": axis_type, + "currentIndex": current_index, "orient": orient, "autoPlay": is_auto_play, "controlPosition": control_position, diff --git a/pyecharts/globals.py b/pyecharts/globals.py index 2179e0556..51faf6f98 100644 --- a/pyecharts/globals.py +++ b/pyecharts/globals.py @@ -123,7 +123,7 @@ class _NotebookType: class _OnlineHost: - DEFAULT_HOST = "https://assets.pyecharts.org/assets/" + DEFAULT_HOST = "https://assets.pyecharts.org/assets/v5/" NOTEBOOK_HOST = "http://localhost:8888/nbextensions/assets/" diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 7150ddb37..498211983 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -731,6 +731,12 @@ def __init__( is_rotate: bool = False, rotate_speed: Numeric = 10, rotate_sensitivity: Numeric = 1, + 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, ): self.opts: dict = { "boxWidth": width, @@ -740,6 +746,12 @@ def __init__( "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, }, } @@ -796,18 +808,32 @@ def __init__( name: str, 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, + axisline_opts: Union[AxisLineOpts, dict, None] = None, + axistick_opts: Union[AxisTickOpts, dict, None] = None, + axislabel_opts: Union[LabelOpts, dict, None] = None, ): self.opts: dict = { "dim": dim, "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, + "axisLine": axisline_opts, + "axisTick": axistick_opts, + "axisLabel": axislabel_opts, } diff --git a/test/test_bar.py b/test/test_bar.py index fd351ada8..1efda76b3 100644 --- a/test/test_bar.py +++ b/test/test_bar.py @@ -190,9 +190,9 @@ def test_bar_default_set_function(fake_writer): def test_bar_default_remote_host(fake_writer): c = Bar().add_xaxis(["A", "B", "C"]).add_yaxis("series0", [1, 2, 4]) c.render() - assert_equal(c.js_host, "https://assets.pyecharts.org/assets/") + assert_equal(c.js_host, "https://assets.pyecharts.org/assets/v5/") _, content = fake_writer.call_args[0] - assert_in("https://assets.pyecharts.org/assets/echarts.min.js", content) + assert_in("https://assets.pyecharts.org/assets/v5/echarts.min.js", content) @patch("pyecharts.render.engine.write_utf8_html_file") diff --git a/test/test_page.py b/test/test_page.py index 4f5c6c67a..6e0ed0ddf 100644 --- a/test/test_page.py +++ b/test/test_page.py @@ -49,7 +49,7 @@ def test_page_jshost_default(): bar = _create_bar() line = _create_line() page = Page().add(bar, line) - assert_equal(page.js_host, "https://assets.pyecharts.org/assets/") + assert_equal(page.js_host, "https://assets.pyecharts.org/assets/v5/") def test_page_jshost_custom(): @@ -84,7 +84,7 @@ def test_page_load_javascript(): line = _create_line() content = Page().add(bar, line).load_javascript() assert_equal("", content.data) - assert_equal(["https://assets.pyecharts.org/assets/echarts.min.js"], content.lib) + assert_equal(["https://assets.pyecharts.org/assets/v5/echarts.min.js"], content.lib) def _get_new_page(unique: bool = True) -> Page: diff --git a/test/test_tab.py b/test/test_tab.py index b494dca41..926654f98 100644 --- a/test/test_tab.py +++ b/test/test_tab.py @@ -59,7 +59,7 @@ def test_tab_render_notebook(): def test_page_jshost_default(): bar = _create_bar() tab = Tab().add(bar, "bar") - assert_equal(tab.js_host, "https://assets.pyecharts.org/assets/") + assert_equal(tab.js_host, "https://assets.pyecharts.org/assets/v5/") def test_tab_jshost_custom(): From f2dabcf616bb21bad844fa6c3717b93b05d4059f Mon Sep 17 00:00:00 2001 From: smthvitas Date: Fri, 15 Oct 2021 00:17:19 +0800 Subject: [PATCH 006/150] =?UTF-8?q?'=E4=BF=AE=E6=AD=A3upperlabel=E4=B8=ADL?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=A4=A7=E5=86=99=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyecharts/charts/basic_charts/treemap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyecharts/charts/basic_charts/treemap.py b/pyecharts/charts/basic_charts/treemap.py index b47f84da6..b9267d451 100644 --- a/pyecharts/charts/basic_charts/treemap.py +++ b/pyecharts/charts/basic_charts/treemap.py @@ -58,7 +58,7 @@ def add( "bottom": pos_bottom, "squareRatio": square_ratio, "label": label_opts, - "upperlabel": upper_label_opts, + "upperLabel": upper_label_opts, "leafDepth": leaf_depth, "drillDownIcon": drilldown_icon, "roam": roam, From 41def33be3b8c25786e668ce3650e7c13068f076 Mon Sep 17 00:00:00 2001 From: dongdong Date: Mon, 15 Nov 2021 14:07:51 +0800 Subject: [PATCH 007/150] Create python-app.yml --- .github/workflows/python-app.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 000000000..2a7a8e644 --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,24 @@ +name: pyecharts CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r test/requirements.txt + - name: Lint with flake8 + run: | + python setup.py install + bash test.sh From a109eb3749da45ec492fd400743d62b323e72651 Mon Sep 17 00:00:00 2001 From: dongdong Date: Mon, 15 Nov 2021 15:14:03 +0800 Subject: [PATCH 008/150] Feat: Add GitHub actions supports (#1931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update: 完善 github actions * Update: 暂时仅支持至 python3.9 --- .github/workflows/python-app.yml | 13 +++++++++---- .travis.yml | 21 --------------------- appveyor.yml | 18 ------------------ make.bat | 2 -- pyecharts/render/engine.py | 5 ++++- setup.py | 3 ++- test.py | 4 ++++ test.sh | 2 -- 8 files changed, 19 insertions(+), 49 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml delete mode 100644 make.bat create mode 100644 test.py delete mode 100644 test.sh diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 2a7a8e644..978600ff4 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -4,10 +4,11 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.6', '3.7', '3.8', '3.9'] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Set up Python @@ -18,7 +19,11 @@ jobs: run: | python -m pip install --upgrade pip pip install -r test/requirements.txt - - name: Lint with flake8 + - name: Run unit test run: | python setup.py install - bash test.sh + python test.py + - name: Codecov + run: | + cd test + codecov diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6bacc1e65..000000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -sudo: false -language: python -notifications: - email: - recipients: - - chenjiandongx@qq.com - on_success: always # default: change - on_failure: always # default: always -python: - - "3.8-dev" - - "3.8" - - "3.7" - - "3.6" -before_install: - - pip install -r test/requirements.txt -script: - - python setup.py install - - bash test.sh -after_success: - cd test && codecov - diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index ad3f1e948..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,18 +0,0 @@ -environment: - - matrix: - - PYTHON: "C:\\Python36-x64" - PYTHON_VERSION: "3.6" - - PYTHON: "C:\\Python37-x64" - PYTHON_VERSION: "3.7" - - PYTHON: "C:\\Python38-x64" - PYTHON_VERSION: "3.8" - -install: - - "%PYTHON%\\python.exe -m pip install -r requirements.txt" - - cd test - - "%PYTHON%\\python.exe -m pip install -r requirements.txt" -build: off - -test_script: - - "%PYTHON%/Scripts/nosetests --with-coverage --cover-package pyecharts --cover-package test && cd .. && %PYTHON%/Scripts/flake8 --exclude build --max-line-length 89 --ignore=F401" diff --git a/make.bat b/make.bat deleted file mode 100644 index 0ae7d7984..000000000 --- a/make.bat +++ /dev/null @@ -1,2 +0,0 @@ -cd test -nosetests --with-coverage --cover-package pyecharts --cover-package test && cd .. && flake8 --exclude build --max-line-length 89 --ignore=F401 diff --git a/pyecharts/render/engine.py b/pyecharts/render/engine.py index 8a267cd9f..481ba4018 100644 --- a/pyecharts/render/engine.py +++ b/pyecharts/render/engine.py @@ -1,5 +1,8 @@ import os -from collections import Iterable +try: + from collections.abc import Iterable +except ImportError: + from collections import Iterable from jinja2 import Environment diff --git a/setup.py b/setup.py index 5ce20f1b8..ba3f50c18 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ def run(self): zip_safe=False, include_package_data=True, classifiers=[ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", @@ -92,6 +92,7 @@ def run(self): "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries", ], cmdclass={"upload": UploadCommand}, diff --git a/test.py b/test.py new file mode 100644 index 000000000..1522b28ba --- /dev/null +++ b/test.py @@ -0,0 +1,4 @@ +import os + +os.system("nosetests --with-coverage --cover-package pyecharts --cover-package") +os.system("flake8 --exclude build --max-line-length 89 --ignore=F401") diff --git a/test.sh b/test.sh deleted file mode 100644 index 0ae7d7984..000000000 --- a/test.sh +++ /dev/null @@ -1,2 +0,0 @@ -cd test -nosetests --with-coverage --cover-package pyecharts --cover-package test && cd .. && flake8 --exclude build --max-line-length 89 --ignore=F401 From 2fae25ff74d4eab5c9ee45a28294447c62f7b793 Mon Sep 17 00:00:00 2001 From: dongdong Date: Mon, 15 Nov 2021 15:18:40 +0800 Subject: [PATCH 009/150] Update README.md --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 83ab0e953..39711075f 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,8 @@ Python ❤️ ECharts = pyecharts

- - Travis Build Status - - - Appveyor Build Status + + Github Actions Status Codecov From d085837000d620793d4482f0d33d9f6d961c3524 Mon Sep 17 00:00:00 2001 From: dongdong Date: Mon, 15 Nov 2021 15:19:18 +0800 Subject: [PATCH 010/150] Update README.en.md --- README.en.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.en.md b/README.en.md index 5bd7063b1..bbfb8bc3d 100644 --- a/README.en.md +++ b/README.en.md @@ -6,11 +6,8 @@ Python ❤️ ECharts = pyecharts

- - Travis Build Status - - - Appveyor Build Status + + Github Actions Status Codecov From 911493f4db7fef6c0f6311d2085d769d2091f061 Mon Sep 17 00:00:00 2001 From: dongdong Date: Tue, 16 Nov 2021 00:50:49 +0800 Subject: [PATCH 011/150] Add python3.10 supports (#1932) --- .github/workflows/python-app.yml | 3 ++- setup.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 978600ff4..f568bc84b 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.6', '3.7', '3.8', '3.9'] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -18,6 +18,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install -r requirements.txt pip install -r test/requirements.txt - name: Run unit test run: | diff --git a/setup.py b/setup.py index ba3f50c18..76da9f96f 100644 --- a/setup.py +++ b/setup.py @@ -93,6 +93,7 @@ def run(self): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Software Development :: Libraries", ], cmdclass={"upload": UploadCommand}, From 4c7e64739e4f6bf695e1ed53523692bbe9cabc0a Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 16 Nov 2021 14:21:57 +0800 Subject: [PATCH 012/150] hot fix for python3.10 -- version 1.9.1 --- pyecharts/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyecharts/_version.py b/pyecharts/_version.py index 957cb16d7..cb3a14c19 100644 --- a/pyecharts/_version.py +++ b/pyecharts/_version.py @@ -1,2 +1,2 @@ -__version__ = "1.9.0" +__version__ = "1.9.1" __author__ = "chenjiandongx" From e6eadc099b666c67baf29e14721aca771ea5d4fa Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 16 Nov 2021 14:46:39 +0800 Subject: [PATCH 013/150] add some configs on DatazoomOpts and TooltipOpts --- pyecharts/options/global_options.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 498211983..e57029ef9 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -376,6 +376,8 @@ def __init__( is_show: bool = True, type_: str = "slider", 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, @@ -393,6 +395,8 @@ def __init__( 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, @@ -543,6 +547,9 @@ def __init__( 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, position: Union[str, Sequence, JSFunc] = None, formatter: Optional[JSFunc] = None, background_color: Optional[str] = None, @@ -560,6 +567,9 @@ def __init__( "alwaysShowContent": is_always_show_content, "showDelay": show_delay, "hideDelay": hide_delay, + "enterable": is_enterable, + "confine": is_confine, + "appendToBody": is_append_to_body, "position": position, "formatter": formatter, "textStyle": textstyle_opts, From 3547c50434400e6bd347204afff56331f27ea767 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 16 Nov 2021 15:16:47 +0800 Subject: [PATCH 014/150] update some config with ThreeAxisChart and Axis3DOpts --- pyecharts/charts/chart.py | 4 ++-- pyecharts/options/global_options.py | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index 185d72507..1bf859866 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -243,8 +243,8 @@ def add( shading: Optional[str] = None, itemstyle_opts: types.ItemStyle = None, label_opts: types.Label = opts.LabelOpts(is_show=False), - xaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="category"), - yaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="category"), + xaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="value"), + yaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="value"), zaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="value"), grid3d_opts: types.Grid3D = opts.Grid3DOpts(), encode: types.Union[types.JSFunc, dict, None] = None, diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index e57029ef9..1fccdbca9 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -772,24 +772,39 @@ def __init__( 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, splitnum: Optional[Numeric] = None, - interval: Optional[Numeric] = None, - margin: Numeric = 8, + 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, "splitNum": splitnum, "type": type_, "min": min_, "max": max_, - "axisLabel": {"margin": margin, "interval": interval}, + "axisLine": axisline_opts, + "axisTick": axistick_opts, + "axisLabel": axislabel_opts, + "axisPointer": axispointer_opts, + "splitLine": splitline_opts, + "splitArea": splitarea_opts, } From bbf193925fc651686ca9274ca1aae30c498ef2b9 Mon Sep 17 00:00:00 2001 From: jackzhenguo Date: Mon, 13 Dec 2021 17:13:32 +0800 Subject: [PATCH 015/150] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dchart=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E7=9A=84=5Fappend=5Fcolor=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _append_color方法,添加轴(执行add_yaxis操作)的顺序与新添加的color值(设置color属性)未一一对应,正好颠倒。已修复。 --- pyecharts/charts/chart.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index fb156a677..71537b7bb 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -17,6 +17,7 @@ def __init__(self, init_opts: types.Init = opts.InitOpts()): "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa " "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597" ).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( @@ -90,7 +91,9 @@ def _append_legend(self, name, is_selected): def _append_color(self, color: Optional[str]): if color: - self.colors = [color] + self.colors + # 这是一个bug + # 添加轴(执行add_yaxis操作)的顺序与新添加的color值(设置color属性)未一一对应,正好颠倒 + self.colors.insert(-self.default_color_n, color) if self.theme == ThemeType.WHITE: self.options.update(color=self.colors) From e3b1297d1a54aff47b68d08c7205a7164b673dec Mon Sep 17 00:00:00 2001 From: zjdorm <99162430@qq.com> Date: Thu, 13 Jan 2022 13:59:37 +0800 Subject: [PATCH 016/150] =?UTF-8?q?=E7=BB=99MarkLineItem=EF=BC=9A=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=E7=BA=BF=E6=95=B0=E6=8D=AE=E9=A1=B9=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?linestyle=5Fopts=E5=8F=82=E6=95=B0=EF=BC=8C=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=92=8CMarkLineOpts=EF=BC=9A=E6=A0=87=E8=AE=B0=E7=BA=BF?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=A1=B9=E9=87=8C=E7=9A=84linestyle=5Fopts?= =?UTF-8?q?=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyecharts/options/series_options.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyecharts/options/series_options.py b/pyecharts/options/series_options.py index a57c368a2..083d4b094 100644 --- a/pyecharts/options/series_options.py +++ b/pyecharts/options/series_options.py @@ -215,6 +215,7 @@ def __init__( 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, ): @@ -227,6 +228,7 @@ def __init__( "x": xcoord, "yAxis": y, "y": ycoord, + "lineStyle": linestyle_opts, "coord": coord, "symbol": symbol, "symbolSize": symbol_size, From 58ca07510fa663983f9a0e89d4bafd48eb73b958 Mon Sep 17 00:00:00 2001 From: guozhen3 Date: Fri, 28 Jan 2022 14:49:53 +0800 Subject: [PATCH 017/150] =?UTF-8?q?=E6=B7=BB=E5=8A=A0chart=E9=A2=9C?= =?UTF-8?q?=E8=89=B2=E9=A2=A0=E5=80=92=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=A8=A1=E5=9D=97=20=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_chart.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/test_chart.py diff --git a/test/test_chart.py b/test/test_chart.py new file mode 100644 index 000000000..8f036df3b --- /dev/null +++ b/test/test_chart.py @@ -0,0 +1,29 @@ +from nose.tools import assert_equal +from pyecharts.charts import Line + + +def test_chart_append_color(): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis( + series_name="品类 1", + y_axis=y_data1, + color='#80FFA5') + .add_yaxis( + series_name="品类 2", + y_axis=y_data2, + color='#00DDFF') + ) + c.render() + default_colors = ( + "#c23531 #2f4554 #61a0a8 #d48265 #749f83 #ca8622 #bda29a #6e7074 " + "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa " + "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597" + ).split() + expected_result = ['#80FFA5', '#00DDFF', *default_colors] + assert_equal(c.colors, expected_result) From c6197f6c4384b6150cdc1f47edd3e39fc3bdba21 Mon Sep 17 00:00:00 2001 From: guozhen3 Date: Fri, 18 Feb 2022 16:43:28 +0800 Subject: [PATCH 018/150] overlap method of chart.py --- pyecharts/charts/chart.py | 4 +++ test/test.csv | 6 ++++ test/test_chart.py | 8 ++++-- test/test_chart_diff.py | 27 ++++++++++++++++++ test/test_grid2.py | 27 ++++++++++++++++++ test/test_overlap.py | 60 +++++++++++++++++++++++++++++++++++++++ test/test_stack2.py | 38 +++++++++++++++++++++++++ 7 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 test/test.csv create mode 100644 test/test_chart_diff.py create mode 100644 test/test_grid2.py create mode 100644 test/test_overlap.py create mode 100644 test/test_stack2.py diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index 71537b7bb..ba6dcb284 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -94,6 +94,7 @@ def _append_color(self, color: Optional[str]): # 这是一个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) @@ -201,6 +202,9 @@ def overlap(self, chart: Base): 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 diff --git a/test/test.csv b/test/test.csv new file mode 100644 index 000000000..5fcc2beb0 --- /dev/null +++ b/test/test.csv @@ -0,0 +1,6 @@ +item,vol,chg +a,35585,-3918 +b,26219,1568 +c,24921,-1162 +d,21255,960 +e,16804,738 diff --git a/test/test_chart.py b/test/test_chart.py index 8f036df3b..ec71a8862 100644 --- a/test/test_chart.py +++ b/test/test_chart.py @@ -1,8 +1,11 @@ +from unittest.mock import patch + from nose.tools import assert_equal from pyecharts.charts import Line -def test_chart_append_color(): +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_append_color(fake_writer): x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] y_data1 = [140, 232, 101, 264, 90, 340, 250] y_data2 = [120, 282, 111, 234, 220, 340, 310] @@ -20,10 +23,11 @@ def test_chart_append_color(): color='#00DDFF') ) c.render() + _, content = fake_writer.call_args[0] default_colors = ( "#c23531 #2f4554 #61a0a8 #d48265 #749f83 #ca8622 #bda29a #6e7074 " "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa " "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597" ).split() expected_result = ['#80FFA5', '#00DDFF', *default_colors] - assert_equal(c.colors, expected_result) + assert_equal(c.colors, expected_result) \ No newline at end of file diff --git a/test/test_chart_diff.py b/test/test_chart_diff.py new file mode 100644 index 000000000..d2a610d38 --- /dev/null +++ b/test/test_chart_diff.py @@ -0,0 +1,27 @@ +# encoding: utf-8 +""" +@file: test_chart_diff.py +@desc: +@author: guozhen3 +@time: 2022/1/28 +""" +from pyecharts.charts import Line + +x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] +y_data1 = [140, 232, 101, 264, 90, 340, 250] +y_data2 = [120, 282, 111, 234, 220, 340, 310] + +c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis( + series_name="品类 1", + y_axis=y_data1, + color='#80FFA5') + .add_yaxis( + series_name="品类 2", + y_axis=y_data2, + color='#00DDFF') + ) + +c.render('rend2.html') \ No newline at end of file diff --git a/test/test_grid2.py b/test/test_grid2.py new file mode 100644 index 000000000..5296c3a0c --- /dev/null +++ b/test/test_grid2.py @@ -0,0 +1,27 @@ +# encoding: utf-8 +""" +@file: test.grid.py +@desc: +@author: guozhen3 +@time: 2022/2/18 +""" + +from nose.tools import assert_equal + +from pyecharts import options as opts +from pyecharts.charts import Bar, Grid, Line +from test_overlap import test_chart_for_grid + + +def test_grid_control_axis_index(): + bar = test_chart_for_grid() + gc = Grid().add( + bar, opts.GridOpts(pos_left="5%", pos_right="20%"), is_control_axis_index=True + ) + expected_idx = (0, 1, 2) + for idx, series in enumerate(gc.options.get("series")): + assert_equal(series.get("yAxisIndex"), expected_idx[idx]) + gc.render("grid_test.html") + + +test_grid_control_axis_index() diff --git a/test/test_overlap.py b/test/test_overlap.py new file mode 100644 index 000000000..8cd2162c8 --- /dev/null +++ b/test/test_overlap.py @@ -0,0 +1,60 @@ +# encoding: utf-8 +""" +@file: test_overlap.py +@desc: +@author: guozhen3 +@time: 2022/2/18 +""" + +from unittest.mock import patch + +from nose.tools import assert_equal, assert_in + +from pyecharts import options as opts +from pyecharts.charts import Bar, Grid, Line + + +def test_chart_for_grid(): + x_data = ["{}月".format(i) for i in range(1, 13)] + bar = ( + Bar() + .add_xaxis(x_data) + .add_yaxis( + "蒸发量", + [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], + yaxis_index=0, + color='red' + ) + .add_yaxis( + "降水量", + [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], + yaxis_index=1, + color='green' + ) + .extend_axis(yaxis=opts.AxisOpts(name="蒸发量", type_="value", position="right")) + .extend_axis(yaxis=opts.AxisOpts(type_="value", name="温度", position="left")) + .set_global_opts( + yaxis_opts=opts.AxisOpts(name="降水量", position="right", offset=80) + ) + ) + + line = ( + Line() + .add_xaxis(x_data) + .add_yaxis( + "平均温度", + [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], + yaxis_index=2, + color="blue", + label_opts=opts.LabelOpts(is_show=False), + ) + ) + + bar.overlap(line) + assert_equal(bar.colors[:3], ['red', 'green', 'blue']) + bar.render("overlap_test_after_colors_update.html") + return bar + + +if __name__ == "__main__": + test_chart_for_grid() diff --git a/test/test_stack2.py b/test/test_stack2.py new file mode 100644 index 000000000..dd66e78c3 --- /dev/null +++ b/test/test_stack2.py @@ -0,0 +1,38 @@ +# encoding: utf-8 +""" +@file: test_stack2.py +@desc: +@author: guozhen3 +@time: 2022/2/18 +""" + +import pandas as pd +from pyecharts.charts import Bar + +data = pd.read_csv("test.csv") +data["base_vol"] = data["chg"].map(lambda x: -x if x > 0 else 0) + data["vol"] + +bar = ( +Bar() +.add_xaxis(xaxis_data=data["item"].tolist()) +.add_yaxis( +series_name="Position Vol", +y_axis=data["base_vol"].tolist(), +color="LightSeaGreen", +stack="vol" +) +.add_yaxis( +series_name="Increased", +y_axis=data["chg"].map(lambda x: x if x > 0 else 0).tolist(), +color="red", +stack="vol" +).add_yaxis( +series_name="Dcreased", +y_axis=data["chg"].map(lambda x: -x if x < 0 else 0).tolist(), +color="green", +stack="vol" +) + + +) +bar.render() \ No newline at end of file From 2b6fd503349b72b6addad57ff33d253c22743a78 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 12 Apr 2022 16:45:16 +0800 Subject: [PATCH 019/150] Update basic_charts, composite_charts and some charts options --- pyecharts/charts/base.py | 1 + pyecharts/charts/basic_charts/boxplot.py | 5 +- pyecharts/charts/basic_charts/calendar.py | 18 ++- pyecharts/charts/basic_charts/gauge.py | 4 + pyecharts/charts/basic_charts/line.py | 2 + pyecharts/charts/chart.py | 9 ++ pyecharts/charts/composite_charts/timeline.py | 4 + pyecharts/options/__init__.py | 4 + pyecharts/options/global_options.py | 132 +++++++++++++++++- pyecharts/options/series_options.py | 2 + pyecharts/types.py | 2 +- 11 files changed, 174 insertions(+), 9 deletions(-) diff --git a/pyecharts/charts/base.py b/pyecharts/charts/base.py index 3c9d724d1..1ca344a41 100644 --- a/pyecharts/charts/base.py +++ b/pyecharts/charts/base.py @@ -39,6 +39,7 @@ def __init__(self, init_opts: Union[InitOpts, dict] = InitOpts()): self.js_dependencies: utils.OrderedSet = utils.OrderedSet("echarts") self.options.update(backgroundColor=_opts.get("bg_color")) self.options.update(_opts.get("animationOpts", AnimationOpts()).opts) + self.options.update(aria=_opts.get("ariaOpts")) self._is_geo_chart: bool = False def get_options(self) -> dict: diff --git a/pyecharts/charts/basic_charts/boxplot.py b/pyecharts/charts/basic_charts/boxplot.py index 82676d619..f537153e7 100644 --- a/pyecharts/charts/basic_charts/boxplot.py +++ b/pyecharts/charts/basic_charts/boxplot.py @@ -18,9 +18,11 @@ def add_yaxis( series_name: str, y_axis: types.Sequence[types.Union[opts.BoxplotItem, dict]], *, + chart_type: str = ChartType.BOXPLOT, is_selected: bool = True, xaxis_index: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, + dataset_index: types.Optional[types.Numeric] = None, box_width: types.Optional[types.Sequence] = None, selected_mode: types.Union[bool, str] = False, label_opts: types.Label = opts.LabelOpts(), @@ -35,10 +37,11 @@ def add_yaxis( self._append_legend(series_name, is_selected) self.options.get("series").append( { - "type": ChartType.BOXPLOT, + "type": chart_type, "name": series_name, "xAxisIndex": xaxis_index, "yAxisIndex": yaxis_index, + "datasetIndex": dataset_index, "boxWidth": box_width, "selected_mode": selected_mode, "data": y_axis, diff --git a/pyecharts/charts/basic_charts/calendar.py b/pyecharts/charts/basic_charts/calendar.py index c4b1c97fd..766ac74fc 100644 --- a/pyecharts/charts/basic_charts/calendar.py +++ b/pyecharts/charts/basic_charts/calendar.py @@ -1,6 +1,7 @@ from ... import options as opts from ... import types from ...charts.chart import Chart +from ...globals import ChartType class Calendar(Chart): @@ -21,28 +22,33 @@ def add( series_name: str, yaxis_data: types.Sequence, *, + type_: types.Union[str, ChartType] = ChartType.HEATMAP, is_selected: bool = True, - label_opts: types.Label = opts.LabelOpts( - is_show=False, - position="inside", - ), - calendar_opts: types.Calendar = None, + 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, + **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, is_selected) self.options.get("series").append( { - "type": "heatmap", + "type": type_, "coordinateSystem": "calendar", + "calendarIndex": calendar_index, "name": series_name, "data": yaxis_data, "label": label_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + **other_calendar_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/gauge.py b/pyecharts/charts/basic_charts/gauge.py index d9d176f8f..424fec815 100644 --- a/pyecharts/charts/basic_charts/gauge.py +++ b/pyecharts/charts/basic_charts/gauge.py @@ -20,6 +20,7 @@ def add( 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, @@ -36,6 +37,8 @@ def add( axisline_opts: types.AxisLine = None, itemstyle_opts: types.ItemStyle = None, ): + if center is None: + center = ["50%", "50%"] self._append_legend(series_name, is_selected) self.options.get("series").append( @@ -47,6 +50,7 @@ def add( "min": min_, "max": max_, "splitNumber": split_number, + "center": center, "radius": radius, "startAngle": start_angle, "endAngle": end_angle, diff --git a/pyecharts/charts/basic_charts/line.py b/pyecharts/charts/basic_charts/line.py index 011b1b15b..b7ed166ac 100644 --- a/pyecharts/charts/basic_charts/line.py +++ b/pyecharts/charts/basic_charts/line.py @@ -32,6 +32,7 @@ def add_yaxis( is_hover_animation: bool = True, z_level: types.Numeric = 0, z: types.Numeric = 0, + log_base: types.Numeric = 10, markpoint_opts: types.MarkPoint = None, markline_opts: types.MarkLine = None, tooltip_opts: types.Tooltip = None, @@ -67,6 +68,7 @@ def add_yaxis( "data": data, "hoverAnimation": is_hover_animation, "label": label_opts, + "logBase": log_base, "lineStyle": linestyle_opts, "areaStyle": areastyle_opts, "markPoint": markpoint_opts, diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index 1bf859866..0ad8fd09a 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -17,6 +17,7 @@ def __init__(self, init_opts: types.Init = opts.InitOpts()): "#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( @@ -167,12 +168,20 @@ def add_dataset( 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, ): 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 diff --git a/pyecharts/charts/composite_charts/timeline.py b/pyecharts/charts/composite_charts/timeline.py index 1188be91e..aa76d7d89 100644 --- a/pyecharts/charts/composite_charts/timeline.py +++ b/pyecharts/charts/composite_charts/timeline.py @@ -90,6 +90,10 @@ def add(self, chart: Base, time_point: str): "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"), } ) self.__check_components(chart) diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index dd58ccf67..09fa3975e 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -56,6 +56,9 @@ AngleAxisItem, AngleAxisOpts, AnimationOpts, + AriaDecalOpts, + AriaLabelOpts, + AriaOpts, Axis3DOpts, AxisLineOpts, AxisOpts, @@ -67,6 +70,7 @@ CalendarMonthLabelOpts, CalendarYearLabelOpts, DataZoomOpts, + DatasetTransformOpts, Grid3DOpts, GridOpts, InitOpts, diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 1fccdbca9..221f00cbe 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -41,6 +41,114 @@ def __init__( } +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, @@ -53,6 +161,7 @@ def __init__( bg_color: Union[str, dict] = None, js_host: str = "", animation_opts: Union[AnimationOpts, dict] = AnimationOpts(), + aria_opts: Union[AriaOpts, dict] = AriaOpts() ): self.opts: dict = { "width": width, @@ -64,6 +173,7 @@ def __init__( "bg_color": bg_color, "js_host": js_host, "animationOpts": animation_opts, + "ariaOpts": aria_opts, } @@ -466,14 +576,16 @@ def __init__( range_text: Optional[Sequence] = None, range_color: Optional[Sequence[str]] = None, range_size: Optional[Sequence[int]] = None, - range_opacity: Optional[Numeric] = 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, @@ -519,11 +631,13 @@ def __init__( "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, @@ -970,6 +1084,8 @@ def __init__( 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, @@ -984,8 +1100,11 @@ def __init__( 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, @@ -1000,6 +1119,7 @@ def __init__( "dayLabel": daylabel_opts, "monthLabel": monthlabel_opts, "yearLabel": yearlabel_opts, + "silent": is_silent, } @@ -1153,3 +1273,13 @@ def __init__( 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} diff --git a/pyecharts/options/series_options.py b/pyecharts/options/series_options.py index a57c368a2..0d7cd1f05 100644 --- a/pyecharts/options/series_options.py +++ b/pyecharts/options/series_options.py @@ -25,6 +25,7 @@ def __init__( 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, ): @@ -35,6 +36,7 @@ def __init__( "borderColor0": border_color0, "borderWidth": border_width, "borderType": border_type, + "borderRadius": border_radius, "opacity": opacity, "areaColor": area_color, } diff --git a/pyecharts/types.py b/pyecharts/types.py index 81690bf4c..839b2c79b 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -63,7 +63,7 @@ Calendar = Union[opts.CalendarOpts, dict, None] CalendarDayLabelOpts = Union[opts.CalendarDayLabelOpts, dict, None] CalendarMonthLabelOpts = Union[opts.CalendarMonthLabelOpts, dict, None] -calendarYearLabelOpts = Union[opts.CalendarYearLabelOpts, dict, None] +CalendarYearLabelOpts = Union[opts.CalendarYearLabelOpts, dict, None] GraphNode = Union[opts.GraphNode, dict] GraphLink = Union[opts.GraphLink, dict] From 9eca43384f74055392213e0feda5c014f5530863 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 12 Apr 2022 17:15:27 +0800 Subject: [PATCH 020/150] Try to fix CI error --- test/requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/requirements.txt b/test/requirements.txt index 154240d5f..e9470c7bc 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,6 +1,8 @@ nose codecov coverage -jupyter +jupyter==1.0.0 flake8 mccabe +notebook==6.4.8 +jupyter_console==6.4.0 From 18f3f05805f609ccc432685f1c23ea3223495367 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 12 Apr 2022 17:46:36 +0800 Subject: [PATCH 021/150] Try to fix CI error again --- test/requirements.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/requirements.txt b/test/requirements.txt index e9470c7bc..53d79bbd0 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,8 +1,5 @@ nose codecov coverage -jupyter==1.0.0 flake8 mccabe -notebook==6.4.8 -jupyter_console==6.4.0 From d25cca137b13fdd852bf91d74de816847877bd05 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Wed, 13 Apr 2022 19:05:11 +0800 Subject: [PATCH 022/150] Update many charts options and Add a Custom Chart Series --- pyecharts/charts/__init__.py | 1 + pyecharts/charts/basic_charts/boxplot.py | 4 +- pyecharts/charts/basic_charts/custom.py | 67 +++++++++++++++++++++++ pyecharts/charts/basic_charts/pie.py | 2 +- pyecharts/charts/chart.py | 35 ++++++++---- pyecharts/options/global_options.py | 68 +++++++++++++++++++++++- 6 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 pyecharts/charts/basic_charts/custom.py diff --git a/pyecharts/charts/__init__.py b/pyecharts/charts/__init__.py index efc966e77..9107e60a3 100644 --- a/pyecharts/charts/__init__.py +++ b/pyecharts/charts/__init__.py @@ -3,6 +3,7 @@ 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 diff --git a/pyecharts/charts/basic_charts/boxplot.py b/pyecharts/charts/basic_charts/boxplot.py index f537153e7..7a372443e 100644 --- a/pyecharts/charts/basic_charts/boxplot.py +++ b/pyecharts/charts/basic_charts/boxplot.py @@ -16,7 +16,9 @@ class Boxplot(RectChart): def add_yaxis( self, series_name: str, - y_axis: types.Sequence[types.Union[opts.BoxplotItem, dict]], + y_axis: types.Optional[ + types.Sequence[types.Union[opts.BoxplotItem, dict]] + ] = None, *, chart_type: str = ChartType.BOXPLOT, is_selected: bool = True, diff --git a/pyecharts/charts/basic_charts/custom.py b/pyecharts/charts/basic_charts/custom.py new file mode 100644 index 000000000..45b0df015 --- /dev/null +++ b/pyecharts/charts/basic_charts/custom.py @@ -0,0 +1,67 @@ +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: types.JSFunc = "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, + ): + + 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, + } + ) + return self diff --git a/pyecharts/charts/basic_charts/pie.py b/pyecharts/charts/basic_charts/pie.py index 49cfe30fd..e8884ea71 100644 --- a/pyecharts/charts/basic_charts/pie.py +++ b/pyecharts/charts/basic_charts/pie.py @@ -33,7 +33,7 @@ def add( 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").get("source")][1:] + data=[d[0] for d in self.options.get("dataset")[0].get("source")][1:] ) elif isinstance(data_pair[0], opts.PieItem): data = data_pair diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index db31be3c9..eaa94bd63 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -176,17 +176,30 @@ def add_dataset( from_dataset_id: types.Optional[types.Numeric] = None, from_transform_result: types.Optional[types.Numeric] = None, ): - 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, - } - ) + 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 diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 221f00cbe..c64dab083 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -664,13 +664,17 @@ def __init__( 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: TextStyleOpts = TextStyleOpts(font_size=14), + extra_css_text: Optional[str] = None, + order: str = "seriesAsc", ): self.opts: dict = { "show": is_show, @@ -684,13 +688,17 @@ def __init__( "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, } @@ -736,12 +744,14 @@ def __init__( is_show: bool = False, link: Sequence[dict] = None, type_: str = "line", + is_snap: Optional[bool] = False, 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, @@ -826,6 +836,10 @@ def __init__( 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 = { @@ -842,6 +856,10 @@ def __init__( "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, } @@ -930,6 +948,11 @@ def __init__( 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, @@ -937,6 +960,11 @@ def __init__( "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, } @@ -944,7 +972,9 @@ class ParallelAxisOpts(BasicOpts): def __init__( self, dim: Numeric, - name: str, + parallel_index: Numeric = 0, + is_realtime: bool = True, + name: Optional[str] = None, data: Sequence = None, type_: Optional[str] = None, name_location: str = "end", @@ -954,12 +984,18 @@ def __init__( 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_, @@ -970,9 +1006,13 @@ def __init__( "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, } @@ -1180,16 +1220,25 @@ def __init__( 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: @@ -1205,16 +1254,25 @@ def __init__( "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, } @@ -1223,7 +1281,7 @@ def __init__( self, polar_index: Optional[int] = None, data: Optional[Sequence[Union[AngleAxisItem, Numeric, dict, str]]] = None, - start_angle: Optional[Numeric] = None, + start_angle: Optional[Numeric] = 90, is_clockwise: bool = False, boundary_gap: Union[bool, Sequence, None] = None, type_: Optional[str] = None, @@ -1236,7 +1294,10 @@ def __init__( 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: @@ -1261,7 +1322,10 @@ def __init__( "axisLine": axisline_opts, "axisTick": axistick_opts, "axisLabel": axislabel_opts, + "minorTick": minor_tick_opts, + "minorSplitLine": minor_split_line_opts, "z": z, + "zlevel": z_level, } From 3cde53e1f305b74ae2f731c35442b33227ef71c4 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Wed, 13 Apr 2022 19:31:43 +0800 Subject: [PATCH 023/150] Update and Add test code && Format chart.py --- pyecharts/charts/chart.py | 2 +- test/test.csv | 6 ---- test/test_chart.py | 28 ++++++++--------- test/test_custom.py | 48 +++++++++++++++++++++++++++++ test/test_global_options.py | 12 +++++++- test/test_grid.py | 56 ++++++++++++++++++++++++++++++++++ test/test_grid2.py | 27 ----------------- test/test_overlap.py | 60 ------------------------------------- test/test_stack2.py | 38 ----------------------- 9 files changed, 130 insertions(+), 147 deletions(-) delete mode 100644 test/test.csv create mode 100644 test/test_custom.py delete mode 100644 test/test_grid2.py delete mode 100644 test/test_overlap.py delete mode 100644 test/test_stack2.py diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index eaa94bd63..209c93567 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -246,7 +246,7 @@ def overlap(self, chart: Base): 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) + self.colors.insert(len(self.colors) - self.default_color_n, c) return self diff --git a/test/test.csv b/test/test.csv deleted file mode 100644 index 5fcc2beb0..000000000 --- a/test/test.csv +++ /dev/null @@ -1,6 +0,0 @@ -item,vol,chg -a,35585,-3918 -b,26219,1568 -c,24921,-1162 -d,21255,960 -e,16804,738 diff --git a/test/test_chart.py b/test/test_chart.py index ec71a8862..87b265a22 100644 --- a/test/test_chart.py +++ b/test/test_chart.py @@ -12,22 +12,22 @@ def test_chart_append_color(fake_writer): c = ( Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis( - series_name="品类 1", - y_axis=y_data1, - color='#80FFA5') - .add_yaxis( - series_name="品类 2", - y_axis=y_data2, - color='#00DDFF') + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") ) c.render() _, content = fake_writer.call_args[0] + # Old Version (Before 2.0) + # default_colors = ( + # "#c23531 #2f4554 #61a0a8 #d48265 #749f83 #ca8622 #bda29a #6e7074 " + # "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa " + # "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597" + # ).split() + + # New Version default_colors = ( - "#c23531 #2f4554 #61a0a8 #d48265 #749f83 #ca8622 #bda29a #6e7074 " - "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa " - "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597" + "#5470c6 #91cc75 #fac858 #ee6666 #73c0de #3ba272 #fc8452 #9a60b4 " "#ea7ccc" ).split() - expected_result = ['#80FFA5', '#00DDFF', *default_colors] - assert_equal(c.colors, expected_result) \ No newline at end of file + expected_result = ["#80FFA5", "#00DDFF", *default_colors] + assert_equal(c.colors, expected_result) diff --git a/test/test_custom.py b/test/test_custom.py new file mode 100644 index 000000000..362fc23ef --- /dev/null +++ b/test/test_custom.py @@ -0,0 +1,48 @@ +from unittest.mock import patch + +from nose.tools import assert_greater, assert_in + + +from pyecharts.charts import Custom +from pyecharts.commons.utils import JsCode + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_custom_base(fake_writer): + c = ( + Custom() + .add( + series_name="", + render_item=JsCode(""" + function (params, api) { + var categoryIndex = api.value(0); + var start = api.coord([api.value(1), categoryIndex]); + var end = api.coord([api.value(2), categoryIndex]); + var height = api.size([0, 1])[1] * 0.6; + + var rectShape = echarts.graphic.clipRectByRect({ + x: start[0], + y: start[1] - height / 2, + width: end[0] - start[0], + height: height + }, { + x: params.coordSys.x, + y: params.coordSys.y, + width: params.coordSys.width, + height: params.coordSys.height + }); + + return rectShape && { + type: 'rect', + shape: rectShape, + style: api.style() + }; + } + """), + data=None, + ) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_greater(len(content), 2000) + assert_in("renderItem", content) diff --git a/test/test_global_options.py b/test/test_global_options.py index 6d5ddde37..3eceede7d 100644 --- a/test/test_global_options.py +++ b/test/test_global_options.py @@ -36,7 +36,7 @@ def test_animation_options_remove_none(): def test_init_options_remove_none(): - option = InitOpts(animation_opts={}) + option = InitOpts(animation_opts={}, aria_opts={}) expected = { "animationOpts": {}, "height": "500px", @@ -44,6 +44,7 @@ def test_init_options_remove_none(): "renderer": "canvas", "theme": "white", "width": "900px", + "ariaOpts": {}, } assert_equal(expected, remove_key_with_none_value(option.opts)) @@ -118,6 +119,8 @@ def test_data_zoom_options_remove_none(): "start": 20, "type": "slider", "zoomLock": False, + "showDetail": True, + "showDataShadow": True, } assert_equal(expected, remove_key_with_none_value(option.opts)) @@ -149,6 +152,8 @@ def test_visual_map_options_remove_none(): "inverse": False, "splitNumber": 5, "type": "continuous", + "hoverLink": True, + "padding": 5, "borderWidth": 0, } assert_equal(expected, remove_key_with_none_value(option.opts)) @@ -166,6 +171,11 @@ def test_tool_tip_options_remove_none(): "showContent": True, "showDelay": 0, "trigger": "item", + "enterable": False, + "confine": False, + "appendToBody": False, + "transitionDuration": 0.4, + "order": "seriesAsc", "triggerOn": "mousemove|click", } assert_equal(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_grid.py b/test/test_grid.py index 6b684c1ee..00c46a25b 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -71,3 +71,59 @@ def test_grid_options(fake_writer): gc.render() _, content = fake_writer.call_args[0] assert_in("containLabel", content) + + +def test_chart_for_grid(): + x_data = ["{}月".format(i) for i in range(1, 13)] + bar = ( + Bar() + .add_xaxis(x_data) + .add_yaxis( + "蒸发量", + [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], + yaxis_index=0, + color="red", + ) + .add_yaxis( + "降水量", + [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], + yaxis_index=1, + color="green", + ) + .extend_axis(yaxis=opts.AxisOpts(name="蒸发量", type_="value", position="right")) + .extend_axis(yaxis=opts.AxisOpts(type_="value", name="温度", position="left")) + .set_global_opts( + yaxis_opts=opts.AxisOpts(name="降水量", position="right", offset=80) + ) + ) + + line = ( + Line() + .add_xaxis(x_data) + .add_yaxis( + "平均温度", + [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], + yaxis_index=2, + color="blue", + label_opts=opts.LabelOpts(is_show=False), + ) + ) + + bar.overlap(line) + assert_equal(bar.colors[:3], ["red", "green", "blue"]) + bar.render("overlap_test_after_colors_update.html") + return bar + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_grid_example_1(fake_writer): + bar = test_chart_for_grid() + gc = Grid().add( + bar, opts.GridOpts(pos_left="5%", pos_right="20%"), is_control_axis_index=True + ) + expected_idx = (0, 1, 2) + for idx, series in enumerate(gc.options.get("series")): + assert_equal(series.get("yAxisIndex"), expected_idx[idx]) + gc.render() + _, content = fake_writer.call_args[0] + assert_in("yAxisIndex", content) diff --git a/test/test_grid2.py b/test/test_grid2.py deleted file mode 100644 index 5296c3a0c..000000000 --- a/test/test_grid2.py +++ /dev/null @@ -1,27 +0,0 @@ -# encoding: utf-8 -""" -@file: test.grid.py -@desc: -@author: guozhen3 -@time: 2022/2/18 -""" - -from nose.tools import assert_equal - -from pyecharts import options as opts -from pyecharts.charts import Bar, Grid, Line -from test_overlap import test_chart_for_grid - - -def test_grid_control_axis_index(): - bar = test_chart_for_grid() - gc = Grid().add( - bar, opts.GridOpts(pos_left="5%", pos_right="20%"), is_control_axis_index=True - ) - expected_idx = (0, 1, 2) - for idx, series in enumerate(gc.options.get("series")): - assert_equal(series.get("yAxisIndex"), expected_idx[idx]) - gc.render("grid_test.html") - - -test_grid_control_axis_index() diff --git a/test/test_overlap.py b/test/test_overlap.py deleted file mode 100644 index 8cd2162c8..000000000 --- a/test/test_overlap.py +++ /dev/null @@ -1,60 +0,0 @@ -# encoding: utf-8 -""" -@file: test_overlap.py -@desc: -@author: guozhen3 -@time: 2022/2/18 -""" - -from unittest.mock import patch - -from nose.tools import assert_equal, assert_in - -from pyecharts import options as opts -from pyecharts.charts import Bar, Grid, Line - - -def test_chart_for_grid(): - x_data = ["{}月".format(i) for i in range(1, 13)] - bar = ( - Bar() - .add_xaxis(x_data) - .add_yaxis( - "蒸发量", - [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], - yaxis_index=0, - color='red' - ) - .add_yaxis( - "降水量", - [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], - yaxis_index=1, - color='green' - ) - .extend_axis(yaxis=opts.AxisOpts(name="蒸发量", type_="value", position="right")) - .extend_axis(yaxis=opts.AxisOpts(type_="value", name="温度", position="left")) - .set_global_opts( - yaxis_opts=opts.AxisOpts(name="降水量", position="right", offset=80) - ) - ) - - line = ( - Line() - .add_xaxis(x_data) - .add_yaxis( - "平均温度", - [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], - yaxis_index=2, - color="blue", - label_opts=opts.LabelOpts(is_show=False), - ) - ) - - bar.overlap(line) - assert_equal(bar.colors[:3], ['red', 'green', 'blue']) - bar.render("overlap_test_after_colors_update.html") - return bar - - -if __name__ == "__main__": - test_chart_for_grid() diff --git a/test/test_stack2.py b/test/test_stack2.py deleted file mode 100644 index dd66e78c3..000000000 --- a/test/test_stack2.py +++ /dev/null @@ -1,38 +0,0 @@ -# encoding: utf-8 -""" -@file: test_stack2.py -@desc: -@author: guozhen3 -@time: 2022/2/18 -""" - -import pandas as pd -from pyecharts.charts import Bar - -data = pd.read_csv("test.csv") -data["base_vol"] = data["chg"].map(lambda x: -x if x > 0 else 0) + data["vol"] - -bar = ( -Bar() -.add_xaxis(xaxis_data=data["item"].tolist()) -.add_yaxis( -series_name="Position Vol", -y_axis=data["base_vol"].tolist(), -color="LightSeaGreen", -stack="vol" -) -.add_yaxis( -series_name="Increased", -y_axis=data["chg"].map(lambda x: x if x > 0 else 0).tolist(), -color="red", -stack="vol" -).add_yaxis( -series_name="Dcreased", -y_axis=data["chg"].map(lambda x: -x if x < 0 else 0).tolist(), -color="green", -stack="vol" -) - - -) -bar.render() \ No newline at end of file From a85711c3114127d866ffac16d27672802d009e81 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 15 Apr 2022 14:07:40 +0800 Subject: [PATCH 024/150] Update some charts configuration and Add a new chart for Lines3D --- pyecharts/charts/__init__.py | 1 + pyecharts/charts/chart.py | 115 +++++++++++++++--- pyecharts/charts/three_axis_charts/lines3D.py | 62 ++++++++++ .../charts/three_axis_charts/surface3D.py | 1 + pyecharts/commons/utils.py | 16 +-- pyecharts/options/charts_options.py | 24 ++++ pyecharts/options/global_options.py | 32 ++++- pyecharts/render/snapshot.py | 4 +- pyecharts/types.py | 4 +- 9 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 pyecharts/charts/three_axis_charts/lines3D.py diff --git a/pyecharts/charts/__init__.py b/pyecharts/charts/__init__.py index 9107e60a3..1460557f2 100644 --- a/pyecharts/charts/__init__.py +++ b/pyecharts/charts/__init__.py @@ -36,6 +36,7 @@ # 3d charts from ..charts.three_axis_charts.bar3D import Bar3D 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 diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index 209c93567..ce24fcb9b 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -259,9 +259,67 @@ def __init__(self, init_opts: types.Init = opts.InitOpts()): init_opts.renderer = RenderType.CANVAS super().__init__(init_opts) self.js_dependencies.add("echarts-gl") - self.options.update(visualMap=opts.VisualMapOpts().opts) 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( @@ -271,11 +329,17 @@ def add( shading: Optional[str] = None, itemstyle_opts: types.ItemStyle = None, label_opts: types.Label = opts.LabelOpts(is_show=False), - xaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="value"), - yaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="value"), - zaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="value"), + 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, + 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( @@ -285,15 +349,36 @@ def add( grid3D=grid3d_opts, ) - self.options.get("series").append( - { - "type": self._3d_chart_type, - "name": series_name, - "data": data, - "label": label_opts, - "shading": shading, - "itemStyle": itemstyle_opts, - "encode": encode, - } - ) + if self._3d_chart_type == "surface": + self.options.get("series").append( + { + "type": self._3d_chart_type, + "name": series_name, + "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, + "data": data, + "label": label_opts, + "shading": shading, + "grid3DIndex": grid_3d_index, + "itemStyle": itemstyle_opts, + "encode": encode, + } + ) return self diff --git a/pyecharts/charts/three_axis_charts/lines3D.py b/pyecharts/charts/three_axis_charts/lines3D.py new file mode 100644 index 000000000..473e33c56 --- /dev/null +++ b/pyecharts/charts/three_axis_charts/lines3D.py @@ -0,0 +1,62 @@ +from ... import options as opts +from ... import types +from ...charts.chart import Chart3D +from ...globals import ChartType +from ...options import InitOpts + + +class Lines3D(Chart3D): + """ + Lines 3D + """ + + def __init__(self, init_opts: types.Init = InitOpts()): + super().__init__(init_opts) + self._3d_chart_type = "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 diff --git a/pyecharts/charts/three_axis_charts/surface3D.py b/pyecharts/charts/three_axis_charts/surface3D.py index 5659d7ce0..8c43287b5 100644 --- a/pyecharts/charts/three_axis_charts/surface3D.py +++ b/pyecharts/charts/three_axis_charts/surface3D.py @@ -1,3 +1,4 @@ +from ... import options as opts from ... import types from ...charts.chart import ThreeAxisChart from ...options import InitOpts diff --git a/pyecharts/commons/utils.py b/pyecharts/commons/utils.py index 2da93772c..88562567f 100644 --- a/pyecharts/commons/utils.py +++ b/pyecharts/commons/utils.py @@ -54,14 +54,14 @@ 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 _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): diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 638f91b0f..48f218ba1 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -1066,6 +1066,30 @@ def __init__( } +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, diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index c64dab083..02fdc8ab1 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -867,23 +867,44 @@ def __init__( 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, @@ -895,6 +916,11 @@ def __init__( "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, } @@ -910,7 +936,8 @@ def __init__( name_gap: Numeric = 20, min_: Union[str, Numeric, None] = None, max_: Union[str, Numeric, None] = None, - splitnum: Optional[Numeric] = 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, @@ -927,7 +954,8 @@ def __init__( "grid3DIndex": grid_3d_index, "nameGap": name_gap, "nameTextStyle": textstyle_opts, - "splitNum": splitnum, + "splitNumber": split_number, + "logBase": log_base, "type": type_, "min": min_, "max": max_, diff --git a/pyecharts/render/snapshot.py b/pyecharts/render/snapshot.py index dbe49427a..62e40f3a4 100644 --- a/pyecharts/render/snapshot.py +++ b/pyecharts/render/snapshot.py @@ -91,7 +91,9 @@ def save_as(image_data: bytes, output_name: str, file_type: str): m.load() color = (255, 255, 255) b = Image.new("RGB", m.size, color) - b.paste(m, mask=m.split()[3]) + # 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") diff --git a/pyecharts/types.py b/pyecharts/types.py index 839b2c79b..ad65b0f24 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -11,7 +11,7 @@ ) from . import options as opts -from .options.charts_options import BaseGraphic +from .options.charts_options import BaseGraphic, GlobeLayersOpts from .options.series_options import JsCode, JSFunc, Numeric Init = Union[opts.InitOpts, dict] @@ -31,6 +31,8 @@ GaugePointer = Union[opts.GaugePointerOpts, dict, None] _GraphicType = Union[BaseGraphic, dict] Graphic = Union[_GraphicType, Sequence[_GraphicType], 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] From 1d5330b1559fe8033556d6b27970e4b14fa3b253 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 15 Apr 2022 14:08:25 +0800 Subject: [PATCH 025/150] Update many unit test codes.(Coverage Up to 99%) --- test/fixtures/city_coordinates.json | 15006 +++++++++++++++++++++ test/fixtures/img.png | Bin 0 -> 443277 bytes test/fixtures/img1.jpg | Bin 0 -> 140536 bytes test/fixtures/les-miserables.json | 2383 ++++ test/fixtures/life-expectancy-table.json | 10782 +++++++++++++++ test/fixtures/resize_cfg.json | 1 + test/test_bar.py | 60 + test/test_base.py | 11 +- test/test_boxplot.py | 33 +- test/test_calendar.py | 4 +- test/test_chart.py | 241 +- test/test_chart_diff.py | 27 - test/test_chart_options.py | 101 + test/test_datasets.py | 24 +- test/test_display.py | 6 + test/test_exception.py | 1 + test/test_faker.py | 14 + test/test_geo.py | 102 + test/test_global_options.py | 159 +- test/test_graph.py | 66 + test/test_graphic.py | 68 + test/test_grid.py | 219 +- test/test_image.py | 21 +- test/test_lines3d.py | 50 + test/test_liquid.py | 4 +- test/test_map3d.py | 220 + test/test_map_globe.py | 15 + test/test_mixins.py | 27 + test/test_page.py | 23 +- test/test_parallel.py | 48 + test/test_pie.py | 55 +- test/test_polar.py | 40 + test/test_sankey.py | 3 + test/test_scatter.py | 181 +- test/test_series_options.py | 92 +- test/test_snapshot.py | 32 + test/test_tab.py | 8 +- test/test_table.py | 24 + test/test_timeline.py | 41 + test/test_tree.py | 9 + test/test_utils.py | 15 + test/test_wordcloud.py | 37 + 42 files changed, 30197 insertions(+), 56 deletions(-) create mode 100644 test/fixtures/city_coordinates.json create mode 100644 test/fixtures/img.png create mode 100644 test/fixtures/img1.jpg create mode 100644 test/fixtures/les-miserables.json create mode 100644 test/fixtures/life-expectancy-table.json create mode 100644 test/fixtures/resize_cfg.json delete mode 100644 test/test_chart_diff.py create mode 100644 test/test_chart_options.py create mode 100644 test/test_faker.py create mode 100644 test/test_graphic.py create mode 100644 test/test_lines3d.py create mode 100644 test/test_mixins.py diff --git a/test/fixtures/city_coordinates.json b/test/fixtures/city_coordinates.json new file mode 100644 index 000000000..7bb4f627b --- /dev/null +++ b/test/fixtures/city_coordinates.json @@ -0,0 +1,15006 @@ +{ + "阿城": [ + 126.58, + 45.32 + ], + "阿克苏": [ + 80.19, + 41.09 + ], + "阿勒泰": [ + 88.12, + 47.5 + ], + "阿图什": [ + 76.08, + 39.42 + ], + "安达": [ + 125.18, + 46.24 + ], + "安国": [ + 115.2, + 38.24 + ], + "安康": [ + 109.01, + 32.41 + ], + "安陆": [ + 113.41, + 31.15 + ], + "安庆": [ + 117.02, + 30.31 + ], + "安丘": [ + 119.12, + 36.25 + ], + "安顺": [ + 105.55, + 26.14 + ], + "安阳": [ + 114.35, + 36.1 + ], + "鞍山": [ + 122.85, + 41.12 + ], + "巴中": [ + 106.43, + 31.51 + ], + "霸州": [ + 116.24, + 39.06 + ], + "白城": [ + 122.5, + 45.38 + ], + "白山": [ + 126.26, + 41.56 + ], + "白银": [ + 104.12, + 36.33 + ], + "百色": [ + 106.36, + 23.54 + ], + "蚌埠": [ + 117.21, + 32.56 + ], + "包头": [ + 110, + 40.58 + ], + "宝鸡": [ + 107.15, + 34.38 + ], + "保定": [ + 115.48, + 38.85 + ], + "保山": [ + 99.1, + 25.08 + ], + "北海": [ + 109.12, + 21.49 + ], + "北流": [ + 110.21, + 22.42 + ], + "北票": [ + 120.47, + 41.48 + ], + "本溪": [ + 123.73, + 41.3 + ], + "毕节": [ + 105.18, + 27.18 + ], + "滨州": [ + 118.03, + 37.36 + ], + "亳州": [ + 115.47, + 33.52 + ], + "博乐": [ + 82.08, + 44.57 + ], + "沧州": [ + 116.83, + 38.33 + ], + "昌吉": [ + 87.18, + 44.02 + ], + "昌邑": [ + 119.24, + 39.52 + ], + "常德": [ + 111.69, + 29.05 + ], + "常熟": [ + 120.74, + 31.64 + ], + "常州": [ + 119.95, + 31.79 + ], + "巢湖": [ + 117.52, + 31.36 + ], + "朝阳": [ + 120.27, + 41.34 + ], + "潮阳": [ + 116.36, + 23.16 + ], + "潮州": [ + 116.63, + 23.68 + ], + "郴州": [ + 113.02, + 25.46 + ], + "成都": [ + 104.06, + 30.67 + ], + "承德": [ + 117.93, + 40.97 + ], + "澄海": [ + 116.46, + 23.28 + ], + "赤峰": [ + 118.87, + 42.28 + ], + "赤水": [ + 105.42, + 28.34 + ], + "崇州": [ + 103.4, + 30.39 + ], + "滁州": [ + 118.18, + 32.18 + ], + "楚雄": [ + 101.32, + 25.01 + ], + "慈溪": [ + 121.15, + 30.11 + ], + "从化": [ + 113.33, + 23.33 + ], + "达川": [ + 107.29, + 31.14 + ], + "大安": [ + 124.18, + 45.3 + ], + "大理": [ + 100.13, + 25.34 + ], + "大连": [ + 121.62, + 38.92 + ], + "大庆": [ + 125.03, + 46.58 + ], + "大石桥": [ + 122.31, + 40.37 + ], + "大同": [ + 113.3, + 40.12 + ], + "大冶": [ + 114.58, + 30.06 + ], + "丹东": [ + 124.37, + 40.13 + ], + "丹江口": [ + 108.3, + 32.33 + ], + "丹阳": [ + 119.32, + 32 + ], + "儋州": [ + 109.34, + 19.31 + ], + "当阳": [ + 111.47, + 30.5 + ], + "德惠": [ + 125.42, + 44.32 + ], + "德令哈": [ + 97.23, + 37.22 + ], + "德兴": [ + 117.35, + 28.57 + ], + "德阳": [ + 104.37, + 31.13 + ], + "德州": [ + 116.29, + 37.45 + ], + "登封": [ + 113.02, + 34.27 + ], + "邓州": [ + 112.05, + 32.42 + ], + "定州": [ + 115, + 38.3 + ], + "东川": [ + 103.12, + 26.06 + ], + "东港": [ + 124.08, + 39.53 + ], + "东莞": [ + 113.75, + 23.04 + ], + "东胜": [ + 109.59, + 39.48 + ], + "东台": [ + 120.19, + 32.51 + ], + "东阳": [ + 120.14, + 29.16 + ], + "东营": [ + 118.49, + 37.46 + ], + "都江堰": [ + 103.37, + 31.01 + ], + "都匀": [ + 107.31, + 26.15 + ], + "敦化": [ + 128.13, + 43.22 + ], + "敦煌": [ + 94.41, + 40.08 + ], + "峨眉山": [ + 103.29, + 29.36 + ], + "额尔古纳": [ + 120.11, + 50.13 + ], + "鄂尔多斯": [ + 109.781327, + 39.608266 + ], + "鄂州": [ + 114.52, + 30.23 + ], + "恩平": [ + 112.19, + 22.12 + ], + "恩施": [ + 109.29, + 30.16 + ], + "二连浩特": [ + 111.58, + 43.38 + ], + "番禺": [ + 113.22, + 22.57 + ], + "防城港": [ + 108.2, + 21.37 + ], + "肥城": [ + 116.46, + 36.14 + ], + "丰城": [ + 115.48, + 28.12 + ], + "丰南": [ + 118.06, + 39.34 + ], + "丰镇": [ + 113.09, + 40.27 + ], + "凤城": [ + 124.02, + 40.28 + ], + "奉化": [ + 121.24, + 29.39 + ], + "佛山": [ + 113.11, + 23.05 + ], + "涪陵": [ + 107.22, + 29.42 + ], + "福安": [ + 119.39, + 27.06 + ], + "福清": [ + 119.23, + 25.42 + ], + "福州": [ + 119.3, + 26.08 + ], + "抚顺": [ + 123.97, + 41.97 + ], + "阜康": [ + 87.58, + 44.09 + ], + "阜新": [ + 121.39, + 42.01 + ], + "阜阳": [ + 115.48, + 32.54 + ], + "富锦": [ + 132.02, + 47.15 + ], + "富阳": [ + 119.95, + 30.07 + ], + "盖州": [ + 122.21, + 40.24 + ], + "赣州": [ + 114.56, + 28.52 + ], + "高安": [ + 115.22, + 28.25 + ], + "高碑店": [ + 115.51, + 39.2 + ], + "高密": [ + 119.44, + 36.22 + ], + "高明": [ + 112.5, + 22.53 + ], + "高平": [ + 112.55, + 35.48 + ], + "高要": [ + 112.26, + 23.02 + ], + "高邮": [ + 119.27, + 32.47 + ], + "高州": [ + 110.5, + 21.54 + ], + "格尔木": [ + 94.55, + 36.26 + ], + "个旧": [ + 103.09, + 23.21 + ], + "根河": [ + 121.29, + 50.48 + ], + "公主岭": [ + 124.49, + 43.31 + ], + "巩义": [ + 112.58, + 34.46 + ], + "古交": [ + 112.09, + 37.54 + ], + "广汉": [ + 104.15, + 30.58 + ], + "广水": [ + 113.48, + 31.37 + ], + "广元": [ + 105.51, + 32.28 + ], + "广州": [ + 113.23, + 23.16 + ], + "贵池": [ + 117.28, + 30.39 + ], + "贵港": [ + 109.36, + 23.06 + ], + "贵阳": [ + 106.71, + 26.57 + ], + "桂林": [ + 110.28, + 25.29 + ], + "桂平": [ + 110.04, + 23.22 + ], + "哈尔滨": [ + 126.63, + 45.75 + ], + "哈密": [ + 93.28, + 42.5 + ], + "海城": [ + 122.43, + 40.51 + ], + "海口": [ + 110.35, + 20.02 + ], + "海拉尔": [ + 119.39, + 49.12 + ], + "海林": [ + 129.21, + 44.35 + ], + "海伦": [ + 126.57, + 47.28 + ], + "海门": [ + 121.15, + 31.89 + ], + "海宁": [ + 120.42, + 30.32 + ], + "邯郸": [ + 114.47, + 36.6 + ], + "韩城": [ + 110.27, + 35.28 + ], + "汉中": [ + 107.01, + 33.04 + ], + "杭州": [ + 120.19, + 30.26 + ], + "蒿城": [ + 114.5, + 38.02 + ], + "合川": [ + 106.15, + 30.02 + ], + "合肥": [ + 117.27, + 31.86 + ], + "合山": [ + 108.52, + 23.47 + ], + "和龙": [ + 129, + 42.32 + ], + "和田": [ + 79.55, + 37.09 + ], + "河池": [ + 108.03, + 24.42 + ], + "河间": [ + 116.05, + 38.26 + ], + "河津": [ + 110.41, + 35.35 + ], + "河源": [ + 114.68, + 23.73 + ], + "菏泽": [ + 115.480656, + 35.23375 + ], + "鹤壁": [ + 114.11, + 35.54 + ], + "鹤岗": [ + 130.16, + 47.2 + ], + "鹤山": [ + 112.57, + 22.46 + ], + "黑河": [ + 127.29, + 50.14 + ], + "衡水": [ + 115.72, + 37.72 + ], + "衡阳": [ + 112.37, + 26.53 + ], + "洪湖": [ + 113.27, + 29.48 + ], + "洪江": [ + 109.59, + 27.07 + ], + "侯马": [ + 111.21, + 35.37 + ], + "呼和浩特": [ + 111.65, + 40.82 + ], + "湖州": [ + 120.1, + 30.86 + ], + "葫芦岛": [ + 120.836932, + 40.711052 + ], + "花都": [ + 113.12, + 23.23 + ], + "华阴": [ + 110.05, + 34.34 + ], + "华蓥": [ + 106.44, + 30.26 + ], + "化州": [ + 110.37, + 21.39 + ], + "桦甸": [ + 126.44, + 42.58 + ], + "怀化": [ + 109.58, + 27.33 + ], + "淮安": [ + 119.15, + 33.5 + ], + "淮北": [ + 116.47, + 33.57 + ], + "淮南": [ + 116.58, + 32.37 + ], + "淮阴": [ + 119.02, + 33.36 + ], + "黄骅": [ + 117.21, + 38.21 + ], + "黄山": [ + 118.18, + 29.43 + ], + "黄石": [ + 115.06, + 30.12 + ], + "黄州": [ + 114.52, + 30.27 + ], + "珲春": [ + 130.22, + 42.52 + ], + "辉县": [ + 113.47, + 35.27 + ], + "惠阳": [ + 114.28, + 22.48 + ], + "惠州": [ + 114.4, + 23.09 + ], + "霍林郭勒": [ + 119.38, + 45.32 + ], + "霍州": [ + 111.42, + 36.34 + ], + "鸡西": [ + 130.57, + 45.17 + ], + "吉安": [ + 114.58, + 27.07 + ], + "吉首": [ + 109.43, + 28.18 + ], + "即墨": [ + 120.45, + 36.38 + ], + "集安": [ + 126.11, + 41.08 + ], + "集宁": [ + 113.06, + 41.02 + ], + "济南": [ + 117, + 36.65 + ], + "济宁": [ + 116.59, + 35.38 + ], + "济源": [ + 112.35, + 35.04 + ], + "冀州": [ + 115.33, + 37.34 + ], + "佳木斯": [ + 130.22, + 46.47 + ], + "嘉兴": [ + 120.76, + 30.77 + ], + "嘉峪关": [ + 98.289152, + 39.77313 + ], + "简阳": [ + 104.32, + 30.24 + ], + "建德": [ + 119.16, + 29.29 + ], + "建瓯": [ + 118.2, + 27.03 + ], + "建阳": [ + 118.07, + 27.21 + ], + "江都": [ + 119.32, + 32.26 + ], + "江津": [ + 106.16, + 29.18 + ], + "江门": [ + 113.06, + 22.61 + ], + "江山": [ + 118.37, + 28.45 + ], + "江阴": [ + 120.26, + 31.91 + ], + "江油": [ + 104.42, + 31.48 + ], + "姜堰": [ + 120.08, + 32.34 + ], + "胶南": [ + 119.97, + 35.88 + ], + "胶州": [ + 120.03336, + 36.264622 + ], + "焦作": [ + 113.21, + 35.24 + ], + "蛟河": [ + 127.21, + 43.42 + ], + "揭阳": [ + 116.35, + 23.55 + ], + "介休": [ + 111.55, + 37.02 + ], + "界首": [ + 115.21, + 33.15 + ], + "金昌": [ + 102.188043, + 38.520089 + ], + "金华": [ + 119.64, + 29.12 + ], + "金坛": [ + 119.56, + 31.74 + ], + "津市": [ + 111.52, + 29.38 + ], + "锦州": [ + 121.15, + 41.13 + ], + "晋城": [ + 112.51, + 35.3 + ], + "晋江": [ + 118.35, + 24.49 + ], + "晋州": [ + 115.02, + 38.02 + ], + "荆门": [ + 112.12, + 31.02 + ], + "荆沙": [ + 112.16, + 30.18 + ], + "荆州": [ + 112.239741, + 30.335165 + ], + "井冈山": [ + 114.1, + 26.34 + ], + "景德镇": [ + 117.13, + 29.17 + ], + "景洪": [ + 100.48, + 22.01 + ], + "靖江": [ + 120.17, + 32.02 + ], + "九江": [ + 115.97, + 29.71 + ], + "九台": [ + 125.51, + 44.09 + ], + "酒泉": [ + 98.31, + 39.44 + ], + "句容": [ + 119.16, + 31.95 + ], + "喀什": [ + 75.59, + 39.3 + ], + "开封": [ + 114.35, + 34.79 + ], + "开平": [ + 112.4, + 22.22 + ], + "开原": [ + 124.02, + 42.32 + ], + "开远": [ + 103.13, + 23.43 + ], + "凯里": [ + 107.58, + 26.35 + ], + "克拉玛依": [ + 84.77, + 45.59 + ], + "库尔勒": [ + 86.06, + 41.68 + ], + "奎屯": [ + 84.56, + 44.27 + ], + "昆明": [ + 102.73, + 25.04 + ], + "昆山": [ + 120.95, + 31.39 + ], + "廓坊": [ + 116.42, + 39.31 + ], + "拉萨": [ + 91.11, + 29.97 + ], + "莱芜": [ + 117.67, + 36.19 + ], + "莱西": [ + 120.53, + 36.86 + ], + "莱阳": [ + 120.42, + 36.58 + ], + "莱州": [ + 119.942327, + 37.177017 + ], + "兰溪": [ + 119.28, + 29.12 + ], + "兰州": [ + 103.73, + 36.03 + ], + "阆中": [ + 105.58, + 31.36 + ], + "廊坊": [ + 116.7, + 39.53 + ], + "老河口": [ + 111.4, + 32.23 + ], + "乐昌": [ + 113.21, + 25.09 + ], + "乐陵": [ + 117.12, + 37.44 + ], + "乐平": [ + 117.08, + 28.58 + ], + "乐清": [ + 120.58, + 28.08 + ], + "乐山": [ + 103.44, + 29.36 + ], + "雷州": [ + 110.04, + 20.54 + ], + "耒阳": [ + 112.51, + 26.24 + ], + "冷水江": [ + 111.26, + 27.42 + ], + "冷水滩": [ + 111.35, + 26.26 + ], + "醴陵": [ + 113.3, + 27.4 + ], + "丽水": [ + 119.92, + 28.45 + ], + "利川": [ + 108.56, + 30.18 + ], + "溧阳": [ + 119.48, + 31.43 + ], + "连云港": [ + 119.16, + 34.59 + ], + "连州": [ + 112.23, + 24.48 + ], + "涟源": [ + 111.41, + 27.41 + ], + "廉江": [ + 110.17, + 21.37 + ], + "辽阳": [ + 123.12, + 41.16 + ], + "辽源": [ + 125.09, + 42.54 + ], + "聊城": [ + 115.97, + 36.45 + ], + "林州": [ + 113.49, + 36.03 + ], + "临安": [ + 119.72, + 30.23 + ], + "临川": [ + 116.21, + 27.59 + ], + "临汾": [ + 111.5, + 36.08 + ], + "临海": [ + 121.08, + 28.51 + ], + "临河": [ + 107.22, + 40.46 + ], + "临江": [ + 126.53, + 41.49 + ], + "临清": [ + 115.42, + 36.51 + ], + "临夏": [ + 103.12, + 35.37 + ], + "临湘": [ + 113.27, + 29.29 + ], + "临沂": [ + 118.35, + 35.05 + ], + "赁祥": [ + 106.44, + 22.07 + ], + "灵宝": [ + 110.52, + 34.31 + ], + "凌海": [ + 121.21, + 41.1 + ], + "凌源": [ + 119.22, + 41.14 + ], + "浏阳": [ + 113.37, + 28.09 + ], + "柳州": [ + 109.4, + 24.33 + ], + "六安": [ + 116.28, + 31.44 + ], + "六盘水": [ + 104.5, + 26.35 + ], + "龙海": [ + 117.48, + 24.26 + ], + "龙井": [ + 129.26, + 42.46 + ], + "龙口": [ + 120.21, + 37.39 + ], + "龙泉": [ + 119.08, + 28.04 + ], + "龙岩": [ + 117.01, + 25.06 + ], + "娄底": [ + 111.59, + 27.44 + ], + "泸州": [ + 105.39, + 28.91 + ], + "鹿泉": [ + 114.19, + 38.04 + ], + "潞城": [ + 113.14, + 36.21 + ], + "罗定": [ + 111.33, + 22.46 + ], + "洛阳": [ + 112.44, + 34.7 + ], + "漯河": [ + 114.02, + 33.33 + ], + "麻城": [ + 115.01, + 31.1 + ], + "马鞍山": [ + 118.48, + 31.56 + ], + "满洲里": [ + 117.23, + 49.35 + ], + "茂名": [ + 110.88, + 21.68 + ], + "梅河口": [ + 125.4, + 42.32 + ], + "梅州": [ + 116.1, + 24.55 + ], + "汨罗": [ + 113.03, + 28.49 + ], + "密山": [ + 131.5, + 45.32 + ], + "绵阳": [ + 104.73, + 31.48 + ], + "明光": [ + 117.58, + 32.47 + ], + "牡丹江": [ + 129.58, + 44.6 + ], + "南安": [ + 118.23, + 24.57 + ], + "南昌": [ + 115.89, + 28.68 + ], + "南充": [ + 106.110698, + 30.837793 + ], + "南川": [ + 107.05, + 29.1 + ], + "南宫": [ + 115.23, + 37.22 + ], + "南海": [ + 113.09, + 23.01 + ], + "南京": [ + 118.78, + 32.04 + ], + "南宁": [ + 108.33, + 22.84 + ], + "南平": [ + 118.1, + 26.38 + ], + "南通": [ + 121.05, + 32.08 + ], + "南阳": [ + 112.32, + 33 + ], + "讷河": [ + 124.51, + 48.29 + ], + "内江": [ + 105.02, + 29.36 + ], + "宁安": [ + 129.28, + 44.21 + ], + "宁波": [ + 121.56, + 29.86 + ], + "宁德": [ + 119.31, + 26.39 + ], + "攀枝花": [ + 101.718637, + 26.582347 + ], + "盘锦": [ + 122.070714, + 41.119997 + ], + "彭州": [ + 103.57, + 30.59 + ], + "蓬莱": [ + 120.75, + 37.8 + ], + "邳州": [ + 117.59, + 34.19 + ], + "平顶山": [ + 113.29, + 33.75 + ], + "平度": [ + 119.97, + 36.77 + ], + "平湖": [ + 121.01, + 30.42 + ], + "平凉": [ + 106.4, + 35.32 + ], + "萍乡": [ + 113.5, + 27.37 + ], + "泊头": [ + 116.34, + 38.04 + ], + "莆田": [ + 119.01, + 24.26 + ], + "濮阳": [ + 115.01, + 35.44 + ], + "浦圻": [ + 113.51, + 29.42 + ], + "普兰店": [ + 121.58, + 39.23 + ], + "普宁": [ + 116.1, + 23.18 + ], + "七台河": [ + 130.49, + 45.48 + ], + "齐齐哈尔": [ + 123.97, + 47.33 + ], + "启乐": [ + 121.39, + 31.48 + ], + "潜江": [ + 112.53, + 30.26 + ], + "钦州": [ + 108.37, + 21.57 + ], + "秦皇岛": [ + 119.57, + 39.95 + ], + "沁阳": [ + 112.57, + 35.05 + ], + "青岛": [ + 120.33, + 36.07 + ], + "青铜峡": [ + 105.59, + 37.56 + ], + "青州": [ + 118.28, + 36.42 + ], + "清远": [ + 113.01, + 23.7 + ], + "清镇": [ + 106.27, + 26.33 + ], + "邛崃": [ + 103.28, + 30.26 + ], + "琼海": [ + 110.28, + 19.14 + ], + "琼山": [ + 110.21, + 19.59 + ], + "曲阜": [ + 116.58, + 35.36 + ], + "曲靖": [ + 103.79, + 25.51 + ], + "衢州": [ + 118.88, + 28.97 + ], + "泉州": [ + 118.58, + 24.93 + ], + "任丘": [ + 116.07, + 38.42 + ], + "日喀则": [ + 88.51, + 29.16 + ], + "日照": [ + 119.46, + 35.42 + ], + "荣成": [ + 122.41, + 37.16 + ], + "如皋": [ + 120.33, + 32.23 + ], + "汝州": [ + 112.5, + 34.09 + ], + "乳山": [ + 121.52, + 36.89 + ], + "瑞安": [ + 120.38, + 27.48 + ], + "瑞昌": [ + 115.38, + 29.4 + ], + "瑞金": [ + 116.01, + 25.53 + ], + "瑞丽": [ + 97.5, + 24 + ], + "三河": [ + 117.04, + 39.58 + ], + "三门峡": [ + 111.19, + 34.76 + ], + "三明": [ + 117.36, + 26.13 + ], + "三水": [ + 112.52, + 23.1 + ], + "三亚": [ + 109.511909, + 18.252847 + ], + "沙河": [ + 114.3, + 36.51 + ], + "厦门": [ + 118.1, + 24.46 + ], + "汕头": [ + 116.69, + 23.39 + ], + "汕尾": [ + 115.375279, + 22.786211 + ], + "商丘": [ + 115.38, + 34.26 + ], + "商州": [ + 109.57, + 33.52 + ], + "上饶": [ + 117.58, + 25.27 + ], + "上虞": [ + 120.52, + 30.01 + ], + "尚志": [ + 127.55, + 45.14 + ], + "韶关": [ + 113.62, + 24.84 + ], + "韶山": [ + 112.29, + 27.54 + ], + "邵武": [ + 117.29, + 27.2 + ], + "邵阳": [ + 111.28, + 27.14 + ], + "绍兴": [ + 120.58, + 30.01 + ], + "深圳": [ + 114.07, + 22.62 + ], + "深州": [ + 115.32, + 38.01 + ], + "沈阳": [ + 123.38, + 41.8 + ], + "十堰": [ + 110.47, + 32.4 + ], + "石河子": [ + 86, + 44.18 + ], + "石家庄": [ + 114.48, + 38.03 + ], + "石狮": [ + 118.38, + 24.44 + ], + "石首": [ + 112.24, + 29.43 + ], + "石嘴山": [ + 106.39, + 39.04 + ], + "寿光": [ + 118.73, + 36.86 + ], + "舒兰": [ + 126.57, + 44.24 + ], + "双城": [ + 126.15, + 45.22 + ], + "双鸭山": [ + 131.11, + 46.38 + ], + "顺德": [ + 113.15, + 22.5 + ], + "朔州": [ + 112.26, + 39.19 + ], + "思茅": [ + 100.58, + 22.48 + ], + "四会": [ + 112.41, + 23.21 + ], + "四平": [ + 124.22, + 43.1 + ], + "松原": [ + 124.49, + 45.11 + ], + "苏州": [ + 120.62, + 31.32 + ], + "宿迁": [ + 118.3, + 33.96 + ], + "宿州": [ + 116.58, + 33.38 + ], + "绥芬河": [ + 131.11, + 44.25 + ], + "绥化": [ + 126.59, + 46.38 + ], + "随州": [ + 113.22, + 31.42 + ], + "遂宁": [ + 105.33, + 30.31 + ], + "塔城": [ + 82.59, + 46.46 + ], + "台北": [ + 121.3, + 25.03 + ], + "台山": [ + 112.48, + 22.15 + ], + "台州": [ + 121.420757, + 28.656386 + ], + "太仓": [ + 121.1, + 31.45 + ], + "太原": [ + 112.53, + 37.87 + ], + "泰安": [ + 117.13, + 36.18 + ], + "泰兴": [ + 120.01, + 32.1 + ], + "泰州": [ + 119.9, + 32.49 + ], + "唐山": [ + 118.02, + 39.63 + ], + "洮南": [ + 122.47, + 45.2 + ], + "滕州": [ + 117.09, + 35.06 + ], + "天门": [ + 113.1, + 30.39 + ], + "天水": [ + 105.42, + 34.37 + ], + "天长": [ + 118.59, + 32.41 + ], + "铁法": [ + 123.32, + 42.28 + ], + "铁力": [ + 128.01, + 46.59 + ], + "铁岭": [ + 123.51, + 42.18 + ], + "通化": [ + 125.56, + 41.43 + ], + "通辽": [ + 122.16, + 43.37 + ], + "通什": [ + 109.31, + 18.46 + ], + "通州": [ + 121.03, + 32.05 + ], + "同江": [ + 132.3, + 47.39 + ], + "桐乡": [ + 120.32, + 30.38 + ], + "铜川": [ + 109.11, + 35.09 + ], + "铜陵": [ + 117.48, + 30.56 + ], + "铜仁": [ + 109.12, + 27.43 + ], + "图们": [ + 129.51, + 42.57 + ], + "吐鲁番": [ + 89.11, + 42.54 + ], + "瓦房店": [ + 121.979603, + 39.627114 + ], + "畹町": [ + 98.04, + 24.06 + ], + "万县": [ + 108.21, + 30.5 + ], + "万源": [ + 108.03, + 32.03 + ], + "威海": [ + 122.1, + 37.5 + ], + "潍坊": [ + 119.1, + 36.62 + ], + "卫辉": [ + 114.03, + 35.24 + ], + "渭南": [ + 109.5, + 34.52 + ], + "温岭": [ + 121.21, + 28.22 + ], + "温州": [ + 120.65, + 28.01 + ], + "文登": [ + 122.05, + 37.2 + ], + "乌海": [ + 106.48, + 39.4 + ], + "乌兰浩特": [ + 122.03, + 46.03 + ], + "乌鲁木齐": [ + 87.68, + 43.77 + ], + "无锡": [ + 120.29, + 31.59 + ], + "吴川": [ + 110.47, + 21.26 + ], + "吴江": [ + 120.63, + 31.16 + ], + "吴忠": [ + 106.11, + 37.59 + ], + "芜湖": [ + 118.38, + 31.33 + ], + "梧州": [ + 111.2, + 23.29 + ], + "五常": [ + 127.11, + 44.55 + ], + "五大连池": [ + 126.07, + 48.38 + ], + "武安": [ + 114.11, + 36.42 + ], + "武冈": [ + 110.37, + 26.43 + ], + "武汉": [ + 114.31, + 30.52 + ], + "武威": [ + 102.39, + 37.56 + ], + "武穴": [ + 115.33, + 29.51 + ], + "武夷山": [ + 118.02, + 27.46 + ], + "舞钢": [ + 113.3, + 33.17 + ], + "西安": [ + 108.95, + 34.27 + ], + "西昌": [ + 102.16, + 27.54 + ], + "西峰": [ + 107.4, + 35.45 + ], + "西宁": [ + 101.74, + 36.56 + ], + "锡林浩特": [ + 116.03, + 43.57 + ], + "仙桃": [ + 113.27, + 30.22 + ], + "咸宁": [ + 114.17, + 29.53 + ], + "咸阳": [ + 108.72, + 34.36 + ], + "湘潭": [ + 112.91, + 27.87 + ], + "湘乡": [ + 112.31, + 27.44 + ], + "襄樊": [ + 112.08, + 32.02 + ], + "项城": [ + 114.54, + 33.26 + ], + "萧山": [ + 120.16, + 30.09 + ], + "孝感": [ + 113.54, + 30.56 + ], + "孝义": [ + 111.48, + 37.08 + ], + "忻州": [ + 112.43, + 38.24 + ], + "辛集": [ + 115.12, + 37.54 + ], + "新会": [ + 113.01, + 22.32 + ], + "新乐": [ + 114.41, + 38.2 + ], + "新密": [ + 113.22, + 34.31 + ], + "新民": [ + 122.49, + 41.59 + ], + "新泰": [ + 117.45, + 35.54 + ], + "新乡": [ + 113.52, + 35.18 + ], + "新沂": [ + 118.2, + 34.22 + ], + "新余": [ + 114.56, + 27.48 + ], + "新郑": [ + 113.43, + 34.24 + ], + "信阳": [ + 114.04, + 32.07 + ], + "邢台": [ + 114.48, + 37.05 + ], + "荥阳": [ + 113.21, + 34.46 + ], + "兴城": [ + 120.41, + 40.37 + ], + "兴化": [ + 119.5, + 32.56 + ], + "兴宁": [ + 115.43, + 24.09 + ], + "兴平": [ + 108.29, + 34.18 + ], + "兴义": [ + 104.53, + 25.05 + ], + "徐州": [ + 117.2, + 34.26 + ], + "许昌": [ + 113.49, + 34.01 + ], + "宣威": [ + 104.06, + 26.13 + ], + "宣州": [ + 118.44, + 30.57 + ], + "牙克石": [ + 120.4, + 49.17 + ], + "雅安": [ + 102.59, + 29.59 + ], + "烟台": [ + 121.39, + 37.52 + ], + "延安": [ + 109.47, + 36.6 + ], + "延吉": [ + 129.3, + 42.54 + ], + "盐城": [ + 120.13, + 33.38 + ], + "盐在": [ + 120.08, + 33.22 + ], + "兖州": [ + 116.49, + 35.32 + ], + "偃师": [ + 112.47, + 34.43 + ], + "扬中": [ + 119.49, + 32.14 + ], + "扬州": [ + 119.42, + 32.39 + ], + "阳春": [ + 111.48, + 22.1 + ], + "阳江": [ + 111.95, + 21.85 + ], + "阳泉": [ + 113.57, + 37.85 + ], + "伊春": [ + 128.56, + 47.42 + ], + "伊宁": [ + 81.2, + 43.55 + ], + "仪征": [ + 119.1, + 32.16 + ], + "宜宾": [ + 104.56, + 29.77 + ], + "宜昌": [ + 111.3, + 30.7 + ], + "宜城": [ + 112.15, + 31.42 + ], + "宜春": [ + 114.23, + 27.47 + ], + "宜兴": [ + 119.82, + 31.36 + ], + "宜州": [ + 108.4, + 24.28 + ], + "义马": [ + 111.55, + 34.43 + ], + "义乌": [ + 120.06, + 29.32 + ], + "益阳": [ + 112.2, + 28.36 + ], + "银川": [ + 106.27, + 38.47 + ], + "应城": [ + 113.33, + 30.57 + ], + "英德": [ + 113.22, + 24.1 + ], + "鹰潭": [ + 117.03, + 28.14 + ], + "营口": [ + 122.18, + 40.65 + ], + "永安": [ + 117.23, + 25.58 + ], + "永川": [ + 105.53, + 29.23 + ], + "永济": [ + 110.27, + 34.52 + ], + "永康": [ + 120.01, + 29.54 + ], + "永州": [ + 111.37, + 26.13 + ], + "余杭": [ + 120.18, + 30.26 + ], + "余姚": [ + 121.1, + 30.02 + ], + "愉树": [ + 126.32, + 44.49 + ], + "榆次": [ + 112.43, + 37.41 + ], + "榆林": [ + 109.47, + 38.18 + ], + "禹城": [ + 116.39, + 36.56 + ], + "禹州": [ + 113.28, + 34.09 + ], + "玉林": [ + 110.09, + 22.38 + ], + "玉门": [ + 97.35, + 39.49 + ], + "玉溪": [ + 102.52, + 24.35 + ], + "沅江": [ + 112.22, + 28.5 + ], + "原平": [ + 112.42, + 38.43 + ], + "岳阳": [ + 113.09, + 29.37 + ], + "云浮": [ + 112.02, + 22.93 + ], + "运城": [ + 110.59, + 35.02 + ], + "枣阳": [ + 112.44, + 32.07 + ], + "枣庄": [ + 117.57, + 34.86 + ], + "增城": [ + 113.49, + 23.18 + ], + "扎兰屯": [ + 122.47, + 48 + ], + "湛江": [ + 110.359377, + 21.270708 + ], + "张家港": [ + 120.555821, + 31.875428 + ], + "张家界": [ + 110.479191, + 29.117096 + ], + "张家口": [ + 114.87, + 40.82 + ], + "张掖": [ + 100.26, + 38.56 + ], + "章丘": [ + 117.53, + 36.72 + ], + "漳平": [ + 117.24, + 25.17 + ], + "漳州": [ + 117.39, + 24.31 + ], + "樟树": [ + 115.32, + 28.03 + ], + "长春": [ + 125.35, + 43.88 + ], + "长葛": [ + 113.47, + 34.12 + ], + "长乐": [ + 119.31, + 25.58 + ], + "长沙": [ + 113, + 28.21 + ], + "长治": [ + 113.08, + 36.18 + ], + "招远": [ + 120.38, + 37.35 + ], + "昭通": [ + 103.42, + 27.2 + ], + "肇东": [ + 125.58, + 46.04 + ], + "肇庆": [ + 112.44, + 23.05 + ], + "镇江": [ + 119.44, + 32.2 + ], + "郑州": [ + 113.65, + 34.76 + ], + "枝城": [ + 111.27, + 30.23 + ], + "中山": [ + 113.38, + 22.52 + ], + "钟祥": [ + 112.34, + 31.1 + ], + "舟山": [ + 122.207216, + 29.985295 + ], + "周口": [ + 114.38, + 33.37 + ], + "株洲": [ + 113.16, + 27.83 + ], + "珠海": [ + 113.52, + 22.3 + ], + "诸城": [ + 119.24, + 35.59 + ], + "诸暨": [ + 120.23, + 29.71 + ], + "驻马店": [ + 114.01, + 32.58 + ], + "庄河": [ + 122.58, + 39.41 + ], + "涿州": [ + 115.59, + 39.29 + ], + "资兴": [ + 113.13, + 25.58 + ], + "资阳": [ + 104.38, + 30.09 + ], + "淄博": [ + 118.05, + 36.78 + ], + "自贡": [ + 104.778442, + 29.33903 + ], + "邹城": [ + 116.58, + 35.24 + ], + "遵化": [ + 117.58, + 40.11 + ], + "遵义": [ + 106.9, + 27.7 + ], + "晋中": [ + 112.75, + 37.68 + ], + "吕梁": [ + 111.13, + 37.52 + ], + "呼伦贝尔": [ + 119.77, + 49.22 + ], + "巴彦淖尔": [ + 107.42, + 40.75 + ], + "抚州": [ + 116.35, + 28 + ], + "襄阳": [ + 112.2, + 32.08 + ], + "黄冈": [ + 114.87, + 30.45 + ], + "红河": [ + 103.4, + 23.37 + ], + "文山": [ + 104.25, + 23.37 + ], + "商洛": [ + 109.93, + 33.87 + ], + "定西": [ + 104.62, + 35.58 + ], + "陇南": [ + 104.92, + 33.4 + ], + "北京市": [ + 116.4, + 39.9 + ], + "天安门": [ + 116.38, + 39.9 + ], + "东城区": [ + 116.42, + 39.93 + ], + "西城区": [ + 116.37, + 39.92 + ], + "崇文区": [ + 116.43, + 39.88 + ], + "宣武区": [ + 116.35, + 39.87 + ], + "丰台区": [ + 116.28, + 39.85 + ], + "石景山区": [ + 116.22, + 39.9 + ], + "海淀区": [ + 116.3, + 39.95 + ], + "门头沟区": [ + 116.1, + 39.93 + ], + "房山区": [ + 116.13, + 39.75 + ], + "通州区": [ + 116.65, + 39.92 + ], + "顺义区": [ + 116.65, + 40.13 + ], + "昌平区": [ + 116.23, + 40.22 + ], + "大兴区": [ + 116.33, + 39.73 + ], + "怀柔区": [ + 116.63, + 40.32 + ], + "平谷区": [ + 117.12, + 40.13 + ], + "密云区": [ + 116.83, + 40.37 + ], + "延庆区": [ + 115.97, + 40.45 + ], + "天津市": [ + 117.2, + 39.12 + ], + "河西区": [ + 117.22, + 39.12 + ], + "南开区": [ + 117.15, + 39.13 + ], + "河北区": [ + 117.18, + 39.15 + ], + "红桥区": [ + 117.15, + 39.17 + ], + "塘沽区": [ + 117.65, + 39.02 + ], + "汉沽区": [ + 117.8, + 39.25 + ], + "大港区": [ + 117.45, + 38.83 + ], + "东丽区": [ + 117.3, + 39.08 + ], + "西青区": [ + 117, + 39.13 + ], + "津南区": [ + 117.38, + 38.98 + ], + "北辰区": [ + 117.13, + 39.22 + ], + "武清区": [ + 117.03, + 39.38 + ], + "宝坻区": [ + 117.3, + 39.72 + ], + "滨海新区": [ + 117.68, + 39.03 + ], + "宁河县": [ + 117.82, + 39.33 + ], + "静海县": [ + 116.92, + 38.93 + ], + "蓟县": [ + 117.4, + 40.05 + ], + "石家庄市": [ + 114.52, + 38.05 + ], + "井陉矿区": [ + 114.05, + 38.08 + ], + "裕华区": [ + 114.52, + 38.02 + ], + "井陉县": [ + 114.13, + 38.03 + ], + "正定县": [ + 114.57, + 38.15 + ], + "栾城县": [ + 114.65, + 37.88 + ], + "行唐县": [ + 114.55, + 38.43 + ], + "灵寿县": [ + 114.37, + 38.3 + ], + "高邑县": [ + 114.6, + 37.6 + ], + "深泽县": [ + 115.2, + 38.18 + ], + "赞皇县": [ + 114.38, + 37.67 + ], + "无极县": [ + 114.97, + 38.18 + ], + "平山县": [ + 114.2, + 38.25 + ], + "元氏县": [ + 114.52, + 37.75 + ], + "赵县": [ + 114.77, + 37.75 + ], + "辛集市": [ + 115.22, + 37.92 + ], + "藁城市": [ + 114.83, + 38.03 + ], + "晋州市": [ + 115.03, + 38.03 + ], + "新乐市": [ + 114.68, + 38.35 + ], + "鹿泉市": [ + 114.3, + 38.08 + ], + "唐山市": [ + 118.2, + 39.63 + ], + "路南区": [ + 118.17, + 39.63 + ], + "路北区": [ + 118.22, + 39.63 + ], + "古冶区": [ + 118.42, + 39.73 + ], + "开平区": [ + 118.27, + 39.68 + ], + "丰南区": [ + 118.1, + 39.57 + ], + "丰润区": [ + 118.17, + 39.83 + ], + "滦县": [ + 118.7, + 39.75 + ], + "滦南县": [ + 118.68, + 39.5 + ], + "乐亭县": [ + 118.9, + 39.42 + ], + "迁西县": [ + 118.32, + 40.15 + ], + "玉田县": [ + 117.73, + 39.88 + ], + "唐海县": [ + 118.45, + 39.27 + ], + "遵化市": [ + 117.95, + 40.18 + ], + "迁安市": [ + 118.7, + 40.02 + ], + "秦皇岛市": [ + 119.6, + 39.93 + ], + "海港区": [ + 119.6, + 39.93 + ], + "山海关区": [ + 119.77, + 40 + ], + "北戴河区": [ + 119.48, + 39.83 + ], + "青龙满族自治县": [ + 118.95, + 40.4 + ], + "昌黎县": [ + 119.17, + 39.7 + ], + "抚宁县": [ + 119.23, + 39.88 + ], + "卢龙县": [ + 118.87, + 39.88 + ], + "邯郸市": [ + 114.48, + 36.62 + ], + "邯山区": [ + 114.48, + 36.6 + ], + "丛台区": [ + 114.48, + 36.63 + ], + "复兴区": [ + 114.45, + 36.63 + ], + "峰峰矿区": [ + 114.2, + 36.42 + ], + "邯郸县": [ + 114.53, + 36.6 + ], + "临漳县": [ + 114.62, + 36.35 + ], + "成安县": [ + 114.68, + 36.43 + ], + "大名县": [ + 115.15, + 36.28 + ], + "涉县": [ + 113.67, + 36.57 + ], + "磁县": [ + 114.37, + 36.35 + ], + "肥乡县": [ + 114.8, + 36.55 + ], + "永年县": [ + 114.48, + 36.78 + ], + "邱县": [ + 115.17, + 36.82 + ], + "鸡泽县": [ + 114.87, + 36.92 + ], + "广平县": [ + 114.93, + 36.48 + ], + "馆陶县": [ + 115.3, + 36.53 + ], + "魏县": [ + 114.93, + 36.37 + ], + "曲周县": [ + 114.95, + 36.78 + ], + "武安市": [ + 114.2, + 36.7 + ], + "邢台市": [ + 114.48, + 37.07 + ], + "邢台县": [ + 114.5, + 37.08 + ], + "临城县": [ + 114.5, + 37.43 + ], + "内丘县": [ + 114.52, + 37.3 + ], + "柏乡县": [ + 114.68, + 37.5 + ], + "隆尧县": [ + 114.77, + 37.35 + ], + "任县": [ + 114.68, + 37.13 + ], + "南和县": [ + 114.68, + 37 + ], + "宁晋县": [ + 114.92, + 37.62 + ], + "巨鹿县": [ + 115.03, + 37.22 + ], + "新河县": [ + 115.25, + 37.53 + ], + "广宗县": [ + 115.15, + 37.07 + ], + "平乡县": [ + 115.03, + 37.07 + ], + "威县": [ + 115.25, + 36.98 + ], + "清河县": [ + 115.67, + 37.07 + ], + "临西县": [ + 115.5, + 36.85 + ], + "南宫市": [ + 115.38, + 37.35 + ], + "沙河市": [ + 114.5, + 36.85 + ], + "保定市": [ + 115.47, + 38.87 + ], + "北市区": [ + 115.48, + 38.87 + ], + "南市区": [ + 115.5, + 38.85 + ], + "满城县": [ + 115.32, + 38.95 + ], + "清苑县": [ + 115.48, + 38.77 + ], + "涞水县": [ + 115.72, + 39.4 + ], + "阜平县": [ + 114.18, + 38.85 + ], + "徐水县": [ + 115.65, + 39.02 + ], + "定兴县": [ + 115.77, + 39.27 + ], + "唐县": [ + 114.98, + 38.75 + ], + "高阳县": [ + 115.78, + 38.68 + ], + "容城县": [ + 115.87, + 39.05 + ], + "涞源县": [ + 114.68, + 39.35 + ], + "望都县": [ + 115.15, + 38.72 + ], + "安新县": [ + 115.93, + 38.92 + ], + "易县": [ + 115.5, + 39.35 + ], + "曲阳县": [ + 114.7, + 38.62 + ], + "蠡县": [ + 115.57, + 38.48 + ], + "顺平县": [ + 115.13, + 38.83 + ], + "博野县": [ + 115.47, + 38.45 + ], + "雄县": [ + 116.1, + 38.98 + ], + "涿州市": [ + 115.97, + 39.48 + ], + "定州市": [ + 114.97, + 38.52 + ], + "安国市": [ + 115.32, + 38.42 + ], + "高碑店市": [ + 115.85, + 39.33 + ], + "张家口市": [ + 114.88, + 40.82 + ], + "宣化区": [ + 115.05, + 40.6 + ], + "下花园区": [ + 115.27, + 40.48 + ], + "宣化县": [ + 115.02, + 40.55 + ], + "张北县": [ + 114.7, + 41.15 + ], + "康保县": [ + 114.62, + 41.85 + ], + "沽源县": [ + 115.7, + 41.67 + ], + "尚义县": [ + 113.97, + 41.08 + ], + "蔚县": [ + 114.57, + 39.85 + ], + "阳原县": [ + 114.17, + 40.12 + ], + "怀安县": [ + 114.42, + 40.67 + ], + "万全县": [ + 114.72, + 40.75 + ], + "怀来县": [ + 115.52, + 40.4 + ], + "涿鹿县": [ + 115.22, + 40.38 + ], + "赤城县": [ + 115.83, + 40.92 + ], + "崇礼县": [ + 115.27, + 40.97 + ], + "承德市": [ + 117.93, + 40.97 + ], + "双滦区": [ + 117.78, + 40.95 + ], + "鹰手营子矿区": [ + 117.65, + 40.55 + ], + "承德县": [ + 118.17, + 40.77 + ], + "兴隆县": [ + 117.52, + 40.43 + ], + "平泉县": [ + 118.68, + 41 + ], + "滦平县": [ + 117.33, + 40.93 + ], + "隆化县": [ + 117.72, + 41.32 + ], + "丰宁满族自治县": [ + 116.65, + 41.2 + ], + "宽城满族自治县": [ + 118.48, + 40.6 + ], + "围场满族蒙古族自治县": [ + 117.75, + 41.93 + ], + "沧州市": [ + 116.83, + 38.3 + ], + "运河区": [ + 116.85, + 38.32 + ], + "沧县": [ + 116.87, + 38.3 + ], + "青县": [ + 116.82, + 38.58 + ], + "东光县": [ + 116.53, + 37.88 + ], + "海兴县": [ + 117.48, + 38.13 + ], + "盐山县": [ + 117.22, + 38.05 + ], + "肃宁县": [ + 115.83, + 38.43 + ], + "南皮县": [ + 116.7, + 38.03 + ], + "吴桥县": [ + 116.38, + 37.62 + ], + "献县": [ + 116.12, + 38.18 + ], + "孟村回族自治县": [ + 117.1, + 38.07 + ], + "泊头市": [ + 116.57, + 38.07 + ], + "任丘市": [ + 116.1, + 38.72 + ], + "黄骅市": [ + 117.35, + 38.37 + ], + "河间市": [ + 116.08, + 38.43 + ], + "廊坊市": [ + 116.7, + 39.52 + ], + "安次区": [ + 116.68, + 39.52 + ], + "广阳区": [ + 116.72, + 39.53 + ], + "固安县": [ + 116.3, + 39.43 + ], + "永清县": [ + 116.5, + 39.32 + ], + "香河县": [ + 117, + 39.77 + ], + "大城县": [ + 116.63, + 38.7 + ], + "文安县": [ + 116.47, + 38.87 + ], + "大厂回族自治县": [ + 116.98, + 39.88 + ], + "霸州市": [ + 116.4, + 39.1 + ], + "三河市": [ + 117.07, + 39.98 + ], + "衡水市": [ + 115.68, + 37.73 + ], + "桃城区": [ + 115.68, + 37.73 + ], + "枣强县": [ + 115.72, + 37.52 + ], + "武邑县": [ + 115.88, + 37.82 + ], + "武强县": [ + 115.98, + 38.03 + ], + "饶阳县": [ + 115.73, + 38.23 + ], + "安平县": [ + 115.52, + 38.23 + ], + "故城县": [ + 115.97, + 37.35 + ], + "景县": [ + 116.27, + 37.7 + ], + "阜城县": [ + 116.15, + 37.87 + ], + "冀州市": [ + 115.57, + 37.57 + ], + "深州市": [ + 115.55, + 38.02 + ], + "太原市": [ + 112.55, + 37.87 + ], + "小店区": [ + 112.57, + 37.73 + ], + "迎泽区": [ + 112.57, + 37.87 + ], + "杏花岭区": [ + 112.57, + 37.88 + ], + "尖草坪区": [ + 112.48, + 37.93 + ], + "万柏林区": [ + 112.52, + 37.87 + ], + "晋源区": [ + 112.48, + 37.73 + ], + "清徐县": [ + 112.35, + 37.6 + ], + "阳曲县": [ + 112.67, + 38.07 + ], + "娄烦县": [ + 111.78, + 38.07 + ], + "古交市": [ + 112.17, + 37.92 + ], + "大同市": [ + 113.3, + 40.08 + ], + "南郊区": [ + 113.13, + 40 + ], + "新荣区": [ + 113.15, + 40.27 + ], + "阳高县": [ + 113.75, + 40.37 + ], + "天镇县": [ + 114.08, + 40.42 + ], + "广灵县": [ + 114.28, + 39.77 + ], + "灵丘县": [ + 114.23, + 39.43 + ], + "浑源县": [ + 113.68, + 39.7 + ], + "左云县": [ + 112.7, + 40 + ], + "大同县": [ + 113.6, + 40.03 + ], + "阳泉市": [ + 113.57, + 37.85 + ], + "平定县": [ + 113.62, + 37.8 + ], + "盂县": [ + 113.4, + 38.08 + ], + "长治市": [ + 113.12, + 36.2 + ], + "长治县": [ + 113.03, + 36.05 + ], + "襄垣县": [ + 113.05, + 36.53 + ], + "屯留县": [ + 112.88, + 36.32 + ], + "平顺县": [ + 113.43, + 36.2 + ], + "黎城县": [ + 113.38, + 36.5 + ], + "壶关县": [ + 113.2, + 36.12 + ], + "长子县": [ + 112.87, + 36.12 + ], + "武乡县": [ + 112.85, + 36.83 + ], + "沁县": [ + 112.7, + 36.75 + ], + "沁源县": [ + 112.33, + 36.5 + ], + "潞城市": [ + 113.22, + 36.33 + ], + "晋城市": [ + 112.83, + 35.5 + ], + "沁水县": [ + 112.18, + 35.68 + ], + "阳城县": [ + 112.42, + 35.48 + ], + "陵川县": [ + 113.27, + 35.78 + ], + "泽州县": [ + 112.83, + 35.5 + ], + "高平市": [ + 112.92, + 35.8 + ], + "朔州市": [ + 112.43, + 39.33 + ], + "朔城区": [ + 112.43, + 39.33 + ], + "山阴县": [ + 112.82, + 39.52 + ], + "应县": [ + 113.18, + 39.55 + ], + "右玉县": [ + 112.47, + 39.98 + ], + "怀仁县": [ + 113.08, + 39.83 + ], + "晋中市": [ + 112.75, + 37.68 + ], + "榆次区": [ + 112.75, + 37.68 + ], + "榆社县": [ + 112.97, + 37.07 + ], + "左权县": [ + 113.37, + 37.07 + ], + "和顺县": [ + 113.57, + 37.33 + ], + "昔阳县": [ + 113.7, + 37.62 + ], + "寿阳县": [ + 113.18, + 37.88 + ], + "太谷县": [ + 112.55, + 37.42 + ], + "祁县": [ + 112.33, + 37.35 + ], + "平遥县": [ + 112.17, + 37.18 + ], + "灵石县": [ + 111.77, + 36.85 + ], + "介休市": [ + 111.92, + 37.03 + ], + "运城市": [ + 110.98, + 35.02 + ], + "盐湖区": [ + 110.98, + 35.02 + ], + "临猗县": [ + 110.77, + 35.15 + ], + "万荣县": [ + 110.83, + 35.42 + ], + "闻喜县": [ + 111.22, + 35.35 + ], + "稷山县": [ + 110.97, + 35.6 + ], + "新绛县": [ + 111.22, + 35.62 + ], + "绛县": [ + 111.57, + 35.48 + ], + "垣曲县": [ + 111.67, + 35.3 + ], + "夏县": [ + 111.22, + 35.15 + ], + "平陆县": [ + 111.22, + 34.83 + ], + "芮城县": [ + 110.68, + 34.7 + ], + "河津市": [ + 110.7, + 35.6 + ], + "忻州市": [ + 112.73, + 38.42 + ], + "忻府区": [ + 112.73, + 38.42 + ], + "定襄县": [ + 112.95, + 38.48 + ], + "五台县": [ + 113.25, + 38.73 + ], + "代县": [ + 112.95, + 39.07 + ], + "繁峙县": [ + 113.25, + 39.18 + ], + "宁武县": [ + 112.3, + 39 + ], + "静乐县": [ + 111.93, + 38.37 + ], + "神池县": [ + 112.2, + 39.08 + ], + "五寨县": [ + 111.85, + 38.9 + ], + "岢岚县": [ + 111.57, + 38.7 + ], + "河曲县": [ + 111.13, + 39.38 + ], + "偏关县": [ + 111.5, + 39.43 + ], + "原平市": [ + 112.7, + 38.73 + ], + "临汾市": [ + 111.52, + 36.08 + ], + "尧都区": [ + 111.52, + 36.08 + ], + "曲沃县": [ + 111.47, + 35.63 + ], + "翼城县": [ + 111.72, + 35.73 + ], + "襄汾县": [ + 111.43, + 35.88 + ], + "洪洞县": [ + 111.67, + 36.25 + ], + "古县": [ + 111.92, + 36.27 + ], + "安泽县": [ + 112.25, + 36.15 + ], + "浮山县": [ + 111.83, + 35.97 + ], + "吉县": [ + 110.68, + 36.1 + ], + "乡宁县": [ + 110.83, + 35.97 + ], + "大宁县": [ + 110.75, + 36.47 + ], + "隰县": [ + 110.93, + 36.7 + ], + "永和县": [ + 110.63, + 36.77 + ], + "蒲县": [ + 111.08, + 36.42 + ], + "汾西县": [ + 111.57, + 36.65 + ], + "侯马市": [ + 111.35, + 35.62 + ], + "霍州市": [ + 111.72, + 36.57 + ], + "吕梁市": [ + 111.13, + 37.52 + ], + "离石区": [ + 111.13, + 37.52 + ], + "文水县": [ + 112.02, + 37.43 + ], + "交城县": [ + 112.15, + 37.55 + ], + "兴县": [ + 111.12, + 38.47 + ], + "临县": [ + 110.98, + 37.95 + ], + "柳林县": [ + 110.9, + 37.43 + ], + "石楼县": [ + 110.83, + 37 + ], + "岚县": [ + 111.67, + 38.28 + ], + "方山县": [ + 111.23, + 37.88 + ], + "中阳县": [ + 111.18, + 37.33 + ], + "交口县": [ + 111.2, + 36.97 + ], + "孝义市": [ + 111.77, + 37.15 + ], + "汾阳市": [ + 111.78, + 37.27 + ], + "呼和浩特市": [ + 111.73, + 40.83 + ], + "回民区": [ + 111.6, + 40.8 + ], + "玉泉区": [ + 111.67, + 40.75 + ], + "赛罕区": [ + 111.68, + 40.8 + ], + "土默特左旗": [ + 111.13, + 40.72 + ], + "托克托县": [ + 111.18, + 40.27 + ], + "和林格尔县": [ + 111.82, + 40.38 + ], + "清水河县": [ + 111.68, + 39.92 + ], + "武川县": [ + 111.45, + 41.08 + ], + "包头市": [ + 109.83, + 40.65 + ], + "东河区": [ + 110.02, + 40.58 + ], + "昆都仑区": [ + 109.83, + 40.63 + ], + "石拐区": [ + 110.27, + 40.68 + ], + "九原区": [ + 109.97, + 40.6 + ], + "土默特右旗": [ + 110.52, + 40.57 + ], + "固阳县": [ + 110.05, + 41.03 + ], + "达尔罕茂明安联合旗": [ + 110.43, + 41.7 + ], + "乌海市": [ + 106.82, + 39.67 + ], + "海勃湾区": [ + 106.83, + 39.7 + ], + "海南区": [ + 106.88, + 39.43 + ], + "乌达区": [ + 106.7, + 39.5 + ], + "赤峰市": [ + 118.92, + 42.27 + ], + "红山区": [ + 118.97, + 42.28 + ], + "元宝山区": [ + 119.28, + 42.03 + ], + "松山区": [ + 118.92, + 42.28 + ], + "阿鲁科尔沁旗": [ + 120.08, + 43.88 + ], + "巴林左旗": [ + 119.38, + 43.98 + ], + "巴林右旗": [ + 118.67, + 43.52 + ], + "林西县": [ + 118.05, + 43.6 + ], + "克什克腾旗": [ + 117.53, + 43.25 + ], + "翁牛特旗": [ + 119.02, + 42.93 + ], + "喀喇沁旗": [ + 118.7, + 41.93 + ], + "宁城县": [ + 119.33, + 41.6 + ], + "敖汉旗": [ + 119.9, + 42.28 + ], + "通辽市": [ + 122.27, + 43.62 + ], + "科尔沁区": [ + 122.27, + 43.62 + ], + "科尔沁左翼中旗": [ + 123.32, + 44.13 + ], + "科尔沁左翼后旗": [ + 122.35, + 42.95 + ], + "开鲁县": [ + 121.3, + 43.6 + ], + "库伦旗": [ + 121.77, + 42.73 + ], + "奈曼旗": [ + 120.65, + 42.85 + ], + "扎鲁特旗": [ + 120.92, + 44.55 + ], + "霍林郭勒市": [ + 119.65, + 45.53 + ], + "鄂尔多斯市": [ + 109.8, + 39.62 + ], + "东胜区": [ + 110, + 39.82 + ], + "达拉特旗": [ + 110.03, + 40.4 + ], + "准格尔旗": [ + 111.23, + 39.87 + ], + "鄂托克前旗": [ + 107.48, + 38.18 + ], + "鄂托克旗": [ + 107.98, + 39.1 + ], + "杭锦旗": [ + 108.72, + 39.83 + ], + "乌审旗": [ + 108.85, + 38.6 + ], + "伊金霍洛旗": [ + 109.73, + 39.57 + ], + "呼伦贝尔市": [ + 119.77, + 49.22 + ], + "海拉尔区": [ + 119.77, + 49.22 + ], + "阿荣旗": [ + 123.47, + 48.13 + ], + "鄂伦春自治旗": [ + 123.72, + 50.58 + ], + "鄂温克族自治旗": [ + 119.75, + 49.13 + ], + "陈巴尔虎旗": [ + 119.43, + 49.32 + ], + "新巴尔虎左旗": [ + 118.27, + 48.22 + ], + "新巴尔虎右旗": [ + 116.82, + 48.67 + ], + "满洲里市": [ + 117.45, + 49.58 + ], + "牙克石市": [ + 120.73, + 49.28 + ], + "扎兰屯市": [ + 122.75, + 47.98 + ], + "额尔古纳市": [ + 120.18, + 50.23 + ], + "根河市": [ + 121.52, + 50.78 + ], + "巴彦淖尔市": [ + 107.42, + 40.75 + ], + "临河区": [ + 107.4, + 40.75 + ], + "五原县": [ + 108.27, + 41.1 + ], + "磴口县": [ + 107.02, + 40.33 + ], + "乌拉特前旗": [ + 108.65, + 40.72 + ], + "乌拉特中旗": [ + 108.52, + 41.57 + ], + "乌拉特后旗": [ + 107.07, + 41.1 + ], + "杭锦后旗": [ + 107.15, + 40.88 + ], + "乌兰察布市": [ + 113.12, + 40.98 + ], + "集宁区": [ + 113.1, + 41.03 + ], + "卓资县": [ + 112.57, + 40.9 + ], + "化德县": [ + 114, + 41.9 + ], + "商都县": [ + 113.53, + 41.55 + ], + "兴和县": [ + 113.88, + 40.88 + ], + "凉城县": [ + 112.48, + 40.53 + ], + "察哈尔右翼前旗": [ + 113.22, + 40.78 + ], + "察哈尔右翼中旗": [ + 112.63, + 41.27 + ], + "察哈尔右翼后旗": [ + 113.18, + 41.45 + ], + "四子王旗": [ + 111.7, + 41.52 + ], + "丰镇市": [ + 113.15, + 40.43 + ], + "兴安盟": [ + 122.05, + 46.08 + ], + "乌兰浩特市": [ + 122.05, + 46.08 + ], + "阿尔山市": [ + 119.93, + 47.18 + ], + "科尔沁右翼前旗": [ + 121.92, + 46.07 + ], + "科尔沁右翼中旗": [ + 121.47, + 45.05 + ], + "扎赉特旗": [ + 122.9, + 46.73 + ], + "突泉县": [ + 121.57, + 45.38 + ], + "锡林郭勒盟": [ + 116.07, + 43.95 + ], + "二连浩特市": [ + 111.98, + 43.65 + ], + "锡林浩特市": [ + 116.07, + 43.93 + ], + "阿巴嘎旗": [ + 114.97, + 44.02 + ], + "苏尼特左旗": [ + 113.63, + 43.85 + ], + "苏尼特右旗": [ + 112.65, + 42.75 + ], + "东乌珠穆沁旗": [ + 116.97, + 45.52 + ], + "西乌珠穆沁旗": [ + 117.6, + 44.58 + ], + "太仆寺旗": [ + 115.28, + 41.9 + ], + "镶黄旗": [ + 113.83, + 42.23 + ], + "正镶白旗": [ + 115, + 42.3 + ], + "正蓝旗": [ + 116, + 42.25 + ], + "多伦县": [ + 116.47, + 42.18 + ], + "阿拉善盟": [ + 105.67, + 38.83 + ], + "阿拉善左旗": [ + 105.67, + 38.83 + ], + "阿拉善右旗": [ + 101.68, + 39.2 + ], + "额济纳旗": [ + 101.07, + 41.97 + ], + "沈阳市": [ + 123.43, + 41.8 + ], + "和平区": [ + 123.4, + 41.78 + ], + "沈河区": [ + 123.45, + 41.8 + ], + "大东区": [ + 123.47, + 41.8 + ], + "皇姑区": [ + 123.42, + 41.82 + ], + "铁西区": [ + 123.35, + 41.8 + ], + "苏家屯区": [ + 123.33, + 41.67 + ], + "东陵区": [ + 123.47, + 41.77 + ], + "新城子区": [ + 123.52, + 42.05 + ], + "于洪区": [ + 123.3, + 41.78 + ], + "辽中县": [ + 122.72, + 41.52 + ], + "康平县": [ + 123.35, + 42.75 + ], + "法库县": [ + 123.4, + 42.5 + ], + "新民市": [ + 122.82, + 42 + ], + "大连市": [ + 121.62, + 38.92 + ], + "中山区": [ + 121.63, + 38.92 + ], + "西岗区": [ + 121.6, + 38.92 + ], + "沙河口区": [ + 121.58, + 38.9 + ], + "甘井子区": [ + 121.57, + 38.95 + ], + "旅顺口区": [ + 121.27, + 38.82 + ], + "金州区": [ + 121.7, + 39.1 + ], + "长海县": [ + 122.58, + 39.27 + ], + "瓦房店市": [ + 122, + 39.62 + ], + "普兰店市": [ + 121.95, + 39.4 + ], + "庄河市": [ + 122.98, + 39.7 + ], + "鞍山市": [ + 122.98, + 41.1 + ], + "立山区": [ + 123, + 41.15 + ], + "千山区": [ + 122.97, + 41.07 + ], + "台安县": [ + 122.42, + 41.38 + ], + "岫岩满族自治县": [ + 123.28, + 40.28 + ], + "海城市": [ + 122.7, + 40.88 + ], + "抚顺市": [ + 123.98, + 41.88 + ], + "新抚区": [ + 123.88, + 41.87 + ], + "东洲区": [ + 124.02, + 41.85 + ], + "望花区": [ + 123.78, + 41.85 + ], + "顺城区": [ + 123.93, + 41.88 + ], + "抚顺县": [ + 123.9, + 41.88 + ], + "新宾满族自治县": [ + 125.03, + 41.73 + ], + "清原满族自治县": [ + 124.92, + 42.1 + ], + "本溪市": [ + 123.77, + 41.3 + ], + "平山区": [ + 123.77, + 41.3 + ], + "溪湖区": [ + 123.77, + 41.33 + ], + "明山区": [ + 123.82, + 41.3 + ], + "南芬区": [ + 123.73, + 41.1 + ], + "本溪满族自治县": [ + 124.12, + 41.3 + ], + "桓仁满族自治县": [ + 125.35, + 41.27 + ], + "丹东市": [ + 124.38, + 40.13 + ], + "元宝区": [ + 124.38, + 40.13 + ], + "振兴区": [ + 124.35, + 40.08 + ], + "振安区": [ + 124.42, + 40.17 + ], + "宽甸满族自治县": [ + 124.78, + 40.73 + ], + "东港市": [ + 124.15, + 39.87 + ], + "凤城市": [ + 124.07, + 40.45 + ], + "锦州市": [ + 121.13, + 41.1 + ], + "古塔区": [ + 121.12, + 41.13 + ], + "凌河区": [ + 121.15, + 41.12 + ], + "太和区": [ + 121.1, + 41.1 + ], + "黑山县": [ + 122.12, + 41.7 + ], + "义县": [ + 121.23, + 41.53 + ], + "凌海市": [ + 121.35, + 41.17 + ], + "营口市": [ + 122.23, + 40.67 + ], + "站前区": [ + 122.27, + 40.68 + ], + "西市区": [ + 122.22, + 40.67 + ], + "鲅鱼圈区": [ + 122.12, + 40.27 + ], + "老边区": [ + 122.37, + 40.67 + ], + "盖州市": [ + 122.35, + 40.4 + ], + "大石桥市": [ + 122.5, + 40.65 + ], + "阜新市": [ + 121.67, + 42.02 + ], + "海州区": [ + 121.65, + 42.02 + ], + "太平区": [ + 121.67, + 42.02 + ], + "清河门区": [ + 121.42, + 41.75 + ], + "细河区": [ + 121.68, + 42.03 + ], + "阜新蒙古族自治县": [ + 121.75, + 42.07 + ], + "彰武县": [ + 122.53, + 42.38 + ], + "辽阳市": [ + 123.17, + 41.27 + ], + "白塔区": [ + 123.17, + 41.27 + ], + "文圣区": [ + 123.18, + 41.27 + ], + "宏伟区": [ + 123.2, + 41.2 + ], + "弓长岭区": [ + 123.45, + 41.13 + ], + "太子河区": [ + 123.18, + 41.25 + ], + "辽阳县": [ + 123.07, + 41.22 + ], + "灯塔市": [ + 123.33, + 41.42 + ], + "盘锦市": [ + 122.07, + 41.12 + ], + "双台子区": [ + 122.05, + 41.2 + ], + "兴隆台区": [ + 122.07, + 41.12 + ], + "大洼县": [ + 122.07, + 40.98 + ], + "盘山县": [ + 122.02, + 41.25 + ], + "铁岭市": [ + 123.83, + 42.28 + ], + "银州区": [ + 123.85, + 42.28 + ], + "铁岭县": [ + 123.83, + 42.3 + ], + "西丰县": [ + 124.72, + 42.73 + ], + "昌图县": [ + 124.1, + 42.78 + ], + "调兵山市": [ + 123.55, + 42.47 + ], + "开原市": [ + 124.03, + 42.55 + ], + "朝阳市": [ + 120.45, + 41.57 + ], + "双塔区": [ + 120.45, + 41.57 + ], + "龙城区": [ + 120.43, + 41.6 + ], + "朝阳县": [ + 120.47, + 41.58 + ], + "建平县": [ + 119.63, + 41.4 + ], + "北票市": [ + 120.77, + 41.8 + ], + "凌源市": [ + 119.4, + 41.25 + ], + "葫芦岛市": [ + 120.83, + 40.72 + ], + "连山区": [ + 120.87, + 40.77 + ], + "龙港区": [ + 120.93, + 40.72 + ], + "南票区": [ + 120.75, + 41.1 + ], + "绥中县": [ + 120.33, + 40.32 + ], + "建昌县": [ + 119.8, + 40.82 + ], + "兴城市": [ + 120.72, + 40.62 + ], + "长春市": [ + 125.32, + 43.9 + ], + "南关区": [ + 125.33, + 43.87 + ], + "宽城区": [ + 125.32, + 43.92 + ], + "朝阳区": [ + 125.28, + 43.83 + ], + "二道区": [ + 125.37, + 43.87 + ], + "绿园区": [ + 125.25, + 43.88 + ], + "双阳区": [ + 125.67, + 43.52 + ], + "农安县": [ + 125.18, + 44.43 + ], + "九台市": [ + 125.83, + 44.15 + ], + "榆树市": [ + 126.55, + 44.82 + ], + "德惠市": [ + 125.7, + 44.53 + ], + "吉林市": [ + 126.55, + 43.83 + ], + "昌邑区": [ + 126.57, + 43.88 + ], + "龙潭区": [ + 126.57, + 43.92 + ], + "船营区": [ + 126.53, + 43.83 + ], + "丰满区": [ + 126.57, + 43.82 + ], + "永吉县": [ + 126.5, + 43.67 + ], + "蛟河市": [ + 127.33, + 43.72 + ], + "桦甸市": [ + 126.73, + 42.97 + ], + "舒兰市": [ + 126.95, + 44.42 + ], + "磐石市": [ + 126.05, + 42.95 + ], + "四平市": [ + 124.35, + 43.17 + ], + "铁东区": [ + 124.38, + 43.17 + ], + "梨树县": [ + 124.33, + 43.32 + ], + "伊通满族自治县": [ + 125.3, + 43.35 + ], + "公主岭市": [ + 124.82, + 43.5 + ], + "双辽市": [ + 123.5, + 43.52 + ], + "辽源市": [ + 125.13, + 42.88 + ], + "龙山区": [ + 125.12, + 42.9 + ], + "西安区": [ + 125.15, + 42.92 + ], + "东丰县": [ + 125.53, + 42.68 + ], + "东辽县": [ + 125, + 42.92 + ], + "通化市": [ + 125.93, + 41.73 + ], + "东昌区": [ + 125.95, + 41.73 + ], + "二道江区": [ + 126.03, + 41.77 + ], + "通化县": [ + 125.75, + 41.68 + ], + "辉南县": [ + 126.03, + 42.68 + ], + "柳河县": [ + 125.73, + 42.28 + ], + "梅河口市": [ + 125.68, + 42.53 + ], + "集安市": [ + 126.18, + 41.12 + ], + "白山市": [ + 126.42, + 41.93 + ], + "八道江区": [ + 126.4, + 41.93 + ], + "抚松县": [ + 127.28, + 42.33 + ], + "靖宇县": [ + 126.8, + 42.4 + ], + "长白朝鲜族自治县": [ + 128.2, + 41.42 + ], + "临江市": [ + 126.9, + 41.8 + ], + "松原市": [ + 124.82, + 45.13 + ], + "宁江区": [ + 124.8, + 45.17 + ], + "长岭县": [ + 123.98, + 44.28 + ], + "乾安县": [ + 124.02, + 45.02 + ], + "扶余县": [ + 126.02, + 44.98 + ], + "白城市": [ + 122.83, + 45.62 + ], + "洮北区": [ + 122.85, + 45.62 + ], + "镇赉县": [ + 123.2, + 45.85 + ], + "通榆县": [ + 123.08, + 44.82 + ], + "洮南市": [ + 122.78, + 45.33 + ], + "大安市": [ + 124.28, + 45.5 + ], + "延边朝鲜族自治州": [ + 129.5, + 42.88 + ], + "延吉市": [ + 129.5, + 42.88 + ], + "图们市": [ + 129.83, + 42.97 + ], + "敦化市": [ + 128.23, + 43.37 + ], + "珲春市": [ + 130.37, + 42.87 + ], + "龙井市": [ + 129.42, + 42.77 + ], + "和龙市": [ + 129, + 42.53 + ], + "汪清县": [ + 129.75, + 43.32 + ], + "安图县": [ + 128.9, + 43.12 + ], + "哈尔滨市": [ + 126.53, + 45.8 + ], + "道里区": [ + 126.62, + 45.77 + ], + "南岗区": [ + 126.68, + 45.77 + ], + "道外区": [ + 126.65, + 45.78 + ], + "香坊区": [ + 126.68, + 45.72 + ], + "平房区": [ + 126.62, + 45.62 + ], + "松北区": [ + 126.55, + 45.8 + ], + "呼兰区": [ + 126.58, + 45.9 + ], + "依兰县": [ + 129.55, + 46.32 + ], + "方正县": [ + 128.83, + 45.83 + ], + "宾县": [ + 127.48, + 45.75 + ], + "巴彦县": [ + 127.4, + 46.08 + ], + "木兰县": [ + 128.03, + 45.95 + ], + "通河县": [ + 128.75, + 45.97 + ], + "延寿县": [ + 128.33, + 45.45 + ], + "双城市": [ + 126.32, + 45.37 + ], + "尚志市": [ + 127.95, + 45.22 + ], + "五常市": [ + 127.15, + 44.92 + ], + "齐齐哈尔市": [ + 123.95, + 47.33 + ], + "龙沙区": [ + 123.95, + 47.32 + ], + "建华区": [ + 123.95, + 47.35 + ], + "铁锋区": [ + 123.98, + 47.35 + ], + "昂昂溪区": [ + 123.8, + 47.15 + ], + "富拉尔基区": [ + 123.62, + 47.2 + ], + "龙江县": [ + 123.18, + 47.33 + ], + "依安县": [ + 125.3, + 47.88 + ], + "泰来县": [ + 123.42, + 46.4 + ], + "甘南县": [ + 123.5, + 47.92 + ], + "富裕县": [ + 124.47, + 47.82 + ], + "克山县": [ + 125.87, + 48.03 + ], + "克东县": [ + 126.25, + 48.03 + ], + "拜泉县": [ + 126.08, + 47.6 + ], + "讷河市": [ + 124.87, + 48.48 + ], + "鸡西市": [ + 130.97, + 45.3 + ], + "鸡冠区": [ + 130.97, + 45.3 + ], + "恒山区": [ + 130.93, + 45.2 + ], + "滴道区": [ + 130.78, + 45.37 + ], + "梨树区": [ + 130.68, + 45.08 + ], + "城子河区": [ + 131, + 45.33 + ], + "麻山区": [ + 130.52, + 45.2 + ], + "鸡东县": [ + 131.13, + 45.25 + ], + "虎林市": [ + 132.98, + 45.77 + ], + "密山市": [ + 131.87, + 45.55 + ], + "鹤岗市": [ + 130.27, + 47.33 + ], + "向阳区": [ + 130.28, + 47.33 + ], + "工农区": [ + 130.25, + 47.32 + ], + "兴安区": [ + 130.22, + 47.27 + ], + "东山区": [ + 130.32, + 47.33 + ], + "兴山区": [ + 130.3, + 47.37 + ], + "萝北县": [ + 130.83, + 47.58 + ], + "绥滨县": [ + 131.85, + 47.28 + ], + "双鸭山市": [ + 131.15, + 46.63 + ], + "尖山区": [ + 131.17, + 46.63 + ], + "岭东区": [ + 131.13, + 46.57 + ], + "四方台区": [ + 131.33, + 46.58 + ], + "集贤县": [ + 131.13, + 46.72 + ], + "友谊县": [ + 131.8, + 46.78 + ], + "宝清县": [ + 132.2, + 46.32 + ], + "饶河县": [ + 134.02, + 46.8 + ], + "大庆市": [ + 125.03, + 46.58 + ], + "萨尔图区": [ + 125.02, + 46.6 + ], + "龙凤区": [ + 125.1, + 46.53 + ], + "让胡路区": [ + 124.85, + 46.65 + ], + "红岗区": [ + 124.88, + 46.4 + ], + "大同区": [ + 124.82, + 46.03 + ], + "肇州县": [ + 125.27, + 45.7 + ], + "肇源县": [ + 125.08, + 45.52 + ], + "林甸县": [ + 124.87, + 47.18 + ], + "杜尔伯特蒙古族自治县": [ + 124.45, + 46.87 + ], + "伊春市": [ + 128.9, + 47.73 + ], + "南岔区": [ + 129.28, + 47.13 + ], + "友好区": [ + 128.82, + 47.85 + ], + "西林区": [ + 129.28, + 47.48 + ], + "翠峦区": [ + 128.65, + 47.72 + ], + "新青区": [ + 129.53, + 48.28 + ], + "美溪区": [ + 129.13, + 47.63 + ], + "金山屯区": [ + 129.43, + 47.42 + ], + "五营区": [ + 129.25, + 48.12 + ], + "乌马河区": [ + 128.78, + 47.72 + ], + "汤旺河区": [ + 129.57, + 48.45 + ], + "带岭区": [ + 129.02, + 47.02 + ], + "乌伊岭区": [ + 129.42, + 48.6 + ], + "红星区": [ + 129.38, + 48.23 + ], + "上甘岭区": [ + 129.02, + 47.97 + ], + "嘉荫县": [ + 130.38, + 48.88 + ], + "铁力市": [ + 128.02, + 46.98 + ], + "佳木斯市": [ + 130.37, + 46.82 + ], + "前进区": [ + 130.37, + 46.82 + ], + "东风区": [ + 130.4, + 46.82 + ], + "桦南县": [ + 130.57, + 46.23 + ], + "桦川县": [ + 130.72, + 47.02 + ], + "汤原县": [ + 129.9, + 46.73 + ], + "抚远县": [ + 134.28, + 48.37 + ], + "同江市": [ + 132.52, + 47.65 + ], + "富锦市": [ + 132.03, + 47.25 + ], + "七台河市": [ + 130.95, + 45.78 + ], + "新兴区": [ + 130.83, + 45.8 + ], + "桃山区": [ + 130.97, + 45.77 + ], + "茄子河区": [ + 131.07, + 45.77 + ], + "勃利县": [ + 130.57, + 45.75 + ], + "牡丹江市": [ + 129.6, + 44.58 + ], + "东安区": [ + 129.62, + 44.58 + ], + "阳明区": [ + 129.63, + 44.6 + ], + "爱民区": [ + 129.58, + 44.58 + ], + "东宁县": [ + 131.12, + 44.07 + ], + "林口县": [ + 130.27, + 45.3 + ], + "绥芬河市": [ + 131.15, + 44.42 + ], + "海林市": [ + 129.38, + 44.57 + ], + "宁安市": [ + 129.47, + 44.35 + ], + "穆棱市": [ + 130.52, + 44.92 + ], + "黑河市": [ + 127.48, + 50.25 + ], + "爱辉区": [ + 127.48, + 50.25 + ], + "逊克县": [ + 128.47, + 49.58 + ], + "孙吴县": [ + 127.32, + 49.42 + ], + "北安市": [ + 126.52, + 48.23 + ], + "五大连池市": [ + 126.2, + 48.52 + ], + "绥化市": [ + 126.98, + 46.63 + ], + "北林区": [ + 126.98, + 46.63 + ], + "望奎县": [ + 126.48, + 46.83 + ], + "兰西县": [ + 126.28, + 46.27 + ], + "青冈县": [ + 126.1, + 46.68 + ], + "庆安县": [ + 127.52, + 46.88 + ], + "明水县": [ + 125.9, + 47.18 + ], + "绥棱县": [ + 127.1, + 47.25 + ], + "安达市": [ + 125.33, + 46.4 + ], + "肇东市": [ + 125.98, + 46.07 + ], + "海伦市": [ + 126.97, + 47.47 + ], + "大兴安岭地区": [ + 124.12, + 50.42 + ], + "呼玛县": [ + 126.65, + 51.73 + ], + "塔河县": [ + 124.7, + 52.32 + ], + "漠河县": [ + 122.53, + 52.97 + ], + "上海市": [ + 121.47, + 31.23 + ], + "黄浦区": [ + 121.48, + 31.23 + ], + "卢湾区": [ + 121.47, + 31.22 + ], + "徐汇区": [ + 121.43, + 31.18 + ], + "长宁区": [ + 121.42, + 31.22 + ], + "静安区": [ + 121.45, + 31.23 + ], + "闸北区": [ + 121.45, + 31.25 + ], + "虹口区": [ + 121.5, + 31.27 + ], + "杨浦区": [ + 121.52, + 31.27 + ], + "闵行区": [ + 121.38, + 31.12 + ], + "宝山区": [ + 121.48, + 31.4 + ], + "嘉定区": [ + 121.27, + 31.38 + ], + "浦东新区": [ + 121.53, + 31.22 + ], + "金山区": [ + 121.33, + 30.75 + ], + "松江区": [ + 121.22, + 31.03 + ], + "青浦区": [ + 121.12, + 31.15 + ], + "南汇区": [ + 121.75, + 31.05 + ], + "奉贤区": [ + 121.47, + 30.92 + ], + "崇明县": [ + 121.4, + 31.62 + ], + "南京市": [ + 118.78, + 32.07 + ], + "玄武区": [ + 118.8, + 32.05 + ], + "白下区": [ + 118.78, + 32.03 + ], + "秦淮区": [ + 118.8, + 32.02 + ], + "建邺区": [ + 118.75, + 32.03 + ], + "下关区": [ + 118.73, + 32.08 + ], + "浦口区": [ + 118.62, + 32.05 + ], + "栖霞区": [ + 118.88, + 32.12 + ], + "雨花台区": [ + 118.77, + 32 + ], + "江宁区": [ + 118.85, + 31.95 + ], + "六合区": [ + 118.83, + 32.35 + ], + "溧水县": [ + 119.02, + 31.65 + ], + "高淳县": [ + 118.88, + 31.33 + ], + "无锡市": [ + 120.3, + 31.57 + ], + "崇安区": [ + 120.3, + 31.58 + ], + "南长区": [ + 120.3, + 31.57 + ], + "北塘区": [ + 120.28, + 31.58 + ], + "锡山区": [ + 120.35, + 31.6 + ], + "惠山区": [ + 120.28, + 31.68 + ], + "滨湖区": [ + 120.27, + 31.57 + ], + "江阴市": [ + 120.27, + 31.9 + ], + "宜兴市": [ + 119.82, + 31.35 + ], + "徐州市": [ + 117.18, + 34.27 + ], + "云龙区": [ + 117.22, + 34.25 + ], + "九里区": [ + 117.13, + 34.3 + ], + "贾汪区": [ + 117.45, + 34.45 + ], + "泉山区": [ + 117.18, + 34.25 + ], + "丰县": [ + 116.6, + 34.7 + ], + "沛县": [ + 116.93, + 34.73 + ], + "铜山县": [ + 117.17, + 34.18 + ], + "睢宁县": [ + 117.95, + 33.9 + ], + "新沂市": [ + 118.35, + 34.38 + ], + "邳州市": [ + 117.95, + 34.32 + ], + "常州市": [ + 119.95, + 31.78 + ], + "天宁区": [ + 119.93, + 31.75 + ], + "钟楼区": [ + 119.93, + 31.78 + ], + "戚墅堰区": [ + 120.05, + 31.73 + ], + "新北区": [ + 119.97, + 31.83 + ], + "武进区": [ + 119.93, + 31.72 + ], + "溧阳市": [ + 119.48, + 31.42 + ], + "金坛市": [ + 119.57, + 31.75 + ], + "苏州市": [ + 120.58, + 31.3 + ], + "沧浪区": [ + 120.63, + 31.3 + ], + "平江区": [ + 120.63, + 31.32 + ], + "金阊区": [ + 120.6, + 31.32 + ], + "虎丘区": [ + 120.57, + 31.3 + ], + "吴中区": [ + 120.63, + 31.27 + ], + "相城区": [ + 120.63, + 31.37 + ], + "常熟市": [ + 120.75, + 31.65 + ], + "张家港市": [ + 120.55, + 31.87 + ], + "昆山市": [ + 120.98, + 31.38 + ], + "吴江市": [ + 120.63, + 31.17 + ], + "太仓市": [ + 121.1, + 31.45 + ], + "南通市": [ + 120.88, + 31.98 + ], + "崇川区": [ + 120.85, + 32 + ], + "港闸区": [ + 120.8, + 32.03 + ], + "海安县": [ + 120.45, + 32.55 + ], + "如东县": [ + 121.18, + 32.32 + ], + "启东市": [ + 121.65, + 31.82 + ], + "如皋市": [ + 120.57, + 32.4 + ], + "通州市": [ + 121.07, + 32.08 + ], + "海门市": [ + 121.17, + 31.9 + ], + "连云港市": [ + 119.22, + 34.6 + ], + "连云区": [ + 119.37, + 34.75 + ], + "新浦区": [ + 119.17, + 34.6 + ], + "赣榆县": [ + 119.12, + 34.83 + ], + "东海县": [ + 118.77, + 34.53 + ], + "灌云县": [ + 119.25, + 34.3 + ], + "灌南县": [ + 119.35, + 34.08 + ], + "淮安市": [ + 119.02, + 33.62 + ], + "清河区": [ + 119.02, + 33.6 + ], + "楚州区": [ + 119.13, + 33.5 + ], + "淮阴区": [ + 119.03, + 33.63 + ], + "清浦区": [ + 119.03, + 33.58 + ], + "涟水县": [ + 119.27, + 33.78 + ], + "洪泽县": [ + 118.83, + 33.3 + ], + "盱眙县": [ + 118.48, + 33 + ], + "金湖县": [ + 119.02, + 33.02 + ], + "盐城市": [ + 120.15, + 33.35 + ], + "亭湖区": [ + 120.13, + 33.4 + ], + "盐都区": [ + 120.15, + 33.33 + ], + "响水县": [ + 119.57, + 34.2 + ], + "滨海县": [ + 119.83, + 33.98 + ], + "阜宁县": [ + 119.8, + 33.78 + ], + "射阳县": [ + 120.25, + 33.78 + ], + "建湖县": [ + 119.8, + 33.47 + ], + "东台市": [ + 120.3, + 32.85 + ], + "大丰市": [ + 120.47, + 33.2 + ], + "扬州市": [ + 119.4, + 32.4 + ], + "广陵区": [ + 119.43, + 32.38 + ], + "邗江区": [ + 119.4, + 32.38 + ], + "维扬区": [ + 119.4, + 32.42 + ], + "宝应县": [ + 119.3, + 33.23 + ], + "仪征市": [ + 119.18, + 32.27 + ], + "高邮市": [ + 119.43, + 32.78 + ], + "江都市": [ + 119.55, + 32.43 + ], + "镇江市": [ + 119.45, + 32.2 + ], + "京口区": [ + 119.47, + 32.2 + ], + "润州区": [ + 119.4, + 32.2 + ], + "丹徒区": [ + 119.45, + 32.13 + ], + "丹阳市": [ + 119.57, + 32 + ], + "扬中市": [ + 119.82, + 32.23 + ], + "句容市": [ + 119.17, + 31.95 + ], + "泰州市": [ + 119.92, + 32.45 + ], + "兴化市": [ + 119.85, + 32.92 + ], + "靖江市": [ + 120.27, + 32.02 + ], + "泰兴市": [ + 120.02, + 32.17 + ], + "姜堰市": [ + 120.15, + 32.52 + ], + "宿迁市": [ + 118.28, + 33.97 + ], + "宿城区": [ + 118.25, + 33.97 + ], + "宿豫区": [ + 118.32, + 33.95 + ], + "沭阳县": [ + 118.77, + 34.13 + ], + "泗阳县": [ + 118.68, + 33.72 + ], + "泗洪县": [ + 118.22, + 33.47 + ], + "杭州市": [ + 120.15, + 30.28 + ], + "上城区": [ + 120.17, + 30.25 + ], + "下城区": [ + 120.17, + 30.28 + ], + "江干区": [ + 120.2, + 30.27 + ], + "拱墅区": [ + 120.13, + 30.32 + ], + "滨江区": [ + 120.2, + 30.2 + ], + "萧山区": [ + 120.27, + 30.17 + ], + "余杭区": [ + 120.3, + 30.42 + ], + "桐庐县": [ + 119.67, + 29.8 + ], + "淳安县": [ + 119.03, + 29.6 + ], + "建德市": [ + 119.28, + 29.48 + ], + "富阳市": [ + 119.95, + 30.05 + ], + "临安市": [ + 119.72, + 30.23 + ], + "宁波市": [ + 121.55, + 29.88 + ], + "海曙区": [ + 121.55, + 29.87 + ], + "江东区": [ + 121.57, + 29.87 + ], + "北仑区": [ + 121.85, + 29.93 + ], + "镇海区": [ + 121.72, + 29.95 + ], + "鄞州区": [ + 121.53, + 29.83 + ], + "象山县": [ + 121.87, + 29.48 + ], + "宁海县": [ + 121.43, + 29.28 + ], + "余姚市": [ + 121.15, + 30.03 + ], + "慈溪市": [ + 121.23, + 30.17 + ], + "奉化市": [ + 121.4, + 29.65 + ], + "温州市": [ + 120.7, + 28 + ], + "鹿城区": [ + 120.65, + 28.02 + ], + "龙湾区": [ + 120.82, + 27.93 + ], + "洞头县": [ + 121.15, + 27.83 + ], + "永嘉县": [ + 120.68, + 28.15 + ], + "平阳县": [ + 120.57, + 27.67 + ], + "苍南县": [ + 120.4, + 27.5 + ], + "文成县": [ + 120.08, + 27.78 + ], + "泰顺县": [ + 119.72, + 27.57 + ], + "瑞安市": [ + 120.63, + 27.78 + ], + "乐清市": [ + 120.95, + 28.13 + ], + "嘉兴市": [ + 120.75, + 30.75 + ], + "秀洲区": [ + 120.7, + 30.77 + ], + "嘉善县": [ + 120.92, + 30.85 + ], + "海盐县": [ + 120.95, + 30.53 + ], + "海宁市": [ + 120.68, + 30.53 + ], + "平湖市": [ + 121.02, + 30.7 + ], + "桐乡市": [ + 120.57, + 30.63 + ], + "湖州市": [ + 120.08, + 30.9 + ], + "吴兴区": [ + 120.12, + 30.87 + ], + "南浔区": [ + 120.43, + 30.88 + ], + "德清县": [ + 119.97, + 30.53 + ], + "长兴县": [ + 119.9, + 31.02 + ], + "安吉县": [ + 119.68, + 30.63 + ], + "绍兴市": [ + 120.57, + 30 + ], + "越城区": [ + 120.57, + 30 + ], + "绍兴县": [ + 120.47, + 30.08 + ], + "新昌县": [ + 120.9, + 29.5 + ], + "诸暨市": [ + 120.23, + 29.72 + ], + "上虞市": [ + 120.87, + 30.03 + ], + "嵊州市": [ + 120.82, + 29.58 + ], + "金华市": [ + 119.65, + 29.08 + ], + "婺城区": [ + 119.65, + 29.08 + ], + "金东区": [ + 119.7, + 29.08 + ], + "武义县": [ + 119.82, + 28.9 + ], + "浦江县": [ + 119.88, + 29.45 + ], + "磐安县": [ + 120.43, + 29.05 + ], + "兰溪市": [ + 119.45, + 29.22 + ], + "义乌市": [ + 120.07, + 29.3 + ], + "东阳市": [ + 120.23, + 29.28 + ], + "永康市": [ + 120.03, + 28.9 + ], + "衢州市": [ + 118.87, + 28.93 + ], + "柯城区": [ + 118.87, + 28.93 + ], + "衢江区": [ + 118.93, + 28.98 + ], + "常山县": [ + 118.52, + 28.9 + ], + "开化县": [ + 118.42, + 29.13 + ], + "龙游县": [ + 119.17, + 29.03 + ], + "江山市": [ + 118.62, + 28.75 + ], + "舟山市": [ + 122.2, + 30 + ], + "定海区": [ + 122.1, + 30.02 + ], + "普陀区": [ + 122.3, + 29.95 + ], + "岱山县": [ + 122.2, + 30.25 + ], + "嵊泗县": [ + 122.45, + 30.73 + ], + "台州市": [ + 121.43, + 28.68 + ], + "椒江区": [ + 121.43, + 28.68 + ], + "黄岩区": [ + 121.27, + 28.65 + ], + "路桥区": [ + 121.38, + 28.58 + ], + "玉环县": [ + 121.23, + 28.13 + ], + "三门县": [ + 121.38, + 29.12 + ], + "天台县": [ + 121.03, + 29.13 + ], + "仙居县": [ + 120.73, + 28.87 + ], + "温岭市": [ + 121.37, + 28.37 + ], + "临海市": [ + 121.12, + 28.85 + ], + "丽水市": [ + 119.92, + 28.45 + ], + "莲都区": [ + 119.92, + 28.45 + ], + "青田县": [ + 120.28, + 28.15 + ], + "缙云县": [ + 120.07, + 28.65 + ], + "遂昌县": [ + 119.27, + 28.6 + ], + "松阳县": [ + 119.48, + 28.45 + ], + "云和县": [ + 119.57, + 28.12 + ], + "庆元县": [ + 119.05, + 27.62 + ], + "景宁畲族自治县": [ + 119.63, + 27.98 + ], + "龙泉市": [ + 119.13, + 28.08 + ], + "合肥市": [ + 117.25, + 31.83 + ], + "瑶海区": [ + 117.3, + 31.87 + ], + "庐阳区": [ + 117.25, + 31.88 + ], + "蜀山区": [ + 117.27, + 31.85 + ], + "包河区": [ + 117.3, + 31.8 + ], + "长丰县": [ + 117.17, + 32.48 + ], + "肥东县": [ + 117.47, + 31.88 + ], + "肥西县": [ + 117.17, + 31.72 + ], + "芜湖市": [ + 118.38, + 31.33 + ], + "镜湖区": [ + 118.37, + 31.35 + ], + "鸠江区": [ + 118.38, + 31.37 + ], + "芜湖县": [ + 118.57, + 31.15 + ], + "繁昌县": [ + 118.2, + 31.08 + ], + "南陵县": [ + 118.33, + 30.92 + ], + "蚌埠市": [ + 117.38, + 32.92 + ], + "龙子湖区": [ + 117.38, + 32.95 + ], + "蚌山区": [ + 117.35, + 32.95 + ], + "禹会区": [ + 117.33, + 32.93 + ], + "淮上区": [ + 117.35, + 32.97 + ], + "怀远县": [ + 117.18, + 32.97 + ], + "五河县": [ + 117.88, + 33.15 + ], + "固镇县": [ + 117.32, + 33.32 + ], + "淮南市": [ + 117, + 32.63 + ], + "大通区": [ + 117.05, + 32.63 + ], + "田家庵区": [ + 117, + 32.67 + ], + "谢家集区": [ + 116.85, + 32.6 + ], + "八公山区": [ + 116.83, + 32.63 + ], + "潘集区": [ + 116.82, + 32.78 + ], + "凤台县": [ + 116.72, + 32.7 + ], + "马鞍山市": [ + 118.5, + 31.7 + ], + "金家庄区": [ + 118.48, + 31.73 + ], + "花山区": [ + 118.5, + 31.72 + ], + "雨山区": [ + 118.48, + 31.68 + ], + "当涂县": [ + 118.48, + 31.55 + ], + "淮北市": [ + 116.8, + 33.95 + ], + "杜集区": [ + 116.82, + 34 + ], + "相山区": [ + 116.8, + 33.95 + ], + "烈山区": [ + 116.8, + 33.9 + ], + "濉溪县": [ + 116.77, + 33.92 + ], + "铜陵市": [ + 117.82, + 30.93 + ], + "铜官山区": [ + 117.82, + 30.93 + ], + "狮子山区": [ + 117.85, + 30.95 + ], + "铜陵县": [ + 117.78, + 30.95 + ], + "安庆市": [ + 117.05, + 30.53 + ], + "迎江区": [ + 117.05, + 30.5 + ], + "大观区": [ + 117.03, + 30.52 + ], + "怀宁县": [ + 116.83, + 30.72 + ], + "枞阳县": [ + 117.2, + 30.7 + ], + "潜山县": [ + 116.57, + 30.63 + ], + "太湖县": [ + 116.27, + 30.43 + ], + "宿松县": [ + 116.12, + 30.15 + ], + "望江县": [ + 116.68, + 30.13 + ], + "岳西县": [ + 116.35, + 30.85 + ], + "桐城市": [ + 116.95, + 31.05 + ], + "黄山市": [ + 118.33, + 29.72 + ], + "屯溪区": [ + 118.33, + 29.72 + ], + "黄山区": [ + 118.13, + 30.3 + ], + "徽州区": [ + 118.33, + 29.82 + ], + "歙县": [ + 118.43, + 29.87 + ], + "休宁县": [ + 118.18, + 29.78 + ], + "黟县": [ + 117.93, + 29.93 + ], + "祁门县": [ + 117.72, + 29.87 + ], + "滁州市": [ + 118.32, + 32.3 + ], + "琅琊区": [ + 118.3, + 32.3 + ], + "南谯区": [ + 118.3, + 32.32 + ], + "来安县": [ + 118.43, + 32.45 + ], + "全椒县": [ + 118.27, + 32.1 + ], + "定远县": [ + 117.67, + 32.53 + ], + "凤阳县": [ + 117.57, + 32.87 + ], + "天长市": [ + 119, + 32.7 + ], + "明光市": [ + 117.98, + 32.78 + ], + "阜阳市": [ + 115.82, + 32.9 + ], + "颍州区": [ + 115.8, + 32.88 + ], + "颍东区": [ + 115.85, + 32.92 + ], + "颍泉区": [ + 115.8, + 32.93 + ], + "临泉县": [ + 115.25, + 33.07 + ], + "太和县": [ + 115.62, + 33.17 + ], + "阜南县": [ + 115.58, + 32.63 + ], + "颍上县": [ + 116.27, + 32.63 + ], + "宿州市": [ + 116.98, + 33.63 + ], + "埇桥区": [ + 116.97, + 33.63 + ], + "砀山县": [ + 116.35, + 34.42 + ], + "萧县": [ + 116.93, + 34.18 + ], + "灵璧县": [ + 117.55, + 33.55 + ], + "泗县": [ + 117.88, + 33.48 + ], + "巢湖市": [ + 117.87, + 31.6 + ], + "居巢区": [ + 117.85, + 31.6 + ], + "庐江县": [ + 117.28, + 31.25 + ], + "无为县": [ + 117.92, + 31.3 + ], + "含山县": [ + 118.1, + 31.72 + ], + "和县": [ + 118.37, + 31.72 + ], + "六安市": [ + 116.5, + 31.77 + ], + "金安区": [ + 116.5, + 31.77 + ], + "裕安区": [ + 116.48, + 31.77 + ], + "寿县": [ + 116.78, + 32.58 + ], + "霍邱县": [ + 116.27, + 32.33 + ], + "舒城县": [ + 116.93, + 31.47 + ], + "金寨县": [ + 115.92, + 31.72 + ], + "霍山县": [ + 116.33, + 31.4 + ], + "亳州市": [ + 115.78, + 33.85 + ], + "谯城区": [ + 115.77, + 33.88 + ], + "涡阳县": [ + 116.22, + 33.52 + ], + "蒙城县": [ + 116.57, + 33.27 + ], + "利辛县": [ + 116.2, + 33.15 + ], + "池州市": [ + 117.48, + 30.67 + ], + "贵池区": [ + 117.48, + 30.65 + ], + "东至县": [ + 117.02, + 30.1 + ], + "石台县": [ + 117.48, + 30.22 + ], + "青阳县": [ + 117.85, + 30.65 + ], + "宣城市": [ + 118.75, + 30.95 + ], + "宣州区": [ + 118.75, + 30.95 + ], + "郎溪县": [ + 119.17, + 31.13 + ], + "广德县": [ + 119.42, + 30.9 + ], + "泾县": [ + 118.4, + 30.7 + ], + "绩溪县": [ + 118.6, + 30.07 + ], + "旌德县": [ + 118.53, + 30.28 + ], + "宁国市": [ + 118.98, + 30.63 + ], + "福州市": [ + 119.3, + 26.08 + ], + "鼓楼区": [ + 119.3, + 26.08 + ], + "台江区": [ + 119.3, + 26.07 + ], + "仓山区": [ + 119.32, + 26.05 + ], + "马尾区": [ + 119.45, + 26 + ], + "晋安区": [ + 119.32, + 26.08 + ], + "闽侯县": [ + 119.13, + 26.15 + ], + "连江县": [ + 119.53, + 26.2 + ], + "罗源县": [ + 119.55, + 26.48 + ], + "闽清县": [ + 118.85, + 26.22 + ], + "永泰县": [ + 118.93, + 25.87 + ], + "平潭县": [ + 119.78, + 25.52 + ], + "福清市": [ + 119.38, + 25.72 + ], + "长乐市": [ + 119.52, + 25.97 + ], + "厦门市": [ + 118.08, + 24.48 + ], + "思明区": [ + 118.08, + 24.45 + ], + "海沧区": [ + 117.98, + 24.47 + ], + "湖里区": [ + 118.08, + 24.52 + ], + "集美区": [ + 118.1, + 24.57 + ], + "同安区": [ + 118.15, + 24.73 + ], + "翔安区": [ + 118.23, + 24.62 + ], + "莆田市": [ + 119, + 25.43 + ], + "城厢区": [ + 119, + 25.43 + ], + "涵江区": [ + 119.1, + 25.45 + ], + "荔城区": [ + 119.02, + 25.43 + ], + "秀屿区": [ + 119.08, + 25.32 + ], + "仙游县": [ + 118.68, + 25.37 + ], + "三明市": [ + 117.62, + 26.27 + ], + "梅列区": [ + 117.63, + 26.27 + ], + "三元区": [ + 117.6, + 26.23 + ], + "明溪县": [ + 117.2, + 26.37 + ], + "清流县": [ + 116.82, + 26.18 + ], + "宁化县": [ + 116.65, + 26.27 + ], + "大田县": [ + 117.85, + 25.7 + ], + "尤溪县": [ + 118.18, + 26.17 + ], + "沙县": [ + 117.78, + 26.4 + ], + "将乐县": [ + 117.47, + 26.73 + ], + "泰宁县": [ + 117.17, + 26.9 + ], + "建宁县": [ + 116.83, + 26.83 + ], + "永安市": [ + 117.37, + 25.98 + ], + "泉州市": [ + 118.67, + 24.88 + ], + "鲤城区": [ + 118.6, + 24.92 + ], + "丰泽区": [ + 118.6, + 24.92 + ], + "洛江区": [ + 118.67, + 24.95 + ], + "泉港区": [ + 118.88, + 25.12 + ], + "惠安县": [ + 118.8, + 25.03 + ], + "安溪县": [ + 118.18, + 25.07 + ], + "永春县": [ + 118.3, + 25.32 + ], + "德化县": [ + 118.23, + 25.5 + ], + "金门县": [ + 118.32, + 24.43 + ], + "石狮市": [ + 118.65, + 24.73 + ], + "晋江市": [ + 118.58, + 24.82 + ], + "南安市": [ + 118.38, + 24.97 + ], + "漳州市": [ + 117.65, + 24.52 + ], + "芗城区": [ + 117.65, + 24.52 + ], + "龙文区": [ + 117.72, + 24.52 + ], + "云霄县": [ + 117.33, + 23.95 + ], + "漳浦县": [ + 117.62, + 24.13 + ], + "诏安县": [ + 117.18, + 23.72 + ], + "长泰县": [ + 117.75, + 24.62 + ], + "东山县": [ + 117.43, + 23.7 + ], + "南靖县": [ + 117.37, + 24.52 + ], + "平和县": [ + 117.3, + 24.37 + ], + "华安县": [ + 117.53, + 25.02 + ], + "龙海市": [ + 117.82, + 24.45 + ], + "南平市": [ + 118.17, + 26.65 + ], + "延平区": [ + 118.17, + 26.65 + ], + "顺昌县": [ + 117.8, + 26.8 + ], + "浦城县": [ + 118.53, + 27.92 + ], + "光泽县": [ + 117.33, + 27.55 + ], + "松溪县": [ + 118.78, + 27.53 + ], + "政和县": [ + 118.85, + 27.37 + ], + "邵武市": [ + 117.48, + 27.37 + ], + "武夷山市": [ + 118.03, + 27.77 + ], + "建瓯市": [ + 118.32, + 27.03 + ], + "建阳市": [ + 118.12, + 27.33 + ], + "龙岩市": [ + 117.03, + 25.1 + ], + "新罗区": [ + 117.03, + 25.1 + ], + "长汀县": [ + 116.35, + 25.83 + ], + "永定县": [ + 116.73, + 24.72 + ], + "上杭县": [ + 116.42, + 25.05 + ], + "武平县": [ + 116.1, + 25.1 + ], + "连城县": [ + 116.75, + 25.72 + ], + "漳平市": [ + 117.42, + 25.3 + ], + "宁德市": [ + 119.52, + 26.67 + ], + "蕉城区": [ + 119.52, + 26.67 + ], + "霞浦县": [ + 120, + 26.88 + ], + "古田县": [ + 118.75, + 26.58 + ], + "屏南县": [ + 118.98, + 26.92 + ], + "寿宁县": [ + 119.5, + 27.47 + ], + "周宁县": [ + 119.33, + 27.12 + ], + "柘荣县": [ + 119.9, + 27.23 + ], + "福安市": [ + 119.65, + 27.08 + ], + "福鼎市": [ + 120.22, + 27.33 + ], + "南昌市": [ + 115.85, + 28.68 + ], + "东湖区": [ + 115.9, + 28.68 + ], + "西湖区": [ + 115.87, + 28.67 + ], + "青云谱区": [ + 115.92, + 28.63 + ], + "湾里区": [ + 115.73, + 28.72 + ], + "青山湖区": [ + 115.95, + 28.68 + ], + "南昌县": [ + 115.93, + 28.55 + ], + "新建县": [ + 115.82, + 28.7 + ], + "安义县": [ + 115.55, + 28.85 + ], + "进贤县": [ + 116.27, + 28.37 + ], + "景德镇市": [ + 117.17, + 29.27 + ], + "昌江区": [ + 117.17, + 29.27 + ], + "珠山区": [ + 117.2, + 29.3 + ], + "浮梁县": [ + 117.25, + 29.37 + ], + "乐平市": [ + 117.12, + 28.97 + ], + "萍乡市": [ + 113.85, + 27.63 + ], + "安源区": [ + 113.87, + 27.65 + ], + "湘东区": [ + 113.73, + 27.65 + ], + "莲花县": [ + 113.95, + 27.13 + ], + "上栗县": [ + 113.8, + 27.88 + ], + "芦溪县": [ + 114.03, + 27.63 + ], + "九江市": [ + 116, + 29.7 + ], + "庐山区": [ + 115.98, + 29.68 + ], + "浔阳区": [ + 115.98, + 29.73 + ], + "九江县": [ + 115.88, + 29.62 + ], + "武宁县": [ + 115.1, + 29.27 + ], + "修水县": [ + 114.57, + 29.03 + ], + "永修县": [ + 115.8, + 29.03 + ], + "德安县": [ + 115.77, + 29.33 + ], + "星子县": [ + 116.03, + 29.45 + ], + "都昌县": [ + 116.18, + 29.27 + ], + "湖口县": [ + 116.22, + 29.73 + ], + "彭泽县": [ + 116.55, + 29.9 + ], + "瑞昌市": [ + 115.67, + 29.68 + ], + "新余市": [ + 114.92, + 27.82 + ], + "渝水区": [ + 114.93, + 27.8 + ], + "分宜县": [ + 114.67, + 27.82 + ], + "鹰潭市": [ + 117.07, + 28.27 + ], + "月湖区": [ + 117.05, + 28.23 + ], + "余江县": [ + 116.82, + 28.2 + ], + "贵溪市": [ + 117.22, + 28.28 + ], + "赣州市": [ + 114.93, + 25.83 + ], + "章贡区": [ + 114.93, + 25.87 + ], + "赣县": [ + 115, + 25.87 + ], + "信丰县": [ + 114.93, + 25.38 + ], + "大余县": [ + 114.35, + 25.4 + ], + "上犹县": [ + 114.53, + 25.8 + ], + "崇义县": [ + 114.3, + 25.7 + ], + "安远县": [ + 115.38, + 25.13 + ], + "龙南县": [ + 114.78, + 24.92 + ], + "定南县": [ + 115.03, + 24.78 + ], + "全南县": [ + 114.52, + 24.75 + ], + "宁都县": [ + 116.02, + 26.48 + ], + "于都县": [ + 115.42, + 25.95 + ], + "兴国县": [ + 115.35, + 26.33 + ], + "会昌县": [ + 115.78, + 25.6 + ], + "寻乌县": [ + 115.65, + 24.95 + ], + "石城县": [ + 116.33, + 26.33 + ], + "瑞金市": [ + 116.03, + 25.88 + ], + "南康市": [ + 114.75, + 25.65 + ], + "吉安市": [ + 114.98, + 27.12 + ], + "吉州区": [ + 114.98, + 27.12 + ], + "青原区": [ + 115, + 27.1 + ], + "吉安县": [ + 114.9, + 27.05 + ], + "吉水县": [ + 115.13, + 27.22 + ], + "峡江县": [ + 115.33, + 27.62 + ], + "新干县": [ + 115.4, + 27.77 + ], + "永丰县": [ + 115.43, + 27.32 + ], + "泰和县": [ + 114.88, + 26.8 + ], + "遂川县": [ + 114.52, + 26.33 + ], + "万安县": [ + 114.78, + 26.47 + ], + "安福县": [ + 114.62, + 27.38 + ], + "永新县": [ + 114.23, + 26.95 + ], + "井冈山市": [ + 114.27, + 26.72 + ], + "宜春市": [ + 114.38, + 27.8 + ], + "袁州区": [ + 114.38, + 27.8 + ], + "奉新县": [ + 115.38, + 28.7 + ], + "万载县": [ + 114.43, + 28.12 + ], + "上高县": [ + 114.92, + 28.23 + ], + "宜丰县": [ + 114.78, + 28.38 + ], + "靖安县": [ + 115.35, + 28.87 + ], + "铜鼓县": [ + 114.37, + 28.53 + ], + "丰城市": [ + 115.78, + 28.2 + ], + "樟树市": [ + 115.53, + 28.07 + ], + "高安市": [ + 115.37, + 28.42 + ], + "抚州市": [ + 116.35, + 28 + ], + "临川区": [ + 116.35, + 27.98 + ], + "南城县": [ + 116.63, + 27.55 + ], + "黎川县": [ + 116.92, + 27.3 + ], + "南丰县": [ + 116.53, + 27.22 + ], + "崇仁县": [ + 116.05, + 27.77 + ], + "乐安县": [ + 115.83, + 27.43 + ], + "宜黄县": [ + 116.22, + 27.55 + ], + "金溪县": [ + 116.77, + 27.92 + ], + "资溪县": [ + 117.07, + 27.7 + ], + "东乡县": [ + 116.62, + 28.23 + ], + "广昌县": [ + 116.32, + 26.83 + ], + "上饶市": [ + 117.97, + 28.45 + ], + "信州区": [ + 117.95, + 28.43 + ], + "上饶县": [ + 117.92, + 28.43 + ], + "广丰县": [ + 118.18, + 28.43 + ], + "玉山县": [ + 118.25, + 28.68 + ], + "铅山县": [ + 117.7, + 28.32 + ], + "横峰县": [ + 117.6, + 28.42 + ], + "弋阳县": [ + 117.43, + 28.4 + ], + "余干县": [ + 116.68, + 28.7 + ], + "鄱阳县": [ + 116.67, + 29 + ], + "万年县": [ + 117.07, + 28.7 + ], + "婺源县": [ + 117.85, + 29.25 + ], + "德兴市": [ + 117.57, + 28.95 + ], + "济南市": [ + 116.98, + 36.67 + ], + "历下区": [ + 117.08, + 36.67 + ], + "市中区": [ + 117.57, + 34.87 + ], + "槐荫区": [ + 116.93, + 36.65 + ], + "天桥区": [ + 116.98, + 36.68 + ], + "历城区": [ + 117.07, + 36.68 + ], + "长清区": [ + 116.73, + 36.55 + ], + "平阴县": [ + 116.45, + 36.28 + ], + "济阳县": [ + 117.22, + 36.98 + ], + "商河县": [ + 117.15, + 37.32 + ], + "章丘市": [ + 117.53, + 36.72 + ], + "青岛市": [ + 120.38, + 36.07 + ], + "市南区": [ + 120.38, + 36.07 + ], + "市北区": [ + 120.38, + 36.08 + ], + "四方区": [ + 120.35, + 36.1 + ], + "黄岛区": [ + 120.18, + 35.97 + ], + "崂山区": [ + 120.47, + 36.1 + ], + "李沧区": [ + 120.43, + 36.15 + ], + "城阳区": [ + 120.37, + 36.3 + ], + "胶州市": [ + 120.03, + 36.27 + ], + "即墨市": [ + 120.45, + 36.38 + ], + "平度市": [ + 119.95, + 36.78 + ], + "胶南市": [ + 120.03, + 35.87 + ], + "莱西市": [ + 120.5, + 36.87 + ], + "淄博市": [ + 118.05, + 36.82 + ], + "张店区": [ + 118.03, + 36.82 + ], + "博山区": [ + 117.85, + 36.5 + ], + "临淄区": [ + 118.3, + 36.82 + ], + "周村区": [ + 117.87, + 36.8 + ], + "桓台县": [ + 118.08, + 36.97 + ], + "高青县": [ + 117.82, + 37.17 + ], + "沂源县": [ + 118.17, + 36.18 + ], + "枣庄市": [ + 117.32, + 34.82 + ], + "薛城区": [ + 117.25, + 34.8 + ], + "峄城区": [ + 117.58, + 34.77 + ], + "台儿庄区": [ + 117.73, + 34.57 + ], + "山亭区": [ + 117.45, + 35.08 + ], + "滕州市": [ + 117.15, + 35.08 + ], + "东营市": [ + 118.67, + 37.43 + ], + "东营区": [ + 118.5, + 37.47 + ], + "河口区": [ + 118.53, + 37.88 + ], + "垦利县": [ + 118.55, + 37.58 + ], + "利津县": [ + 118.25, + 37.48 + ], + "广饶县": [ + 118.4, + 37.07 + ], + "烟台市": [ + 121.43, + 37.45 + ], + "芝罘区": [ + 121.38, + 37.53 + ], + "福山区": [ + 121.25, + 37.5 + ], + "牟平区": [ + 121.6, + 37.38 + ], + "莱山区": [ + 121.43, + 37.5 + ], + "长岛县": [ + 120.73, + 37.92 + ], + "龙口市": [ + 120.52, + 37.65 + ], + "莱阳市": [ + 120.7, + 36.98 + ], + "莱州市": [ + 119.93, + 37.18 + ], + "蓬莱市": [ + 120.75, + 37.82 + ], + "招远市": [ + 120.4, + 37.37 + ], + "栖霞市": [ + 120.83, + 37.3 + ], + "海阳市": [ + 121.15, + 36.78 + ], + "潍坊市": [ + 119.15, + 36.7 + ], + "潍城区": [ + 119.1, + 36.72 + ], + "寒亭区": [ + 119.22, + 36.77 + ], + "坊子区": [ + 119.17, + 36.67 + ], + "奎文区": [ + 119.12, + 36.72 + ], + "临朐县": [ + 118.53, + 36.52 + ], + "昌乐县": [ + 118.82, + 36.7 + ], + "青州市": [ + 118.47, + 36.68 + ], + "诸城市": [ + 119.4, + 36 + ], + "寿光市": [ + 118.73, + 36.88 + ], + "安丘市": [ + 119.2, + 36.43 + ], + "高密市": [ + 119.75, + 36.38 + ], + "昌邑市": [ + 119.4, + 36.87 + ], + "济宁市": [ + 116.58, + 35.42 + ], + "任城区": [ + 116.58, + 35.42 + ], + "微山县": [ + 117.13, + 34.82 + ], + "鱼台县": [ + 116.65, + 35 + ], + "金乡县": [ + 116.3, + 35.07 + ], + "嘉祥县": [ + 116.33, + 35.42 + ], + "汶上县": [ + 116.48, + 35.73 + ], + "泗水县": [ + 117.27, + 35.67 + ], + "梁山县": [ + 116.08, + 35.8 + ], + "曲阜市": [ + 116.98, + 35.58 + ], + "兖州市": [ + 116.83, + 35.55 + ], + "邹城市": [ + 116.97, + 35.4 + ], + "泰安市": [ + 117.08, + 36.2 + ], + "泰山区": [ + 117.13, + 36.18 + ], + "岱岳区": [ + 117, + 36.18 + ], + "宁阳县": [ + 116.8, + 35.77 + ], + "东平县": [ + 116.47, + 35.93 + ], + "新泰市": [ + 117.77, + 35.92 + ], + "肥城市": [ + 116.77, + 36.18 + ], + "威海市": [ + 122.12, + 37.52 + ], + "环翠区": [ + 122.12, + 37.5 + ], + "文登市": [ + 122.05, + 37.2 + ], + "荣成市": [ + 122.42, + 37.17 + ], + "乳山市": [ + 121.53, + 36.92 + ], + "日照市": [ + 119.52, + 35.42 + ], + "东港区": [ + 119.45, + 35.42 + ], + "岚山区": [ + 119.33, + 35.1 + ], + "五莲县": [ + 119.2, + 35.75 + ], + "莒县": [ + 118.83, + 35.58 + ], + "莱芜市": [ + 117.67, + 36.22 + ], + "莱城区": [ + 117.65, + 36.2 + ], + "钢城区": [ + 117.8, + 36.07 + ], + "临沂市": [ + 118.35, + 35.05 + ], + "兰山区": [ + 118.33, + 35.07 + ], + "罗庄区": [ + 118.28, + 34.98 + ], + "河东区": [ + 118.4, + 35.08 + ], + "沂南县": [ + 118.47, + 35.55 + ], + "郯城县": [ + 118.35, + 34.62 + ], + "沂水县": [ + 118.62, + 35.78 + ], + "苍山县": [ + 118.05, + 34.85 + ], + "费县": [ + 117.97, + 35.27 + ], + "平邑县": [ + 117.63, + 35.5 + ], + "莒南县": [ + 118.83, + 35.18 + ], + "蒙阴县": [ + 117.93, + 35.72 + ], + "临沭县": [ + 118.65, + 34.92 + ], + "德州市": [ + 116.3, + 37.45 + ], + "德城区": [ + 116.3, + 37.45 + ], + "陵县": [ + 116.57, + 37.33 + ], + "宁津县": [ + 116.78, + 37.65 + ], + "庆云县": [ + 117.38, + 37.78 + ], + "临邑县": [ + 116.87, + 37.18 + ], + "齐河县": [ + 116.75, + 36.8 + ], + "平原县": [ + 116.43, + 37.17 + ], + "夏津县": [ + 116, + 36.95 + ], + "武城县": [ + 116.07, + 37.22 + ], + "乐陵市": [ + 117.23, + 37.73 + ], + "禹城市": [ + 116.63, + 36.93 + ], + "聊城市": [ + 115.98, + 36.45 + ], + "东昌府区": [ + 115.98, + 36.45 + ], + "阳谷县": [ + 115.78, + 36.12 + ], + "莘县": [ + 115.67, + 36.23 + ], + "茌平县": [ + 116.25, + 36.58 + ], + "东阿县": [ + 116.25, + 36.33 + ], + "冠县": [ + 115.43, + 36.48 + ], + "高唐县": [ + 116.23, + 36.87 + ], + "临清市": [ + 115.7, + 36.85 + ], + "滨州市": [ + 117.97, + 37.38 + ], + "滨城区": [ + 118, + 37.38 + ], + "惠民县": [ + 117.5, + 37.48 + ], + "阳信县": [ + 117.58, + 37.63 + ], + "无棣县": [ + 117.6, + 37.73 + ], + "沾化县": [ + 118.13, + 37.7 + ], + "博兴县": [ + 118.13, + 37.15 + ], + "邹平县": [ + 117.73, + 36.88 + ], + "牡丹区": [ + 115.43, + 35.25 + ], + "曹县": [ + 115.53, + 34.83 + ], + "单县": [ + 116.08, + 34.8 + ], + "成武县": [ + 115.88, + 34.95 + ], + "巨野县": [ + 116.08, + 35.4 + ], + "郓城县": [ + 115.93, + 35.6 + ], + "鄄城县": [ + 115.5, + 35.57 + ], + "定陶县": [ + 115.57, + 35.07 + ], + "东明县": [ + 115.08, + 35.28 + ], + "郑州市": [ + 113.62, + 34.75 + ], + "中原区": [ + 113.6, + 34.75 + ], + "二七区": [ + 113.65, + 34.73 + ], + "管城回族区": [ + 113.67, + 34.75 + ], + "金水区": [ + 113.65, + 34.78 + ], + "上街区": [ + 113.28, + 34.82 + ], + "惠济区": [ + 113.6, + 34.87 + ], + "中牟县": [ + 113.97, + 34.72 + ], + "巩义市": [ + 112.98, + 34.77 + ], + "荥阳市": [ + 113.4, + 34.78 + ], + "新密市": [ + 113.38, + 34.53 + ], + "新郑市": [ + 113.73, + 34.4 + ], + "登封市": [ + 113.03, + 34.47 + ], + "开封市": [ + 114.3, + 34.8 + ], + "龙亭区": [ + 114.35, + 34.8 + ], + "顺河回族区": [ + 114.35, + 34.8 + ], + "杞县": [ + 114.78, + 34.55 + ], + "通许县": [ + 114.47, + 34.48 + ], + "尉氏县": [ + 114.18, + 34.42 + ], + "开封县": [ + 114.43, + 34.77 + ], + "兰考县": [ + 114.82, + 34.82 + ], + "洛阳市": [ + 112.45, + 34.62 + ], + "老城区": [ + 112.47, + 34.68 + ], + "西工区": [ + 112.43, + 34.67 + ], + "涧西区": [ + 112.4, + 34.67 + ], + "吉利区": [ + 112.58, + 34.9 + ], + "洛龙区": [ + 112.45, + 34.62 + ], + "孟津县": [ + 112.43, + 34.83 + ], + "新安县": [ + 112.15, + 34.72 + ], + "栾川县": [ + 111.62, + 33.78 + ], + "嵩县": [ + 112.1, + 34.15 + ], + "汝阳县": [ + 112.47, + 34.15 + ], + "宜阳县": [ + 112.17, + 34.52 + ], + "洛宁县": [ + 111.65, + 34.38 + ], + "伊川县": [ + 112.42, + 34.42 + ], + "偃师市": [ + 112.78, + 34.73 + ], + "平顶山市": [ + 113.18, + 33.77 + ], + "新华区": [ + 113.3, + 33.73 + ], + "卫东区": [ + 113.33, + 33.73 + ], + "石龙区": [ + 112.88, + 33.9 + ], + "湛河区": [ + 113.28, + 33.73 + ], + "宝丰县": [ + 113.07, + 33.88 + ], + "叶县": [ + 113.35, + 33.62 + ], + "鲁山县": [ + 112.9, + 33.73 + ], + "郏县": [ + 113.22, + 33.97 + ], + "舞钢市": [ + 113.52, + 33.3 + ], + "汝州市": [ + 112.83, + 34.17 + ], + "安阳市": [ + 114.38, + 36.1 + ], + "文峰区": [ + 114.35, + 36.08 + ], + "北关区": [ + 114.35, + 36.12 + ], + "殷都区": [ + 114.3, + 36.12 + ], + "龙安区": [ + 114.32, + 36.1 + ], + "安阳县": [ + 114.35, + 36.1 + ], + "汤阴县": [ + 114.35, + 35.92 + ], + "滑县": [ + 114.52, + 35.58 + ], + "内黄县": [ + 114.9, + 35.95 + ], + "林州市": [ + 113.82, + 36.07 + ], + "鹤壁市": [ + 114.28, + 35.75 + ], + "鹤山区": [ + 114.15, + 35.95 + ], + "山城区": [ + 114.18, + 35.9 + ], + "淇滨区": [ + 114.3, + 35.73 + ], + "浚县": [ + 114.55, + 35.67 + ], + "淇县": [ + 114.2, + 35.6 + ], + "新乡市": [ + 113.9, + 35.3 + ], + "红旗区": [ + 113.87, + 35.3 + ], + "卫滨区": [ + 113.85, + 35.3 + ], + "凤泉区": [ + 113.92, + 35.38 + ], + "牧野区": [ + 113.9, + 35.32 + ], + "新乡县": [ + 113.8, + 35.2 + ], + "获嘉县": [ + 113.65, + 35.27 + ], + "原阳县": [ + 113.97, + 35.05 + ], + "延津县": [ + 114.2, + 35.15 + ], + "封丘县": [ + 114.42, + 35.05 + ], + "长垣县": [ + 114.68, + 35.2 + ], + "卫辉市": [ + 114.07, + 35.4 + ], + "辉县市": [ + 113.8, + 35.47 + ], + "焦作市": [ + 113.25, + 35.22 + ], + "解放区": [ + 113.22, + 35.25 + ], + "中站区": [ + 113.17, + 35.23 + ], + "马村区": [ + 113.32, + 35.27 + ], + "山阳区": [ + 113.25, + 35.22 + ], + "修武县": [ + 113.43, + 35.23 + ], + "博爱县": [ + 113.07, + 35.17 + ], + "武陟县": [ + 113.38, + 35.1 + ], + "温县": [ + 113.08, + 34.93 + ], + "济源市": [ + 112.58, + 35.07 + ], + "沁阳市": [ + 112.93, + 35.08 + ], + "孟州市": [ + 112.78, + 34.9 + ], + "濮阳市": [ + 115.03, + 35.77 + ], + "华龙区": [ + 115.07, + 35.78 + ], + "清丰县": [ + 115.12, + 35.9 + ], + "南乐县": [ + 115.2, + 36.08 + ], + "范县": [ + 115.5, + 35.87 + ], + "台前县": [ + 115.85, + 36 + ], + "濮阳县": [ + 115.02, + 35.7 + ], + "许昌市": [ + 113.85, + 34.03 + ], + "魏都区": [ + 113.82, + 34.03 + ], + "许昌县": [ + 113.83, + 34 + ], + "鄢陵县": [ + 114.2, + 34.1 + ], + "襄城县": [ + 113.48, + 33.85 + ], + "禹州市": [ + 113.47, + 34.17 + ], + "长葛市": [ + 113.77, + 34.22 + ], + "漯河市": [ + 114.02, + 33.58 + ], + "郾城区": [ + 114, + 33.58 + ], + "召陵区": [ + 114.07, + 33.57 + ], + "舞阳县": [ + 113.6, + 33.43 + ], + "临颍县": [ + 113.93, + 33.82 + ], + "三门峡市": [ + 111.2, + 34.78 + ], + "湖滨区": [ + 111.2, + 34.78 + ], + "渑池县": [ + 111.75, + 34.77 + ], + "陕县": [ + 111.08, + 34.7 + ], + "卢氏县": [ + 111.05, + 34.05 + ], + "义马市": [ + 111.87, + 34.75 + ], + "灵宝市": [ + 110.87, + 34.52 + ], + "南阳市": [ + 112.52, + 33 + ], + "宛城区": [ + 112.55, + 33.02 + ], + "卧龙区": [ + 112.53, + 32.98 + ], + "南召县": [ + 112.43, + 33.5 + ], + "方城县": [ + 113, + 33.27 + ], + "西峡县": [ + 111.48, + 33.28 + ], + "镇平县": [ + 112.23, + 33.03 + ], + "内乡县": [ + 111.85, + 33.05 + ], + "淅川县": [ + 111.48, + 33.13 + ], + "社旗县": [ + 112.93, + 33.05 + ], + "唐河县": [ + 112.83, + 32.7 + ], + "新野县": [ + 112.35, + 32.52 + ], + "桐柏县": [ + 113.4, + 32.37 + ], + "邓州市": [ + 112.08, + 32.68 + ], + "商丘市": [ + 115.65, + 34.45 + ], + "梁园区": [ + 115.63, + 34.45 + ], + "睢阳区": [ + 115.63, + 34.38 + ], + "民权县": [ + 115.13, + 34.65 + ], + "睢县": [ + 115.07, + 34.45 + ], + "宁陵县": [ + 115.32, + 34.45 + ], + "柘城县": [ + 115.3, + 34.07 + ], + "虞城县": [ + 115.85, + 34.4 + ], + "夏邑县": [ + 116.13, + 34.23 + ], + "永城市": [ + 116.43, + 33.92 + ], + "信阳市": [ + 114.07, + 32.13 + ], + "浉河区": [ + 114.05, + 32.12 + ], + "平桥区": [ + 114.12, + 32.1 + ], + "罗山县": [ + 114.53, + 32.2 + ], + "光山县": [ + 114.9, + 32.02 + ], + "新县": [ + 114.87, + 31.63 + ], + "商城县": [ + 115.4, + 31.8 + ], + "固始县": [ + 115.68, + 32.18 + ], + "潢川县": [ + 115.03, + 32.13 + ], + "淮滨县": [ + 115.4, + 32.43 + ], + "息县": [ + 114.73, + 32.35 + ], + "周口市": [ + 114.65, + 33.62 + ], + "扶沟县": [ + 114.38, + 34.07 + ], + "西华县": [ + 114.53, + 33.8 + ], + "商水县": [ + 114.6, + 33.53 + ], + "沈丘县": [ + 115.07, + 33.4 + ], + "郸城县": [ + 115.2, + 33.65 + ], + "淮阳县": [ + 114.88, + 33.73 + ], + "太康县": [ + 114.85, + 34.07 + ], + "鹿邑县": [ + 115.48, + 33.87 + ], + "项城市": [ + 114.9, + 33.45 + ], + "驻马店市": [ + 114.02, + 32.98 + ], + "驿城区": [ + 114.05, + 32.97 + ], + "西平县": [ + 114.02, + 33.38 + ], + "上蔡县": [ + 114.27, + 33.27 + ], + "平舆县": [ + 114.63, + 32.97 + ], + "正阳县": [ + 114.38, + 32.6 + ], + "确山县": [ + 114.02, + 32.8 + ], + "泌阳县": [ + 113.32, + 32.72 + ], + "汝南县": [ + 114.35, + 33 + ], + "遂平县": [ + 114, + 33.15 + ], + "新蔡县": [ + 114.98, + 32.75 + ], + "武汉市": [ + 114.3, + 30.6 + ], + "江岸区": [ + 114.3, + 30.6 + ], + "江汉区": [ + 114.27, + 30.6 + ], + "硚口区": [ + 114.27, + 30.57 + ], + "汉阳区": [ + 114.27, + 30.55 + ], + "武昌区": [ + 114.3, + 30.57 + ], + "青山区": [ + 114.38, + 30.63 + ], + "洪山区": [ + 114.33, + 30.5 + ], + "东西湖区": [ + 114.13, + 30.62 + ], + "汉南区": [ + 114.08, + 30.32 + ], + "蔡甸区": [ + 114.03, + 30.58 + ], + "江夏区": [ + 114.32, + 30.35 + ], + "黄陂区": [ + 114.37, + 30.87 + ], + "新洲区": [ + 114.8, + 30.85 + ], + "黄石市": [ + 115.03, + 30.2 + ], + "黄石港区": [ + 115.07, + 30.23 + ], + "西塞山区": [ + 115.12, + 30.2 + ], + "下陆区": [ + 114.97, + 30.18 + ], + "铁山区": [ + 114.9, + 30.2 + ], + "阳新县": [ + 115.2, + 29.85 + ], + "大冶市": [ + 114.97, + 30.1 + ], + "十堰市": [ + 110.78, + 32.65 + ], + "茅箭区": [ + 110.82, + 32.6 + ], + "张湾区": [ + 110.78, + 32.65 + ], + "郧县": [ + 110.82, + 32.83 + ], + "郧西县": [ + 110.42, + 33 + ], + "竹山县": [ + 110.23, + 32.23 + ], + "竹溪县": [ + 109.72, + 32.32 + ], + "房县": [ + 110.73, + 32.07 + ], + "丹江口市": [ + 111.52, + 32.55 + ], + "宜昌市": [ + 111.28, + 30.7 + ], + "西陵区": [ + 111.27, + 30.7 + ], + "伍家岗区": [ + 111.35, + 30.65 + ], + "点军区": [ + 111.27, + 30.7 + ], + "猇亭区": [ + 111.42, + 30.53 + ], + "夷陵区": [ + 111.32, + 30.77 + ], + "远安县": [ + 111.63, + 31.07 + ], + "兴山县": [ + 110.75, + 31.35 + ], + "秭归县": [ + 110.98, + 30.83 + ], + "长阳土家族自治县": [ + 111.18, + 30.47 + ], + "五峰土家族自治县": [ + 110.67, + 30.2 + ], + "宜都市": [ + 111.45, + 30.4 + ], + "当阳市": [ + 111.78, + 30.82 + ], + "枝江市": [ + 111.77, + 30.43 + ], + "襄樊市": [ + 112.15, + 32.02 + ], + "襄城区": [ + 112.15, + 32.02 + ], + "樊城区": [ + 112.13, + 32.03 + ], + "襄阳区": [ + 112.2, + 32.08 + ], + "南漳县": [ + 111.83, + 31.78 + ], + "谷城县": [ + 111.65, + 32.27 + ], + "保康县": [ + 111.25, + 31.88 + ], + "老河口市": [ + 111.67, + 32.38 + ], + "枣阳市": [ + 112.75, + 32.13 + ], + "宜城市": [ + 112.25, + 31.72 + ], + "鄂州市": [ + 114.88, + 30.4 + ], + "梁子湖区": [ + 114.67, + 30.08 + ], + "华容区": [ + 114.73, + 30.53 + ], + "鄂城区": [ + 114.88, + 30.4 + ], + "荆门市": [ + 112.2, + 31.03 + ], + "东宝区": [ + 112.2, + 31.05 + ], + "掇刀区": [ + 112.2, + 30.98 + ], + "京山县": [ + 113.1, + 31.02 + ], + "沙洋县": [ + 112.58, + 30.7 + ], + "钟祥市": [ + 112.58, + 31.17 + ], + "孝感市": [ + 113.92, + 30.93 + ], + "孝南区": [ + 113.92, + 30.92 + ], + "孝昌县": [ + 113.97, + 31.25 + ], + "大悟县": [ + 114.12, + 31.57 + ], + "云梦县": [ + 113.75, + 31.02 + ], + "应城市": [ + 113.57, + 30.95 + ], + "安陆市": [ + 113.68, + 31.27 + ], + "汉川市": [ + 113.83, + 30.65 + ], + "荆州市": [ + 112.23, + 30.33 + ], + "沙市区": [ + 112.25, + 30.32 + ], + "荆州区": [ + 112.18, + 30.35 + ], + "公安县": [ + 112.23, + 30.07 + ], + "监利县": [ + 112.88, + 29.82 + ], + "江陵县": [ + 112.42, + 30.03 + ], + "石首市": [ + 112.4, + 29.73 + ], + "洪湖市": [ + 113.45, + 29.8 + ], + "松滋市": [ + 111.77, + 30.18 + ], + "黄冈市": [ + 114.87, + 30.45 + ], + "黄州区": [ + 114.88, + 30.43 + ], + "团风县": [ + 114.87, + 30.63 + ], + "红安县": [ + 114.62, + 31.28 + ], + "罗田县": [ + 115.4, + 30.78 + ], + "英山县": [ + 115.67, + 30.75 + ], + "浠水县": [ + 115.27, + 30.45 + ], + "蕲春县": [ + 115.43, + 30.23 + ], + "黄梅县": [ + 115.93, + 30.08 + ], + "麻城市": [ + 115.03, + 31.18 + ], + "武穴市": [ + 115.55, + 29.85 + ], + "咸宁市": [ + 114.32, + 29.85 + ], + "咸安区": [ + 114.3, + 29.87 + ], + "嘉鱼县": [ + 113.9, + 29.98 + ], + "通城县": [ + 113.82, + 29.25 + ], + "崇阳县": [ + 114.03, + 29.55 + ], + "通山县": [ + 114.52, + 29.6 + ], + "赤壁市": [ + 113.88, + 29.72 + ], + "随州市": [ + 113.37, + 31.72 + ], + "曾都区": [ + 113.37, + 31.72 + ], + "广水市": [ + 113.82, + 31.62 + ], + "恩施土家族苗族自治州": [ + 109.47, + 30.3 + ], + "恩施市": [ + 109.47, + 30.3 + ], + "利川市": [ + 108.93, + 30.3 + ], + "建始县": [ + 109.73, + 30.6 + ], + "巴东县": [ + 110.33, + 31.05 + ], + "宣恩县": [ + 109.48, + 29.98 + ], + "咸丰县": [ + 109.15, + 29.68 + ], + "来凤县": [ + 109.4, + 29.52 + ], + "鹤峰县": [ + 110.03, + 29.9 + ], + "仙桃市": [ + 113.45, + 30.37 + ], + "潜江市": [ + 112.88, + 30.42 + ], + "天门市": [ + 113.17, + 30.67 + ], + "神农架林区": [ + 110.67, + 31.75 + ], + "长沙市": [ + 112.93, + 28.23 + ], + "芙蓉区": [ + 113.03, + 28.18 + ], + "天心区": [ + 112.98, + 28.12 + ], + "岳麓区": [ + 112.93, + 28.23 + ], + "开福区": [ + 112.98, + 28.25 + ], + "雨花区": [ + 113.03, + 28.13 + ], + "长沙县": [ + 113.07, + 28.25 + ], + "望城县": [ + 112.82, + 28.37 + ], + "宁乡县": [ + 112.55, + 28.25 + ], + "浏阳市": [ + 113.63, + 28.15 + ], + "株洲市": [ + 113.13, + 27.83 + ], + "荷塘区": [ + 113.17, + 27.87 + ], + "芦淞区": [ + 113.15, + 27.83 + ], + "石峰区": [ + 113.1, + 27.87 + ], + "天元区": [ + 113.12, + 27.83 + ], + "株洲县": [ + 113.13, + 27.72 + ], + "攸县": [ + 113.33, + 27 + ], + "茶陵县": [ + 113.53, + 26.8 + ], + "炎陵县": [ + 113.77, + 26.48 + ], + "醴陵市": [ + 113.48, + 27.67 + ], + "湘潭市": [ + 112.93, + 27.83 + ], + "雨湖区": [ + 112.9, + 27.87 + ], + "岳塘区": [ + 112.95, + 27.87 + ], + "湘潭县": [ + 112.95, + 27.78 + ], + "湘乡市": [ + 112.53, + 27.73 + ], + "韶山市": [ + 112.52, + 27.93 + ], + "衡阳市": [ + 112.57, + 26.9 + ], + "珠晖区": [ + 112.62, + 26.9 + ], + "雁峰区": [ + 112.6, + 26.88 + ], + "石鼓区": [ + 112.6, + 26.9 + ], + "蒸湘区": [ + 112.6, + 26.9 + ], + "南岳区": [ + 112.73, + 27.25 + ], + "衡阳县": [ + 112.37, + 26.97 + ], + "衡南县": [ + 112.67, + 26.73 + ], + "衡山县": [ + 112.87, + 27.23 + ], + "衡东县": [ + 112.95, + 27.08 + ], + "祁东县": [ + 112.12, + 26.78 + ], + "耒阳市": [ + 112.85, + 26.42 + ], + "常宁市": [ + 112.38, + 26.42 + ], + "邵阳市": [ + 111.47, + 27.25 + ], + "双清区": [ + 111.47, + 27.23 + ], + "大祥区": [ + 111.45, + 27.23 + ], + "北塔区": [ + 111.45, + 27.25 + ], + "邵东县": [ + 111.75, + 27.25 + ], + "新邵县": [ + 111.45, + 27.32 + ], + "邵阳县": [ + 111.27, + 27 + ], + "隆回县": [ + 111.03, + 27.12 + ], + "洞口县": [ + 110.57, + 27.05 + ], + "绥宁县": [ + 110.15, + 26.58 + ], + "新宁县": [ + 110.85, + 26.43 + ], + "城步苗族自治县": [ + 110.32, + 26.37 + ], + "武冈市": [ + 110.63, + 26.73 + ], + "岳阳市": [ + 113.12, + 29.37 + ], + "岳阳楼区": [ + 113.1, + 29.37 + ], + "云溪区": [ + 113.3, + 29.47 + ], + "君山区": [ + 113, + 29.43 + ], + "岳阳县": [ + 113.12, + 29.15 + ], + "华容县": [ + 112.57, + 29.52 + ], + "湘阴县": [ + 112.88, + 28.68 + ], + "平江县": [ + 113.58, + 28.72 + ], + "汨罗市": [ + 113.08, + 28.8 + ], + "临湘市": [ + 113.47, + 29.48 + ], + "常德市": [ + 111.68, + 29.05 + ], + "武陵区": [ + 111.68, + 29.03 + ], + "鼎城区": [ + 111.68, + 29.02 + ], + "安乡县": [ + 112.17, + 29.42 + ], + "汉寿县": [ + 111.97, + 28.9 + ], + "澧县": [ + 111.75, + 29.63 + ], + "临澧县": [ + 111.65, + 29.45 + ], + "桃源县": [ + 111.48, + 28.9 + ], + "石门县": [ + 111.38, + 29.58 + ], + "津市市": [ + 111.88, + 29.62 + ], + "张家界市": [ + 110.47, + 29.13 + ], + "永定区": [ + 110.48, + 29.13 + ], + "武陵源区": [ + 110.53, + 29.35 + ], + "慈利县": [ + 111.12, + 29.42 + ], + "桑植县": [ + 110.15, + 29.4 + ], + "益阳市": [ + 112.32, + 28.6 + ], + "资阳区": [ + 112.32, + 28.6 + ], + "赫山区": [ + 112.37, + 28.6 + ], + "南县": [ + 112.4, + 29.38 + ], + "桃江县": [ + 112.12, + 28.53 + ], + "安化县": [ + 111.22, + 28.38 + ], + "沅江市": [ + 112.38, + 28.85 + ], + "郴州市": [ + 113.02, + 25.78 + ], + "北湖区": [ + 113.02, + 25.8 + ], + "苏仙区": [ + 113.03, + 25.8 + ], + "桂阳县": [ + 112.73, + 25.73 + ], + "宜章县": [ + 112.95, + 25.4 + ], + "永兴县": [ + 113.1, + 26.13 + ], + "嘉禾县": [ + 112.37, + 25.58 + ], + "临武县": [ + 112.55, + 25.28 + ], + "汝城县": [ + 113.68, + 25.55 + ], + "桂东县": [ + 113.93, + 26.08 + ], + "安仁县": [ + 113.27, + 26.7 + ], + "资兴市": [ + 113.23, + 25.98 + ], + "永州市": [ + 111.62, + 26.43 + ], + "冷水滩区": [ + 111.6, + 26.43 + ], + "祁阳县": [ + 111.85, + 26.58 + ], + "东安县": [ + 111.28, + 26.4 + ], + "双牌县": [ + 111.65, + 25.97 + ], + "道县": [ + 111.58, + 25.53 + ], + "江永县": [ + 111.33, + 25.28 + ], + "宁远县": [ + 111.93, + 25.6 + ], + "蓝山县": [ + 112.18, + 25.37 + ], + "新田县": [ + 112.22, + 25.92 + ], + "江华瑶族自治县": [ + 111.58, + 25.18 + ], + "怀化市": [ + 110, + 27.57 + ], + "鹤城区": [ + 109.95, + 27.55 + ], + "中方县": [ + 109.93, + 27.4 + ], + "沅陵县": [ + 110.38, + 28.47 + ], + "辰溪县": [ + 110.18, + 28 + ], + "溆浦县": [ + 110.58, + 27.92 + ], + "会同县": [ + 109.72, + 26.87 + ], + "麻阳苗族自治县": [ + 109.8, + 27.87 + ], + "新晃侗族自治县": [ + 109.17, + 27.37 + ], + "芷江侗族自治县": [ + 109.68, + 27.45 + ], + "靖州苗族侗族自治县": [ + 109.68, + 26.58 + ], + "通道侗族自治县": [ + 109.78, + 26.17 + ], + "洪江市": [ + 109.82, + 27.2 + ], + "娄底市": [ + 112, + 27.73 + ], + "娄星区": [ + 112, + 27.73 + ], + "双峰县": [ + 112.2, + 27.45 + ], + "新化县": [ + 111.3, + 27.75 + ], + "冷水江市": [ + 111.43, + 27.68 + ], + "涟源市": [ + 111.67, + 27.7 + ], + "湘西土家族苗族自治州": [ + 109.73, + 28.32 + ], + "吉首市": [ + 109.73, + 28.32 + ], + "泸溪县": [ + 110.22, + 28.22 + ], + "凤凰县": [ + 109.6, + 27.95 + ], + "花垣县": [ + 109.48, + 28.58 + ], + "保靖县": [ + 109.65, + 28.72 + ], + "古丈县": [ + 109.95, + 28.62 + ], + "永顺县": [ + 109.85, + 29 + ], + "龙山县": [ + 109.43, + 29.47 + ], + "广州市": [ + 113.27, + 23.13 + ], + "荔湾区": [ + 113.23, + 23.13 + ], + "越秀区": [ + 113.27, + 23.13 + ], + "海珠区": [ + 113.25, + 23.1 + ], + "天河区": [ + 113.35, + 23.12 + ], + "黄埔区": [ + 113.45, + 23.1 + ], + "番禺区": [ + 113.35, + 22.95 + ], + "花都区": [ + 113.22, + 23.4 + ], + "增城市": [ + 113.83, + 23.3 + ], + "从化市": [ + 113.58, + 23.55 + ], + "韶关市": [ + 113.6, + 24.82 + ], + "武江区": [ + 113.57, + 24.8 + ], + "浈江区": [ + 113.6, + 24.8 + ], + "曲江区": [ + 113.6, + 24.68 + ], + "始兴县": [ + 114.07, + 24.95 + ], + "仁化县": [ + 113.75, + 25.08 + ], + "翁源县": [ + 114.13, + 24.35 + ], + "乳源瑶族自治县": [ + 113.27, + 24.78 + ], + "新丰县": [ + 114.2, + 24.07 + ], + "乐昌市": [ + 113.35, + 25.13 + ], + "南雄市": [ + 114.3, + 25.12 + ], + "深圳市": [ + 114.05, + 22.55 + ], + "罗湖区": [ + 114.12, + 22.55 + ], + "福田区": [ + 114.05, + 22.53 + ], + "南山区": [ + 113.92, + 22.52 + ], + "宝安区": [ + 113.9, + 22.57 + ], + "龙岗区": [ + 114.27, + 22.73 + ], + "盐田区": [ + 114.22, + 22.55 + ], + "珠海市": [ + 113.57, + 22.27 + ], + "香洲区": [ + 113.55, + 22.27 + ], + "斗门区": [ + 113.28, + 22.22 + ], + "金湾区": [ + 113.4, + 22.07 + ], + "汕头市": [ + 116.68, + 23.35 + ], + "龙湖区": [ + 116.72, + 23.37 + ], + "金平区": [ + 116.7, + 23.37 + ], + "潮阳区": [ + 116.6, + 23.27 + ], + "潮南区": [ + 116.43, + 23.25 + ], + "澄海区": [ + 116.77, + 23.48 + ], + "南澳县": [ + 117.02, + 23.42 + ], + "佛山市": [ + 113.12, + 23.02 + ], + "南海区": [ + 113.15, + 23.03 + ], + "顺德区": [ + 113.3, + 22.8 + ], + "三水区": [ + 112.87, + 23.17 + ], + "高明区": [ + 112.88, + 22.9 + ], + "江门市": [ + 113.08, + 22.58 + ], + "新会区": [ + 113.03, + 22.47 + ], + "台山市": [ + 112.78, + 22.25 + ], + "开平市": [ + 112.67, + 22.38 + ], + "鹤山市": [ + 112.97, + 22.77 + ], + "恩平市": [ + 112.3, + 22.18 + ], + "湛江市": [ + 110.35, + 21.27 + ], + "赤坎区": [ + 110.37, + 21.27 + ], + "霞山区": [ + 110.4, + 21.2 + ], + "坡头区": [ + 110.47, + 21.23 + ], + "麻章区": [ + 110.32, + 21.27 + ], + "遂溪县": [ + 110.25, + 21.38 + ], + "徐闻县": [ + 110.17, + 20.33 + ], + "廉江市": [ + 110.27, + 21.62 + ], + "雷州市": [ + 110.08, + 20.92 + ], + "吴川市": [ + 110.77, + 21.43 + ], + "茂名市": [ + 110.92, + 21.67 + ], + "茂南区": [ + 110.92, + 21.63 + ], + "茂港区": [ + 111.02, + 21.47 + ], + "电白县": [ + 111, + 21.5 + ], + "高州市": [ + 110.85, + 21.92 + ], + "化州市": [ + 110.63, + 21.67 + ], + "信宜市": [ + 110.95, + 22.35 + ], + "肇庆市": [ + 112.47, + 23.05 + ], + "端州区": [ + 112.48, + 23.05 + ], + "鼎湖区": [ + 112.57, + 23.17 + ], + "广宁县": [ + 112.43, + 23.63 + ], + "怀集县": [ + 112.18, + 23.92 + ], + "封开县": [ + 111.5, + 23.43 + ], + "德庆县": [ + 111.77, + 23.15 + ], + "高要市": [ + 112.45, + 23.03 + ], + "四会市": [ + 112.68, + 23.33 + ], + "惠州市": [ + 114.42, + 23.12 + ], + "惠城区": [ + 114.4, + 23.08 + ], + "惠阳区": [ + 114.47, + 22.8 + ], + "博罗县": [ + 114.28, + 23.18 + ], + "惠东县": [ + 114.72, + 22.98 + ], + "龙门县": [ + 114.25, + 23.73 + ], + "梅州市": [ + 116.12, + 24.28 + ], + "梅江区": [ + 116.12, + 24.32 + ], + "梅县": [ + 116.05, + 24.28 + ], + "大埔县": [ + 116.7, + 24.35 + ], + "丰顺县": [ + 116.18, + 23.77 + ], + "五华县": [ + 115.77, + 23.93 + ], + "平远县": [ + 115.88, + 24.57 + ], + "蕉岭县": [ + 116.17, + 24.67 + ], + "兴宁市": [ + 115.73, + 24.15 + ], + "汕尾市": [ + 115.37, + 22.78 + ], + "海丰县": [ + 115.33, + 22.97 + ], + "陆河县": [ + 115.65, + 23.3 + ], + "陆丰市": [ + 115.65, + 22.95 + ], + "河源市": [ + 114.7, + 23.73 + ], + "源城区": [ + 114.7, + 23.73 + ], + "紫金县": [ + 115.18, + 23.63 + ], + "龙川县": [ + 115.25, + 24.1 + ], + "连平县": [ + 114.48, + 24.37 + ], + "和平县": [ + 114.93, + 24.45 + ], + "东源县": [ + 114.77, + 23.82 + ], + "阳江市": [ + 111.98, + 21.87 + ], + "江城区": [ + 111.95, + 21.87 + ], + "阳西县": [ + 111.62, + 21.75 + ], + "阳东县": [ + 112.02, + 21.88 + ], + "阳春市": [ + 111.78, + 22.18 + ], + "清远市": [ + 113.03, + 23.7 + ], + "清城区": [ + 113.02, + 23.7 + ], + "佛冈县": [ + 113.53, + 23.88 + ], + "阳山县": [ + 112.63, + 24.48 + ], + "连山壮族瑶族自治县": [ + 112.08, + 24.57 + ], + "连南瑶族自治县": [ + 112.28, + 24.72 + ], + "清新县": [ + 112.98, + 23.73 + ], + "英德市": [ + 113.4, + 24.18 + ], + "连州市": [ + 112.38, + 24.78 + ], + "东莞市": [ + 113.75, + 23.05 + ], + "中山市": [ + 113.38, + 22.52 + ], + "潮州市": [ + 116.62, + 23.67 + ], + "湘桥区": [ + 116.63, + 23.68 + ], + "潮安县": [ + 116.68, + 23.45 + ], + "饶平县": [ + 117, + 23.67 + ], + "揭阳市": [ + 116.37, + 23.55 + ], + "揭东县": [ + 116.42, + 23.57 + ], + "揭西县": [ + 115.83, + 23.43 + ], + "惠来县": [ + 116.28, + 23.03 + ], + "普宁市": [ + 116.18, + 23.3 + ], + "云浮市": [ + 112.03, + 22.92 + ], + "云城区": [ + 112.03, + 22.93 + ], + "新兴县": [ + 112.23, + 22.7 + ], + "郁南县": [ + 111.53, + 23.23 + ], + "云安县": [ + 112, + 23.08 + ], + "罗定市": [ + 111.57, + 22.77 + ], + "南宁市": [ + 108.37, + 22.82 + ], + "兴宁区": [ + 108.38, + 22.87 + ], + "江南区": [ + 108.28, + 22.78 + ], + "西乡塘区": [ + 108.3, + 22.83 + ], + "良庆区": [ + 108.32, + 22.77 + ], + "邕宁区": [ + 108.48, + 22.75 + ], + "武鸣县": [ + 108.27, + 23.17 + ], + "隆安县": [ + 107.68, + 23.18 + ], + "马山县": [ + 108.17, + 23.72 + ], + "上林县": [ + 108.6, + 23.43 + ], + "宾阳县": [ + 108.8, + 23.22 + ], + "横县": [ + 109.27, + 22.68 + ], + "柳州市": [ + 109.42, + 24.33 + ], + "柳南区": [ + 109.38, + 24.35 + ], + "柳江县": [ + 109.33, + 24.27 + ], + "柳城县": [ + 109.23, + 24.65 + ], + "鹿寨县": [ + 109.73, + 24.48 + ], + "融安县": [ + 109.4, + 25.23 + ], + "融水苗族自治县": [ + 109.25, + 25.07 + ], + "三江侗族自治县": [ + 109.6, + 25.78 + ], + "桂林市": [ + 110.28, + 25.28 + ], + "阳朔县": [ + 110.48, + 24.78 + ], + "临桂县": [ + 110.2, + 25.23 + ], + "灵川县": [ + 110.32, + 25.42 + ], + "全州县": [ + 111.07, + 25.93 + ], + "兴安县": [ + 110.67, + 25.62 + ], + "永福县": [ + 109.98, + 24.98 + ], + "灌阳县": [ + 111.15, + 25.48 + ], + "龙胜各族自治县": [ + 110, + 25.8 + ], + "资源县": [ + 110.63, + 26.03 + ], + "平乐县": [ + 110.63, + 24.63 + ], + "恭城瑶族自治县": [ + 110.83, + 24.83 + ], + "梧州市": [ + 111.27, + 23.48 + ], + "苍梧县": [ + 111.23, + 23.42 + ], + "藤县": [ + 110.92, + 23.38 + ], + "蒙山县": [ + 110.52, + 24.2 + ], + "岑溪市": [ + 110.98, + 22.92 + ], + "北海市": [ + 109.12, + 21.48 + ], + "铁山港区": [ + 109.43, + 21.53 + ], + "合浦县": [ + 109.2, + 21.67 + ], + "防城港市": [ + 108.35, + 21.7 + ], + "港口区": [ + 108.37, + 21.65 + ], + "防城区": [ + 108.35, + 21.77 + ], + "上思县": [ + 107.98, + 22.15 + ], + "东兴市": [ + 107.97, + 21.53 + ], + "钦州市": [ + 108.62, + 21.95 + ], + "钦北区": [ + 108.63, + 21.98 + ], + "灵山县": [ + 109.3, + 22.43 + ], + "浦北县": [ + 109.55, + 22.27 + ], + "贵港市": [ + 109.6, + 23.1 + ], + "覃塘区": [ + 109.42, + 23.13 + ], + "平南县": [ + 110.38, + 23.55 + ], + "桂平市": [ + 110.08, + 23.4 + ], + "玉林市": [ + 110.17, + 22.63 + ], + "容县": [ + 110.55, + 22.87 + ], + "陆川县": [ + 110.27, + 22.33 + ], + "博白县": [ + 109.97, + 22.28 + ], + "兴业县": [ + 109.87, + 22.75 + ], + "北流市": [ + 110.35, + 22.72 + ], + "百色市": [ + 106.62, + 23.9 + ], + "田阳县": [ + 106.92, + 23.73 + ], + "田东县": [ + 107.12, + 23.6 + ], + "平果县": [ + 107.58, + 23.32 + ], + "德保县": [ + 106.62, + 23.33 + ], + "靖西县": [ + 106.42, + 23.13 + ], + "那坡县": [ + 105.83, + 23.42 + ], + "凌云县": [ + 106.57, + 24.35 + ], + "乐业县": [ + 106.55, + 24.78 + ], + "田林县": [ + 106.23, + 24.3 + ], + "西林县": [ + 105.1, + 24.5 + ], + "隆林各族自治县": [ + 105.33, + 24.77 + ], + "贺州市": [ + 111.55, + 24.42 + ], + "昭平县": [ + 110.8, + 24.17 + ], + "钟山县": [ + 111.3, + 24.53 + ], + "富川瑶族自治县": [ + 111.27, + 24.83 + ], + "河池市": [ + 108.07, + 24.7 + ], + "金城江区": [ + 108.05, + 24.7 + ], + "南丹县": [ + 107.53, + 24.98 + ], + "天峨县": [ + 107.17, + 25 + ], + "凤山县": [ + 107.05, + 24.55 + ], + "东兰县": [ + 107.37, + 24.52 + ], + "罗城仫佬族自治县": [ + 108.9, + 24.78 + ], + "环江毛南族自治县": [ + 108.25, + 24.83 + ], + "巴马瑶族自治县": [ + 107.25, + 24.15 + ], + "都安瑶族自治县": [ + 108.1, + 23.93 + ], + "大化瑶族自治县": [ + 107.98, + 23.73 + ], + "宜州市": [ + 108.67, + 24.5 + ], + "来宾市": [ + 109.23, + 23.73 + ], + "忻城县": [ + 108.67, + 24.07 + ], + "象州县": [ + 109.68, + 23.97 + ], + "武宣县": [ + 109.67, + 23.6 + ], + "金秀瑶族自治县": [ + 110.18, + 24.13 + ], + "合山市": [ + 108.87, + 23.82 + ], + "崇左市": [ + 107.37, + 22.4 + ], + "扶绥县": [ + 107.9, + 22.63 + ], + "宁明县": [ + 107.07, + 22.13 + ], + "龙州县": [ + 106.85, + 22.35 + ], + "大新县": [ + 107.2, + 22.83 + ], + "天等县": [ + 107.13, + 23.08 + ], + "凭祥市": [ + 106.75, + 22.12 + ], + "海口市": [ + 110.32, + 20.03 + ], + "秀英区": [ + 110.28, + 20.02 + ], + "龙华区": [ + 110.3, + 20.03 + ], + "琼山区": [ + 110.35, + 20 + ], + "美兰区": [ + 110.37, + 20.03 + ], + "三亚市": [ + 109.5, + 18.25 + ], + "五指山市": [ + 109.52, + 18.78 + ], + "琼海市": [ + 110.47, + 19.25 + ], + "儋州市": [ + 109.57, + 19.52 + ], + "文昌市": [ + 110.8, + 19.55 + ], + "万宁市": [ + 110.4, + 18.8 + ], + "东方市": [ + 108.63, + 19.1 + ], + "定安县": [ + 110.32, + 19.7 + ], + "屯昌县": [ + 110.1, + 19.37 + ], + "澄迈县": [ + 110, + 19.73 + ], + "临高县": [ + 109.68, + 19.92 + ], + "白沙黎族自治县": [ + 109.45, + 19.23 + ], + "昌江黎族自治县": [ + 109.05, + 19.25 + ], + "乐东黎族自治县": [ + 109.17, + 18.75 + ], + "陵水黎族自治县": [ + 110.03, + 18.5 + ], + "保亭黎族苗族自治县": [ + 109.7, + 18.63 + ], + "琼中黎族苗族自治县": [ + 109.83, + 19.03 + ], + "重庆市": [ + 106.55, + 29.57 + ], + "万州区": [ + 108.4, + 30.82 + ], + "涪陵区": [ + 107.4, + 29.72 + ], + "渝中区": [ + 106.57, + 29.55 + ], + "大渡口区": [ + 106.48, + 29.48 + ], + "江北区": [ + 106.57, + 29.6 + ], + "沙坪坝区": [ + 106.45, + 29.53 + ], + "九龙坡区": [ + 106.5, + 29.5 + ], + "南岸区": [ + 106.57, + 29.52 + ], + "北碚区": [ + 106.4, + 29.8 + ], + "万盛区": [ + 106.92, + 28.97 + ], + "双桥区": [ + 105.78, + 29.48 + ], + "渝北区": [ + 106.63, + 29.72 + ], + "巴南区": [ + 106.52, + 29.38 + ], + "黔江区": [ + 108.77, + 29.53 + ], + "长寿区": [ + 107.08, + 29.87 + ], + "綦江县": [ + 106.65, + 29.03 + ], + "潼南县": [ + 105.83, + 30.18 + ], + "铜梁县": [ + 106.05, + 29.85 + ], + "大足县": [ + 105.72, + 29.7 + ], + "荣昌县": [ + 105.58, + 29.4 + ], + "璧山县": [ + 106.22, + 29.6 + ], + "梁平县": [ + 107.8, + 30.68 + ], + "城口县": [ + 108.67, + 31.95 + ], + "丰都县": [ + 107.73, + 29.87 + ], + "垫江县": [ + 107.35, + 30.33 + ], + "武隆县": [ + 107.75, + 29.33 + ], + "忠县": [ + 108.02, + 30.3 + ], + "开县": [ + 108.42, + 31.18 + ], + "云阳县": [ + 108.67, + 30.95 + ], + "奉节县": [ + 109.47, + 31.02 + ], + "巫山县": [ + 109.88, + 31.08 + ], + "巫溪县": [ + 109.63, + 31.4 + ], + "石柱土家族自治县": [ + 108.12, + 30 + ], + "秀山土家族苗族自治县": [ + 108.98, + 28.45 + ], + "酉阳土家族苗族自治县": [ + 108.77, + 28.85 + ], + "彭水苗族土家族自治县": [ + 108.17, + 29.3 + ], + "成都市": [ + 104.07, + 30.67 + ], + "锦江区": [ + 104.08, + 30.67 + ], + "青羊区": [ + 104.05, + 30.68 + ], + "金牛区": [ + 104.05, + 30.7 + ], + "武侯区": [ + 104.05, + 30.65 + ], + "成华区": [ + 104.1, + 30.67 + ], + "龙泉驿区": [ + 104.27, + 30.57 + ], + "青白江区": [ + 104.23, + 30.88 + ], + "新都区": [ + 104.15, + 30.83 + ], + "温江区": [ + 103.83, + 30.7 + ], + "金堂县": [ + 104.43, + 30.85 + ], + "双流县": [ + 103.92, + 30.58 + ], + "郫县": [ + 103.88, + 30.82 + ], + "大邑县": [ + 103.52, + 30.58 + ], + "蒲江县": [ + 103.5, + 30.2 + ], + "新津县": [ + 103.82, + 30.42 + ], + "都江堰市": [ + 103.62, + 31 + ], + "彭州市": [ + 103.93, + 30.98 + ], + "邛崃市": [ + 103.47, + 30.42 + ], + "崇州市": [ + 103.67, + 30.63 + ], + "自贡市": [ + 104.78, + 29.35 + ], + "自流井区": [ + 104.77, + 29.35 + ], + "贡井区": [ + 104.72, + 29.35 + ], + "大安区": [ + 104.77, + 29.37 + ], + "沿滩区": [ + 104.87, + 29.27 + ], + "荣县": [ + 104.42, + 29.47 + ], + "富顺县": [ + 104.98, + 29.18 + ], + "攀枝花市": [ + 101.72, + 26.58 + ], + "东区": [ + 101.7, + 26.55 + ], + "西区": [ + 101.6, + 26.6 + ], + "仁和区": [ + 101.73, + 26.5 + ], + "米易县": [ + 102.12, + 26.88 + ], + "盐边县": [ + 101.85, + 26.7 + ], + "泸州市": [ + 105.43, + 28.87 + ], + "江阳区": [ + 105.45, + 28.88 + ], + "纳溪区": [ + 105.37, + 28.77 + ], + "龙马潭区": [ + 105.43, + 28.9 + ], + "泸县": [ + 105.38, + 29.15 + ], + "合江县": [ + 105.83, + 28.82 + ], + "叙永县": [ + 105.43, + 28.17 + ], + "古蔺县": [ + 105.82, + 28.05 + ], + "德阳市": [ + 104.38, + 31.13 + ], + "旌阳区": [ + 104.38, + 31.13 + ], + "中江县": [ + 104.68, + 31.03 + ], + "罗江县": [ + 104.5, + 31.32 + ], + "广汉市": [ + 104.28, + 30.98 + ], + "什邡市": [ + 104.17, + 31.13 + ], + "绵竹市": [ + 104.2, + 31.35 + ], + "绵阳市": [ + 104.73, + 31.47 + ], + "涪城区": [ + 104.73, + 31.47 + ], + "游仙区": [ + 104.75, + 31.47 + ], + "三台县": [ + 105.08, + 31.1 + ], + "盐亭县": [ + 105.38, + 31.22 + ], + "安县": [ + 104.57, + 31.53 + ], + "梓潼县": [ + 105.17, + 31.63 + ], + "北川羌族自治县": [ + 104.45, + 31.82 + ], + "平武县": [ + 104.53, + 32.42 + ], + "江油市": [ + 104.75, + 31.78 + ], + "广元市": [ + 105.83, + 32.43 + ], + "元坝区": [ + 105.97, + 32.32 + ], + "朝天区": [ + 105.88, + 32.65 + ], + "旺苍县": [ + 106.28, + 32.23 + ], + "青川县": [ + 105.23, + 32.58 + ], + "剑阁县": [ + 105.52, + 32.28 + ], + "苍溪县": [ + 105.93, + 31.73 + ], + "遂宁市": [ + 105.57, + 30.52 + ], + "船山区": [ + 105.57, + 30.52 + ], + "安居区": [ + 105.45, + 30.35 + ], + "蓬溪县": [ + 105.72, + 30.78 + ], + "射洪县": [ + 105.38, + 30.87 + ], + "大英县": [ + 105.25, + 30.58 + ], + "内江市": [ + 105.05, + 29.58 + ], + "东兴区": [ + 105.07, + 29.6 + ], + "威远县": [ + 104.67, + 29.53 + ], + "资中县": [ + 104.85, + 29.78 + ], + "隆??县": [ + 105.28, + 29.35 + ], + "乐山市": [ + 103.77, + 29.57 + ], + "沙湾区": [ + 103.55, + 29.42 + ], + "五通桥区": [ + 103.82, + 29.4 + ], + "金口河区": [ + 103.08, + 29.25 + ], + "犍为县": [ + 103.95, + 29.22 + ], + "井研县": [ + 104.07, + 29.65 + ], + "夹江县": [ + 103.57, + 29.73 + ], + "沐川县": [ + 103.9, + 28.97 + ], + "峨边彝族自治县": [ + 103.27, + 29.23 + ], + "马边彝族自治县": [ + 103.55, + 28.83 + ], + "峨眉山市": [ + 103.48, + 29.6 + ], + "南充市": [ + 106.08, + 30.78 + ], + "顺庆区": [ + 106.08, + 30.78 + ], + "高坪区": [ + 106.1, + 30.77 + ], + "嘉陵区": [ + 106.05, + 30.77 + ], + "南部县": [ + 106.07, + 31.35 + ], + "营山县": [ + 106.57, + 31.08 + ], + "蓬安县": [ + 106.42, + 31.03 + ], + "仪陇县": [ + 106.28, + 31.27 + ], + "西充县": [ + 105.88, + 31 + ], + "阆中市": [ + 106, + 31.55 + ], + "眉山市": [ + 103.83, + 30.05 + ], + "东坡区": [ + 103.83, + 30.05 + ], + "仁寿县": [ + 104.15, + 30 + ], + "彭山县": [ + 103.87, + 30.2 + ], + "洪雅县": [ + 103.37, + 29.92 + ], + "丹棱县": [ + 103.52, + 30.02 + ], + "青神县": [ + 103.85, + 29.83 + ], + "宜宾市": [ + 104.62, + 28.77 + ], + "翠屏区": [ + 104.62, + 28.77 + ], + "宜宾县": [ + 104.55, + 28.7 + ], + "南溪县": [ + 104.98, + 28.85 + ], + "江安县": [ + 105.07, + 28.73 + ], + "长宁县": [ + 104.92, + 28.58 + ], + "高县": [ + 104.52, + 28.43 + ], + "珙县": [ + 104.72, + 28.45 + ], + "筠连县": [ + 104.52, + 28.17 + ], + "兴文县": [ + 105.23, + 28.3 + ], + "屏山县": [ + 104.33, + 28.83 + ], + "广安市": [ + 106.63, + 30.47 + ], + "岳池县": [ + 106.43, + 30.55 + ], + "武胜县": [ + 106.28, + 30.35 + ], + "邻水县": [ + 106.93, + 30.33 + ], + "华蓥市": [ + 106.77, + 30.38 + ], + "达州市": [ + 107.5, + 31.22 + ], + "通川区": [ + 107.48, + 31.22 + ], + "达县": [ + 107.5, + 31.2 + ], + "宣汉县": [ + 107.72, + 31.35 + ], + "开江县": [ + 107.87, + 31.08 + ], + "大竹县": [ + 107.2, + 30.73 + ], + "渠县": [ + 106.97, + 30.83 + ], + "万源市": [ + 108.03, + 32.07 + ], + "雅安市": [ + 103, + 29.98 + ], + "雨城区": [ + 103, + 29.98 + ], + "名山县": [ + 103.12, + 30.08 + ], + "荥经县": [ + 102.85, + 29.8 + ], + "汉源县": [ + 102.65, + 29.35 + ], + "石棉县": [ + 102.37, + 29.23 + ], + "天全县": [ + 102.75, + 30.07 + ], + "芦山县": [ + 102.92, + 30.15 + ], + "宝兴县": [ + 102.82, + 30.37 + ], + "巴中市": [ + 106.77, + 31.85 + ], + "巴州区": [ + 106.77, + 31.85 + ], + "通江县": [ + 107.23, + 31.92 + ], + "南江县": [ + 106.83, + 32.35 + ], + "平昌县": [ + 107.1, + 31.57 + ], + "资阳市": [ + 104.65, + 30.12 + ], + "雁江区": [ + 104.65, + 30.12 + ], + "安岳县": [ + 105.33, + 30.1 + ], + "乐至县": [ + 105.02, + 30.28 + ], + "简阳市": [ + 104.55, + 30.4 + ], + "阿坝藏族羌族自治州": [ + 102.22, + 31.9 + ], + "汶川县": [ + 103.58, + 31.48 + ], + "理县": [ + 103.17, + 31.43 + ], + "茂县": [ + 103.85, + 31.68 + ], + "松潘县": [ + 103.6, + 32.63 + ], + "九寨沟县": [ + 104.23, + 33.27 + ], + "金川县": [ + 102.07, + 31.48 + ], + "小金县": [ + 102.37, + 31 + ], + "黑水县": [ + 102.98, + 32.07 + ], + "马尔康县": [ + 102.22, + 31.9 + ], + "壤塘县": [ + 100.98, + 32.27 + ], + "阿坝县": [ + 101.7, + 32.9 + ], + "若尔盖县": [ + 102.95, + 33.58 + ], + "红原县": [ + 102.55, + 32.8 + ], + "甘孜藏族自治州": [ + 101.97, + 30.05 + ], + "康定县": [ + 101.97, + 30.05 + ], + "泸定县": [ + 102.23, + 29.92 + ], + "丹巴县": [ + 101.88, + 30.88 + ], + "九龙县": [ + 101.5, + 29 + ], + "雅江县": [ + 101.02, + 30.03 + ], + "道孚县": [ + 101.12, + 30.98 + ], + "炉霍县": [ + 100.68, + 31.4 + ], + "甘孜县": [ + 99.98, + 31.62 + ], + "新龙县": [ + 100.32, + 30.95 + ], + "德格县": [ + 98.58, + 31.82 + ], + "白玉县": [ + 98.83, + 31.22 + ], + "石渠县": [ + 98.1, + 32.98 + ], + "色达县": [ + 100.33, + 32.27 + ], + "理塘县": [ + 100.27, + 30 + ], + "巴塘县": [ + 99.1, + 30 + ], + "乡城县": [ + 99.8, + 28.93 + ], + "稻城县": [ + 100.3, + 29.03 + ], + "得荣县": [ + 99.28, + 28.72 + ], + "凉山彝族自治州": [ + 102.27, + 27.9 + ], + "西昌市": [ + 102.27, + 27.9 + ], + "木里藏族自治县": [ + 101.28, + 27.93 + ], + "盐源县": [ + 101.5, + 27.43 + ], + "德昌县": [ + 102.18, + 27.4 + ], + "会理县": [ + 102.25, + 26.67 + ], + "会东县": [ + 102.58, + 26.63 + ], + "宁南县": [ + 102.77, + 27.07 + ], + "普格县": [ + 102.53, + 27.38 + ], + "布拖县": [ + 102.82, + 27.72 + ], + "金阳县": [ + 103.25, + 27.7 + ], + "昭觉县": [ + 102.85, + 28.02 + ], + "喜德县": [ + 102.42, + 28.32 + ], + "冕宁县": [ + 102.17, + 28.55 + ], + "越西县": [ + 102.52, + 28.65 + ], + "甘洛县": [ + 102.77, + 28.97 + ], + "美姑县": [ + 103.13, + 28.33 + ], + "雷波县": [ + 103.57, + 28.27 + ], + "贵阳市": [ + 106.63, + 26.65 + ], + "南明区": [ + 106.72, + 26.57 + ], + "云岩区": [ + 106.72, + 26.62 + ], + "乌当区": [ + 106.75, + 26.63 + ], + "白云区": [ + 106.65, + 26.68 + ], + "小河区": [ + 106.7, + 26.53 + ], + "开阳县": [ + 106.97, + 27.07 + ], + "息烽县": [ + 106.73, + 27.1 + ], + "修文县": [ + 106.58, + 26.83 + ], + "清镇市": [ + 106.47, + 26.55 + ], + "六盘水市": [ + 104.83, + 26.6 + ], + "钟山区": [ + 104.83, + 26.6 + ], + "六枝特区": [ + 105.48, + 26.22 + ], + "水城县": [ + 104.95, + 26.55 + ], + "盘县": [ + 104.47, + 25.72 + ], + "遵义市": [ + 106.92, + 27.73 + ], + "红花岗区": [ + 106.92, + 27.65 + ], + "汇川区": [ + 106.92, + 27.73 + ], + "遵义县": [ + 106.83, + 27.53 + ], + "桐梓县": [ + 106.82, + 28.13 + ], + "绥阳县": [ + 107.18, + 27.95 + ], + "正安县": [ + 107.43, + 28.55 + ], + "道真仡佬族苗族自治县": [ + 107.6, + 28.88 + ], + "务川仡佬族苗族自治县": [ + 107.88, + 28.53 + ], + "凤冈县": [ + 107.72, + 27.97 + ], + "湄潭县": [ + 107.48, + 27.77 + ], + "余庆县": [ + 107.88, + 27.22 + ], + "习水县": [ + 106.22, + 28.32 + ], + "赤水市": [ + 105.7, + 28.58 + ], + "仁怀市": [ + 106.42, + 27.82 + ], + "安顺市": [ + 105.95, + 26.25 + ], + "西秀区": [ + 105.92, + 26.25 + ], + "平坝县": [ + 106.25, + 26.42 + ], + "普定县": [ + 105.75, + 26.32 + ], + "镇宁布依族苗族自治县": [ + 105.77, + 26.07 + ], + "关岭布依族苗族自治县": [ + 105.62, + 25.95 + ], + "紫云苗族布依族自治县": [ + 106.08, + 25.75 + ], + "铜仁地区": [ + 109.18, + 27.72 + ], + "铜仁市": [ + 109.18, + 27.72 + ], + "江口县": [ + 108.85, + 27.7 + ], + "玉屏侗族自治县": [ + 108.92, + 27.23 + ], + "石阡县": [ + 108.23, + 27.52 + ], + "思南县": [ + 108.25, + 27.93 + ], + "印江土家族苗族自治县": [ + 108.4, + 28 + ], + "德江县": [ + 108.12, + 28.27 + ], + "沿河土家族自治县": [ + 108.5, + 28.57 + ], + "松桃苗族自治县": [ + 109.2, + 28.17 + ], + "万山特区": [ + 109.2, + 27.52 + ], + "兴义市": [ + 104.9, + 25.08 + ], + "兴仁县": [ + 105.18, + 25.43 + ], + "普安县": [ + 104.95, + 25.78 + ], + "晴隆县": [ + 105.22, + 25.83 + ], + "贞丰县": [ + 105.65, + 25.38 + ], + "望谟县": [ + 106.1, + 25.17 + ], + "册亨县": [ + 105.82, + 24.98 + ], + "安龙县": [ + 105.47, + 25.12 + ], + "毕节地区": [ + 105.28, + 27.3 + ], + "毕节市": [ + 105.28, + 27.3 + ], + "大方县": [ + 105.6, + 27.15 + ], + "黔西县": [ + 106.03, + 27.03 + ], + "金沙县": [ + 106.22, + 27.47 + ], + "织金县": [ + 105.77, + 26.67 + ], + "纳雍县": [ + 105.38, + 26.78 + ], + "赫章县": [ + 104.72, + 27.13 + ], + "黔东南苗族侗族自治州": [ + 107.97, + 26.58 + ], + "凯里市": [ + 107.97, + 26.58 + ], + "黄平县": [ + 107.9, + 26.9 + ], + "施秉县": [ + 108.12, + 27.03 + ], + "三穗县": [ + 108.68, + 26.97 + ], + "镇远县": [ + 108.42, + 27.05 + ], + "岑巩县": [ + 108.82, + 27.18 + ], + "天柱县": [ + 109.2, + 26.92 + ], + "锦屏县": [ + 109.2, + 26.68 + ], + "剑河县": [ + 108.45, + 26.73 + ], + "台江县": [ + 108.32, + 26.67 + ], + "黎平县": [ + 109.13, + 26.23 + ], + "榕江县": [ + 108.52, + 25.93 + ], + "从江县": [ + 108.9, + 25.75 + ], + "雷山县": [ + 108.07, + 26.38 + ], + "麻江县": [ + 107.58, + 26.5 + ], + "丹寨县": [ + 107.8, + 26.2 + ], + "黔南布依族苗族自治州": [ + 107.52, + 26.27 + ], + "都匀市": [ + 107.52, + 26.27 + ], + "福泉市": [ + 107.5, + 26.7 + ], + "荔波县": [ + 107.88, + 25.42 + ], + "贵定县": [ + 107.23, + 26.58 + ], + "瓮安县": [ + 107.47, + 27.07 + ], + "独山县": [ + 107.53, + 25.83 + ], + "平塘县": [ + 107.32, + 25.83 + ], + "罗甸县": [ + 106.75, + 25.43 + ], + "长顺县": [ + 106.45, + 26.03 + ], + "龙里县": [ + 106.97, + 26.45 + ], + "惠水县": [ + 106.65, + 26.13 + ], + "三都水族自治县": [ + 107.87, + 25.98 + ], + "昆明市": [ + 102.72, + 25.05 + ], + "五华区": [ + 102.7, + 25.05 + ], + "盘龙区": [ + 102.72, + 25.03 + ], + "官渡区": [ + 102.75, + 25.02 + ], + "西山区": [ + 102.67, + 25.03 + ], + "东川区": [ + 103.18, + 26.08 + ], + "呈贡县": [ + 102.8, + 24.88 + ], + "晋宁县": [ + 102.6, + 24.67 + ], + "富民县": [ + 102.5, + 25.22 + ], + "宜良县": [ + 103.15, + 24.92 + ], + "石林彝族自治县": [ + 103.27, + 24.77 + ], + "嵩明县": [ + 103.03, + 25.35 + ], + "禄劝彝族苗族自治县": [ + 102.47, + 25.55 + ], + "寻甸回族彝族自治县": [ + 103.25, + 25.57 + ], + "安宁市": [ + 102.48, + 24.92 + ], + "曲靖市": [ + 103.8, + 25.5 + ], + "麒麟区": [ + 103.8, + 25.5 + ], + "马龙县": [ + 103.58, + 25.43 + ], + "陆良县": [ + 103.67, + 25.03 + ], + "师宗县": [ + 103.98, + 24.83 + ], + "罗平县": [ + 104.3, + 24.88 + ], + "富源县": [ + 104.25, + 25.67 + ], + "会泽县": [ + 103.3, + 26.42 + ], + "沾益县": [ + 103.82, + 25.62 + ], + "宣威市": [ + 104.1, + 26.22 + ], + "玉溪市": [ + 102.55, + 24.35 + ], + "江川县": [ + 102.75, + 24.28 + ], + "澄江县": [ + 102.92, + 24.67 + ], + "通海县": [ + 102.75, + 24.12 + ], + "华宁县": [ + 102.93, + 24.2 + ], + "易门县": [ + 102.17, + 24.67 + ], + "峨山彝族自治县": [ + 102.4, + 24.18 + ], + "新平彝族傣族自治县": [ + 101.98, + 24.07 + ], + "保山市": [ + 99.17, + 25.12 + ], + "隆阳区": [ + 99.17, + 25.12 + ], + "施甸县": [ + 99.18, + 24.73 + ], + "腾冲县": [ + 98.5, + 25.03 + ], + "龙陵县": [ + 98.68, + 24.58 + ], + "昌宁县": [ + 99.6, + 24.83 + ], + "昭通市": [ + 103.72, + 27.33 + ], + "昭阳区": [ + 103.72, + 27.33 + ], + "鲁甸县": [ + 103.55, + 27.2 + ], + "巧家县": [ + 102.92, + 26.92 + ], + "盐津县": [ + 104.23, + 28.12 + ], + "大关县": [ + 103.88, + 27.75 + ], + "永善县": [ + 103.63, + 28.23 + ], + "绥江县": [ + 103.95, + 28.6 + ], + "镇雄县": [ + 104.87, + 27.45 + ], + "彝良县": [ + 104.05, + 27.63 + ], + "威信县": [ + 105.05, + 27.85 + ], + "水富县": [ + 104.4, + 28.63 + ], + "丽江市": [ + 100.23, + 26.88 + ], + "古城区": [ + 100.23, + 26.88 + ], + "玉龙纳西族自治县": [ + 100.23, + 26.82 + ], + "永胜县": [ + 100.75, + 26.68 + ], + "华坪县": [ + 101.27, + 26.63 + ], + "宁蒗彝族自治县": [ + 100.85, + 27.28 + ], + "墨江哈尼族自治县": [ + 101.68, + 23.43 + ], + "景东彝族自治县": [ + 100.83, + 24.45 + ], + "景谷傣族彝族自治县": [ + 100.7, + 23.5 + ], + "江城哈尼族彝族自治县": [ + 101.85, + 22.58 + ], + "澜沧拉祜族自治县": [ + 99.93, + 22.55 + ], + "西盟佤族自治县": [ + 99.62, + 22.63 + ], + "临沧市": [ + 100.08, + 23.88 + ], + "临翔区": [ + 100.08, + 23.88 + ], + "凤庆县": [ + 99.92, + 24.6 + ], + "云县": [ + 100.13, + 24.45 + ], + "永德县": [ + 99.25, + 24.03 + ], + "镇康县": [ + 98.83, + 23.78 + ], + "耿马傣族佤族自治县": [ + 99.4, + 23.55 + ], + "沧源佤族自治县": [ + 99.25, + 23.15 + ], + "楚雄彝族自治州": [ + 101.55, + 25.03 + ], + "楚雄市": [ + 101.55, + 25.03 + ], + "双柏县": [ + 101.63, + 24.7 + ], + "牟定县": [ + 101.53, + 25.32 + ], + "南华县": [ + 101.27, + 25.2 + ], + "姚安县": [ + 101.23, + 25.5 + ], + "大姚县": [ + 101.32, + 25.73 + ], + "永仁县": [ + 101.67, + 26.07 + ], + "元谋县": [ + 101.88, + 25.7 + ], + "武定县": [ + 102.4, + 25.53 + ], + "禄丰县": [ + 102.08, + 25.15 + ], + "红河哈尼族彝族自治州": [ + 103.4, + 23.37 + ], + "个旧市": [ + 103.15, + 23.37 + ], + "开远市": [ + 103.27, + 23.72 + ], + "蒙自县": [ + 103.4, + 23.37 + ], + "屏边苗族自治县": [ + 103.68, + 22.98 + ], + "建水县": [ + 102.83, + 23.62 + ], + "石屏县": [ + 102.5, + 23.72 + ], + "弥勒县": [ + 103.43, + 24.4 + ], + "泸西县": [ + 103.77, + 24.53 + ], + "元阳县": [ + 102.83, + 23.23 + ], + "红河县": [ + 102.42, + 23.37 + ], + "绿春县": [ + 102.4, + 23 + ], + "河口瑶族自治县": [ + 103.97, + 22.52 + ], + "文山壮族苗族自治州": [ + 104.25, + 23.37 + ], + "文山县": [ + 104.25, + 23.37 + ], + "砚山县": [ + 104.33, + 23.62 + ], + "西畴县": [ + 104.67, + 23.45 + ], + "麻栗坡县": [ + 104.7, + 23.12 + ], + "马关县": [ + 104.4, + 23.02 + ], + "丘北县": [ + 104.18, + 24.05 + ], + "广南县": [ + 105.07, + 24.05 + ], + "富宁县": [ + 105.62, + 23.63 + ], + "西双版纳傣族自治州": [ + 100.8, + 22.02 + ], + "景洪市": [ + 100.8, + 22.02 + ], + "勐海县": [ + 100.45, + 21.97 + ], + "勐腊县": [ + 101.57, + 21.48 + ], + "大理白族自治州": [ + 100.23, + 25.6 + ], + "大理市": [ + 100.23, + 25.6 + ], + "漾濞彝族自治县": [ + 99.95, + 25.67 + ], + "祥云县": [ + 100.55, + 25.48 + ], + "宾川县": [ + 100.58, + 25.83 + ], + "弥渡县": [ + 100.48, + 25.35 + ], + "南涧彝族自治县": [ + 100.52, + 25.05 + ], + "巍山彝族回族自治县": [ + 100.3, + 25.23 + ], + "永平县": [ + 99.53, + 25.47 + ], + "云龙县": [ + 99.37, + 25.88 + ], + "洱源县": [ + 99.95, + 26.12 + ], + "剑川县": [ + 99.9, + 26.53 + ], + "鹤庆县": [ + 100.18, + 26.57 + ], + "德宏傣族景颇族自治州": [ + 98.58, + 24.43 + ], + "瑞丽市": [ + 97.85, + 24.02 + ], + "潞西市": [ + 98.58, + 24.43 + ], + "梁河县": [ + 98.3, + 24.82 + ], + "盈江县": [ + 97.93, + 24.72 + ], + "陇川县": [ + 97.8, + 24.2 + ], + "怒江傈僳族自治州": [ + 98.85, + 25.85 + ], + "泸水县": [ + 98.85, + 25.85 + ], + "福贡县": [ + 98.87, + 26.9 + ], + "贡山独龙族怒族自治县": [ + 98.67, + 27.73 + ], + "兰坪白族普米族自治县": [ + 99.42, + 26.45 + ], + "迪庆藏族自治州": [ + 99.7, + 27.83 + ], + "香格里拉县": [ + 99.7, + 27.83 + ], + "德钦县": [ + 98.92, + 28.48 + ], + "维西傈僳族自治县": [ + 99.28, + 27.18 + ], + "拉萨市": [ + 91.13, + 29.65 + ], + "林周县": [ + 91.25, + 29.9 + ], + "当雄县": [ + 91.1, + 30.48 + ], + "尼木县": [ + 90.15, + 29.45 + ], + "曲水县": [ + 90.73, + 29.37 + ], + "堆龙德庆县": [ + 91, + 29.65 + ], + "达孜县": [ + 91.35, + 29.68 + ], + "墨竹工卡县": [ + 91.73, + 29.83 + ], + "昌都地区": [ + 97.18, + 31.13 + ], + "昌都县": [ + 97.18, + 31.13 + ], + "江达县": [ + 98.22, + 31.5 + ], + "贡觉县": [ + 98.27, + 30.87 + ], + "类乌齐县": [ + 96.6, + 31.22 + ], + "丁青县": [ + 95.6, + 31.42 + ], + "察雅县": [ + 97.57, + 30.65 + ], + "八宿县": [ + 96.92, + 30.05 + ], + "左贡县": [ + 97.85, + 29.67 + ], + "芒康县": [ + 98.6, + 29.68 + ], + "洛隆县": [ + 95.83, + 30.75 + ], + "边坝县": [ + 94.7, + 30.93 + ], + "山南地区": [ + 91.77, + 29.23 + ], + "乃东县": [ + 91.77, + 29.23 + ], + "扎囊县": [ + 91.33, + 29.25 + ], + "贡嘎县": [ + 90.98, + 29.3 + ], + "桑日县": [ + 92.02, + 29.27 + ], + "琼结县": [ + 91.68, + 29.03 + ], + "曲松县": [ + 92.2, + 29.07 + ], + "措美县": [ + 91.43, + 28.43 + ], + "洛扎县": [ + 90.87, + 28.38 + ], + "加查县": [ + 92.58, + 29.15 + ], + "隆子县": [ + 92.47, + 28.42 + ], + "错那县": [ + 91.95, + 28 + ], + "浪卡子县": [ + 90.4, + 28.97 + ], + "日喀则地区": [ + 88.88, + 29.27 + ], + "日喀则市": [ + 88.88, + 29.27 + ], + "南木林县": [ + 89.1, + 29.68 + ], + "江孜县": [ + 89.6, + 28.92 + ], + "定日县": [ + 87.12, + 28.67 + ], + "萨迦县": [ + 88.02, + 28.9 + ], + "拉孜县": [ + 87.63, + 29.08 + ], + "昂仁县": [ + 87.23, + 29.3 + ], + "谢通门县": [ + 88.27, + 29.43 + ], + "白朗县": [ + 89.27, + 29.12 + ], + "仁布县": [ + 89.83, + 29.23 + ], + "康马县": [ + 89.68, + 28.57 + ], + "定结县": [ + 87.77, + 28.37 + ], + "仲巴县": [ + 84.03, + 29.77 + ], + "亚东县": [ + 88.9, + 27.48 + ], + "吉隆县": [ + 85.3, + 28.85 + ], + "聂拉木县": [ + 85.98, + 28.17 + ], + "萨嘎县": [ + 85.23, + 29.33 + ], + "岗巴县": [ + 88.52, + 28.28 + ], + "那曲地区": [ + 92.07, + 31.48 + ], + "那曲县": [ + 92.07, + 31.48 + ], + "嘉黎县": [ + 93.25, + 30.65 + ], + "比如县": [ + 93.68, + 31.48 + ], + "聂荣县": [ + 92.3, + 32.12 + ], + "安多县": [ + 91.68, + 32.27 + ], + "申扎县": [ + 88.7, + 30.93 + ], + "索县": [ + 93.78, + 31.88 + ], + "班戈县": [ + 90.02, + 31.37 + ], + "巴青县": [ + 94.03, + 31.93 + ], + "尼玛县": [ + 87.23, + 31.78 + ], + "阿里地区": [ + 80.1, + 32.5 + ], + "普兰县": [ + 81.17, + 30.3 + ], + "札达县": [ + 79.8, + 31.48 + ], + "噶尔县": [ + 80.1, + 32.5 + ], + "日土县": [ + 79.72, + 33.38 + ], + "革吉县": [ + 81.12, + 32.4 + ], + "改则县": [ + 84.07, + 32.3 + ], + "措勤县": [ + 85.17, + 31.02 + ], + "林芝地区": [ + 94.37, + 29.68 + ], + "林芝县": [ + 94.37, + 29.68 + ], + "工布江达县": [ + 93.25, + 29.88 + ], + "米林县": [ + 94.22, + 29.22 + ], + "墨脱县": [ + 95.33, + 29.33 + ], + "波密县": [ + 95.77, + 29.87 + ], + "察隅县": [ + 97.47, + 28.67 + ], + "朗县": [ + 93.07, + 29.05 + ], + "西安市": [ + 108.93, + 34.27 + ], + "新城区": [ + 108.95, + 34.27 + ], + "碑林区": [ + 108.93, + 34.23 + ], + "莲湖区": [ + 108.93, + 34.27 + ], + "灞桥区": [ + 109.07, + 34.27 + ], + "未央区": [ + 108.93, + 34.28 + ], + "雁塔区": [ + 108.95, + 34.22 + ], + "阎良区": [ + 109.23, + 34.65 + ], + "临潼区": [ + 109.22, + 34.37 + ], + "长安区": [ + 108.93, + 34.17 + ], + "蓝田县": [ + 109.32, + 34.15 + ], + "周至县": [ + 108.2, + 34.17 + ], + "户县": [ + 108.6, + 34.1 + ], + "高陵县": [ + 109.08, + 34.53 + ], + "铜川市": [ + 108.93, + 34.9 + ], + "王益区": [ + 109.07, + 35.07 + ], + "印台区": [ + 109.1, + 35.1 + ], + "耀州区": [ + 108.98, + 34.92 + ], + "宜君县": [ + 109.12, + 35.4 + ], + "宝鸡市": [ + 107.13, + 34.37 + ], + "渭滨区": [ + 107.15, + 34.37 + ], + "金台区": [ + 107.13, + 34.38 + ], + "陈仓区": [ + 107.37, + 34.37 + ], + "凤翔县": [ + 107.38, + 34.52 + ], + "岐山县": [ + 107.62, + 34.45 + ], + "扶风县": [ + 107.87, + 34.37 + ], + "眉县": [ + 107.75, + 34.28 + ], + "陇县": [ + 106.85, + 34.9 + ], + "千阳县": [ + 107.13, + 34.65 + ], + "麟游县": [ + 107.78, + 34.68 + ], + "凤县": [ + 106.52, + 33.92 + ], + "太白县": [ + 107.32, + 34.07 + ], + "咸阳市": [ + 108.7, + 34.33 + ], + "秦都区": [ + 108.72, + 34.35 + ], + "杨凌区": [ + 108.07, + 34.28 + ], + "渭城区": [ + 108.73, + 34.33 + ], + "三原县": [ + 108.93, + 34.62 + ], + "泾阳县": [ + 108.83, + 34.53 + ], + "乾县": [ + 108.23, + 34.53 + ], + "礼泉县": [ + 108.42, + 34.48 + ], + "永寿县": [ + 108.13, + 34.7 + ], + "彬县": [ + 108.08, + 35.03 + ], + "长武县": [ + 107.78, + 35.2 + ], + "旬邑县": [ + 108.33, + 35.12 + ], + "淳化县": [ + 108.58, + 34.78 + ], + "武功县": [ + 108.2, + 34.27 + ], + "兴平市": [ + 108.48, + 34.3 + ], + "渭南市": [ + 109.5, + 34.5 + ], + "临渭区": [ + 109.48, + 34.5 + ], + "华县": [ + 109.77, + 34.52 + ], + "潼关县": [ + 110.23, + 34.55 + ], + "大荔县": [ + 109.93, + 34.8 + ], + "合阳县": [ + 110.15, + 35.23 + ], + "澄城县": [ + 109.93, + 35.18 + ], + "蒲城县": [ + 109.58, + 34.95 + ], + "白水县": [ + 109.58, + 35.18 + ], + "富平县": [ + 109.18, + 34.75 + ], + "韩城市": [ + 110.43, + 35.48 + ], + "华阴市": [ + 110.08, + 34.57 + ], + "延安市": [ + 109.48, + 36.6 + ], + "宝塔区": [ + 109.48, + 36.6 + ], + "延长县": [ + 110, + 36.58 + ], + "延川县": [ + 110.18, + 36.88 + ], + "子长县": [ + 109.67, + 37.13 + ], + "安塞县": [ + 109.32, + 36.87 + ], + "志丹县": [ + 108.77, + 36.82 + ], + "甘泉县": [ + 109.35, + 36.28 + ], + "富县": [ + 109.37, + 35.98 + ], + "洛川县": [ + 109.43, + 35.77 + ], + "宜川县": [ + 110.17, + 36.05 + ], + "黄龙县": [ + 109.83, + 35.58 + ], + "黄陵县": [ + 109.25, + 35.58 + ], + "汉中市": [ + 107.02, + 33.07 + ], + "汉台区": [ + 107.03, + 33.07 + ], + "南郑县": [ + 106.93, + 33 + ], + "城固县": [ + 107.33, + 33.15 + ], + "洋县": [ + 107.55, + 33.22 + ], + "西乡县": [ + 107.77, + 32.98 + ], + "勉县": [ + 106.67, + 33.15 + ], + "宁强县": [ + 106.25, + 32.83 + ], + "略阳县": [ + 106.15, + 33.33 + ], + "镇巴县": [ + 107.9, + 32.53 + ], + "留坝县": [ + 106.92, + 33.62 + ], + "佛坪县": [ + 107.98, + 33.53 + ], + "榆林市": [ + 109.73, + 38.28 + ], + "榆阳区": [ + 109.75, + 38.28 + ], + "神木县": [ + 110.5, + 38.83 + ], + "府谷县": [ + 111.07, + 39.03 + ], + "横山县": [ + 109.28, + 37.95 + ], + "靖边县": [ + 108.8, + 37.6 + ], + "定边县": [ + 107.6, + 37.58 + ], + "绥德县": [ + 110.25, + 37.5 + ], + "米脂县": [ + 110.18, + 37.75 + ], + "佳县": [ + 110.48, + 38.02 + ], + "吴堡县": [ + 110.73, + 37.45 + ], + "清涧县": [ + 110.12, + 37.08 + ], + "子洲县": [ + 110.03, + 37.62 + ], + "安康市": [ + 109.02, + 32.68 + ], + "汉滨区": [ + 109.02, + 32.68 + ], + "汉阴县": [ + 108.5, + 32.9 + ], + "石泉县": [ + 108.25, + 33.05 + ], + "宁陕县": [ + 108.32, + 33.32 + ], + "紫阳县": [ + 108.53, + 32.52 + ], + "岚皋县": [ + 108.9, + 32.32 + ], + "平利县": [ + 109.35, + 32.4 + ], + "镇坪县": [ + 109.52, + 31.88 + ], + "旬阳县": [ + 109.38, + 32.83 + ], + "白河县": [ + 110.1, + 32.82 + ], + "商洛市": [ + 109.93, + 33.87 + ], + "商州区": [ + 109.93, + 33.87 + ], + "洛南县": [ + 110.13, + 34.08 + ], + "丹凤县": [ + 110.33, + 33.7 + ], + "商南县": [ + 110.88, + 33.53 + ], + "山阳县": [ + 109.88, + 33.53 + ], + "镇安县": [ + 109.15, + 33.43 + ], + "柞水县": [ + 109.1, + 33.68 + ], + "兰州市": [ + 103.82, + 36.07 + ], + "城关区": [ + 103.83, + 36.05 + ], + "西固区": [ + 103.62, + 36.1 + ], + "红古区": [ + 102.87, + 36.33 + ], + "永登县": [ + 103.27, + 36.73 + ], + "皋兰县": [ + 103.95, + 36.33 + ], + "榆中县": [ + 104.12, + 35.85 + ], + "嘉峪关市": [ + 98.27, + 39.8 + ], + "金昌市": [ + 102.18, + 38.5 + ], + "金川区": [ + 102.18, + 38.5 + ], + "永昌县": [ + 101.97, + 38.25 + ], + "白银市": [ + 104.18, + 36.55 + ], + "白银区": [ + 104.18, + 36.55 + ], + "平川区": [ + 104.83, + 36.73 + ], + "靖远县": [ + 104.68, + 36.57 + ], + "会宁县": [ + 105.05, + 35.7 + ], + "景泰县": [ + 104.07, + 37.15 + ], + "天水市": [ + 105.72, + 34.58 + ], + "清水县": [ + 106.13, + 34.75 + ], + "秦安县": [ + 105.67, + 34.87 + ], + "甘谷县": [ + 105.33, + 34.73 + ], + "武山县": [ + 104.88, + 34.72 + ], + "张家川回族自治县": [ + 106.22, + 35 + ], + "武威市": [ + 102.63, + 37.93 + ], + "凉州区": [ + 102.63, + 37.93 + ], + "民勤县": [ + 103.08, + 38.62 + ], + "古浪县": [ + 102.88, + 37.47 + ], + "天祝藏族自治县": [ + 103.13, + 36.98 + ], + "张掖市": [ + 100.45, + 38.93 + ], + "甘州区": [ + 100.45, + 38.93 + ], + "肃南裕固族自治县": [ + 99.62, + 38.83 + ], + "民乐县": [ + 100.82, + 38.43 + ], + "临泽县": [ + 100.17, + 39.13 + ], + "高台县": [ + 99.82, + 39.38 + ], + "山丹县": [ + 101.08, + 38.78 + ], + "平凉市": [ + 106.67, + 35.55 + ], + "崆峒区": [ + 106.67, + 35.55 + ], + "泾川县": [ + 107.37, + 35.33 + ], + "灵台县": [ + 107.62, + 35.07 + ], + "崇信县": [ + 107.03, + 35.3 + ], + "华亭县": [ + 106.65, + 35.22 + ], + "庄浪县": [ + 106.05, + 35.2 + ], + "静宁县": [ + 105.72, + 35.52 + ], + "酒泉市": [ + 98.52, + 39.75 + ], + "肃州区": [ + 98.52, + 39.75 + ], + "金塔县": [ + 98.9, + 39.98 + ], + "肃北蒙古族自治县": [ + 94.88, + 39.52 + ], + "阿克塞哈萨克族自治县": [ + 94.33, + 39.63 + ], + "玉门市": [ + 97.05, + 40.28 + ], + "敦煌市": [ + 94.67, + 40.13 + ], + "庆阳市": [ + 107.63, + 35.73 + ], + "西峰区": [ + 107.63, + 35.73 + ], + "庆城县": [ + 107.88, + 36 + ], + "环县": [ + 107.3, + 36.58 + ], + "华池县": [ + 107.98, + 36.47 + ], + "合水县": [ + 108.02, + 35.82 + ], + "正宁县": [ + 108.37, + 35.5 + ], + "宁县": [ + 107.92, + 35.5 + ], + "镇原县": [ + 107.2, + 35.68 + ], + "定西市": [ + 104.62, + 35.58 + ], + "安定区": [ + 104.62, + 35.58 + ], + "通渭县": [ + 105.25, + 35.2 + ], + "陇西县": [ + 104.63, + 35 + ], + "渭源县": [ + 104.22, + 35.13 + ], + "临洮县": [ + 103.87, + 35.38 + ], + "漳县": [ + 104.47, + 34.85 + ], + "岷县": [ + 104.03, + 34.43 + ], + "陇南市": [ + 104.92, + 33.4 + ], + "武都区": [ + 104.92, + 33.4 + ], + "成县": [ + 105.72, + 33.73 + ], + "文县": [ + 104.68, + 32.95 + ], + "宕昌县": [ + 104.38, + 34.05 + ], + "康县": [ + 105.6, + 33.33 + ], + "西和县": [ + 105.3, + 34.02 + ], + "礼县": [ + 105.17, + 34.18 + ], + "徽县": [ + 106.08, + 33.77 + ], + "两当县": [ + 106.3, + 33.92 + ], + "临夏回族自治州": [ + 103.22, + 35.6 + ], + "临夏市": [ + 103.22, + 35.6 + ], + "临夏县": [ + 103, + 35.5 + ], + "康乐县": [ + 103.72, + 35.37 + ], + "永靖县": [ + 103.32, + 35.93 + ], + "广河县": [ + 103.58, + 35.48 + ], + "和政县": [ + 103.35, + 35.43 + ], + "东乡族自治县": [ + 103.4, + 35.67 + ], + "甘南藏族自治州": [ + 102.92, + 34.98 + ], + "合作市": [ + 102.92, + 34.98 + ], + "临潭县": [ + 103.35, + 34.7 + ], + "卓尼县": [ + 103.5, + 34.58 + ], + "舟曲县": [ + 104.37, + 33.78 + ], + "迭部县": [ + 103.22, + 34.05 + ], + "玛曲县": [ + 102.07, + 34 + ], + "碌曲县": [ + 102.48, + 34.58 + ], + "夏河县": [ + 102.52, + 35.2 + ], + "西宁市": [ + 101.78, + 36.62 + ], + "城东区": [ + 101.8, + 36.62 + ], + "城中区": [ + 101.78, + 36.62 + ], + "城西区": [ + 101.77, + 36.62 + ], + "城北区": [ + 101.77, + 36.67 + ], + "大通回族土族自治县": [ + 101.68, + 36.93 + ], + "湟中县": [ + 101.57, + 36.5 + ], + "湟源县": [ + 101.27, + 36.68 + ], + "海东地区": [ + 102.12, + 36.5 + ], + "平安县": [ + 102.12, + 36.5 + ], + "民和回族土族自治县": [ + 102.8, + 36.33 + ], + "乐都县": [ + 102.4, + 36.48 + ], + "互助土族自治县": [ + 101.95, + 36.83 + ], + "化隆回族自治县": [ + 102.27, + 36.1 + ], + "循化撒拉族自治县": [ + 102.48, + 35.85 + ], + "海北藏族自治州": [ + 100.9, + 36.97 + ], + "门源回族自治县": [ + 101.62, + 37.38 + ], + "祁连县": [ + 100.25, + 38.18 + ], + "海晏县": [ + 100.98, + 36.9 + ], + "刚察县": [ + 100.13, + 37.33 + ], + "黄南藏族自治州": [ + 102.02, + 35.52 + ], + "同仁县": [ + 102.02, + 35.52 + ], + "尖扎县": [ + 102.03, + 35.93 + ], + "泽库县": [ + 101.47, + 35.03 + ], + "河南蒙古族自治县": [ + 101.6, + 34.73 + ], + "海南藏族自治州": [ + 100.62, + 36.28 + ], + "共和县": [ + 100.62, + 36.28 + ], + "同德县": [ + 100.57, + 35.25 + ], + "贵德县": [ + 101.43, + 36.05 + ], + "兴海县": [ + 99.98, + 35.58 + ], + "贵南县": [ + 100.75, + 35.58 + ], + "果洛藏族自治州": [ + 100.23, + 34.48 + ], + "玛沁县": [ + 100.23, + 34.48 + ], + "班玛县": [ + 100.73, + 32.93 + ], + "甘德县": [ + 99.9, + 33.97 + ], + "达日县": [ + 99.65, + 33.75 + ], + "久治县": [ + 101.48, + 33.43 + ], + "玛多县": [ + 98.18, + 34.92 + ], + "玉树藏族自治州": [ + 97.02, + 33 + ], + "玉树县": [ + 97.02, + 33 + ], + "杂多县": [ + 95.3, + 32.9 + ], + "称多县": [ + 97.1, + 33.37 + ], + "治多县": [ + 95.62, + 33.85 + ], + "囊谦县": [ + 96.48, + 32.2 + ], + "曲麻莱县": [ + 95.8, + 34.13 + ], + "海西蒙古族藏族自治州": [ + 97.37, + 37.37 + ], + "格尔木市": [ + 94.9, + 36.42 + ], + "德令哈市": [ + 97.37, + 37.37 + ], + "乌兰县": [ + 98.48, + 36.93 + ], + "都兰县": [ + 98.08, + 36.3 + ], + "天峻县": [ + 99.02, + 37.3 + ], + "银川市": [ + 106.28, + 38.47 + ], + "兴庆区": [ + 106.28, + 38.48 + ], + "西夏区": [ + 106.18, + 38.48 + ], + "金凤区": [ + 106.25, + 38.47 + ], + "永宁县": [ + 106.25, + 38.28 + ], + "贺兰县": [ + 106.35, + 38.55 + ], + "灵武市": [ + 106.33, + 38.1 + ], + "石嘴山市": [ + 106.38, + 39.02 + ], + "大武口区": [ + 106.38, + 39.02 + ], + "惠农区": [ + 106.78, + 39.25 + ], + "平罗县": [ + 106.53, + 38.9 + ], + "吴忠市": [ + 106.2, + 37.98 + ], + "利通区": [ + 106.2, + 37.98 + ], + "盐池县": [ + 107.4, + 37.78 + ], + "同心县": [ + 105.92, + 36.98 + ], + "青铜峡市": [ + 106.07, + 38.02 + ], + "固原市": [ + 106.28, + 36 + ], + "原州区": [ + 106.28, + 36 + ], + "西吉县": [ + 105.73, + 35.97 + ], + "隆德县": [ + 106.12, + 35.62 + ], + "泾源县": [ + 106.33, + 35.48 + ], + "彭阳县": [ + 106.63, + 35.85 + ], + "中卫市": [ + 105.18, + 37.52 + ], + "沙坡头区": [ + 105.18, + 37.52 + ], + "中宁县": [ + 105.67, + 37.48 + ], + "海原县": [ + 105.65, + 36.57 + ], + "乌鲁木齐市": [ + 87.62, + 43.82 + ], + "天山区": [ + 87.65, + 43.78 + ], + "沙依巴克区": [ + 87.6, + 43.78 + ], + "新市区": [ + 87.6, + 43.85 + ], + "水磨沟区": [ + 87.63, + 43.83 + ], + "头屯河区": [ + 87.42, + 43.87 + ], + "达坂城区": [ + 88.3, + 43.35 + ], + "乌鲁木齐县": [ + 87.6, + 43.8 + ], + "克拉玛依市": [ + 84.87, + 45.6 + ], + "独山子区": [ + 84.85, + 44.32 + ], + "克拉玛依区": [ + 84.87, + 45.6 + ], + "白碱滩区": [ + 85.13, + 45.7 + ], + "乌尔禾区": [ + 85.68, + 46.08 + ], + "吐鲁番地区": [ + 89.17, + 42.95 + ], + "吐鲁番市": [ + 89.17, + 42.95 + ], + "鄯善县": [ + 90.22, + 42.87 + ], + "托克逊县": [ + 88.65, + 42.78 + ], + "哈密地区": [ + 93.52, + 42.83 + ], + "哈密市": [ + 93.52, + 42.83 + ], + "伊吾县": [ + 94.7, + 43.25 + ], + "昌吉回族自治州": [ + 87.3, + 44.02 + ], + "昌吉市": [ + 87.3, + 44.02 + ], + "阜康市": [ + 87.98, + 44.15 + ], + "米泉市": [ + 87.65, + 43.97 + ], + "呼图壁县": [ + 86.9, + 44.18 + ], + "玛纳斯县": [ + 86.22, + 44.3 + ], + "奇台县": [ + 89.58, + 44.02 + ], + "吉木萨尔县": [ + 89.18, + 44 + ], + "木垒哈萨克自治县": [ + 90.28, + 43.83 + ], + "博尔塔拉蒙古自治州": [ + 82.07, + 44.9 + ], + "博乐市": [ + 82.07, + 44.9 + ], + "精河县": [ + 82.88, + 44.6 + ], + "温泉县": [ + 81.03, + 44.97 + ], + "巴音郭楞蒙古自治州": [ + 86.15, + 41.77 + ], + "库尔勒市": [ + 86.15, + 41.77 + ], + "轮台县": [ + 84.27, + 41.78 + ], + "尉犁县": [ + 86.25, + 41.33 + ], + "若羌县": [ + 88.17, + 39.02 + ], + "且末县": [ + 85.53, + 38.13 + ], + "焉耆回族自治县": [ + 86.57, + 42.07 + ], + "和静县": [ + 86.4, + 42.32 + ], + "和硕县": [ + 86.87, + 42.27 + ], + "博湖县": [ + 86.63, + 41.98 + ], + "阿克苏地区": [ + 80.27, + 41.17 + ], + "阿克苏市": [ + 80.27, + 41.17 + ], + "温宿县": [ + 80.23, + 41.28 + ], + "库车县": [ + 82.97, + 41.72 + ], + "沙雅县": [ + 82.78, + 41.22 + ], + "新和县": [ + 82.6, + 41.55 + ], + "拜城县": [ + 81.87, + 41.8 + ], + "乌什县": [ + 79.23, + 41.22 + ], + "阿瓦提县": [ + 80.38, + 40.63 + ], + "柯坪县": [ + 79.05, + 40.5 + ], + "阿图什市": [ + 76.17, + 39.72 + ], + "阿克陶县": [ + 75.95, + 39.15 + ], + "阿合奇县": [ + 78.45, + 40.93 + ], + "乌恰县": [ + 75.25, + 39.72 + ], + "喀什地区": [ + 75.98, + 39.47 + ], + "喀什市": [ + 75.98, + 39.47 + ], + "疏附县": [ + 75.85, + 39.38 + ], + "疏勒县": [ + 76.05, + 39.4 + ], + "英吉沙县": [ + 76.17, + 38.93 + ], + "泽普县": [ + 77.27, + 38.18 + ], + "莎车县": [ + 77.23, + 38.42 + ], + "叶城县": [ + 77.42, + 37.88 + ], + "麦盖提县": [ + 77.65, + 38.9 + ], + "岳普湖县": [ + 76.77, + 39.23 + ], + "伽师县": [ + 76.73, + 39.5 + ], + "巴楚县": [ + 78.55, + 39.78 + ], + "和田地区": [ + 79.92, + 37.12 + ], + "和田市": [ + 79.92, + 37.12 + ], + "和田县": [ + 79.93, + 37.1 + ], + "墨玉县": [ + 79.73, + 37.27 + ], + "皮山县": [ + 78.28, + 37.62 + ], + "洛浦县": [ + 80.18, + 37.07 + ], + "策勒县": [ + 80.8, + 37 + ], + "于田县": [ + 81.67, + 36.85 + ], + "民丰县": [ + 82.68, + 37.07 + ], + "伊犁哈萨克自治州": [ + 81.32, + 43.92 + ], + "伊宁市": [ + 81.32, + 43.92 + ], + "奎屯市": [ + 84.9, + 44.42 + ], + "伊宁县": [ + 81.52, + 43.98 + ], + "察布查尔锡伯自治县": [ + 81.15, + 43.83 + ], + "霍城县": [ + 80.88, + 44.05 + ], + "巩留县": [ + 82.23, + 43.48 + ], + "新源县": [ + 83.25, + 43.43 + ], + "昭苏县": [ + 81.13, + 43.15 + ], + "特克斯县": [ + 81.83, + 43.22 + ], + "尼勒克县": [ + 82.5, + 43.78 + ], + "塔城地区": [ + 82.98, + 46.75 + ], + "塔城市": [ + 82.98, + 46.75 + ], + "乌苏市": [ + 84.68, + 44.43 + ], + "额敏县": [ + 83.63, + 46.53 + ], + "沙湾县": [ + 85.62, + 44.33 + ], + "托里县": [ + 83.6, + 45.93 + ], + "裕民县": [ + 82.98, + 46.2 + ], + "和布克赛尔蒙古自治县": [ + 85.72, + 46.8 + ], + "阿勒泰地区": [ + 88.13, + 47.85 + ], + "阿勒泰市": [ + 88.13, + 47.85 + ], + "布尔津县": [ + 86.85, + 47.7 + ], + "富蕴县": [ + 89.52, + 47 + ], + "福海县": [ + 87.5, + 47.12 + ], + "哈巴河县": [ + 86.42, + 48.07 + ], + "青河县": [ + 90.38, + 46.67 + ], + "吉木乃县": [ + 85.88, + 47.43 + ], + "石河子市": [ + 86.03, + 44.3 + ], + "阿拉尔市": [ + 81.28, + 40.55 + ], + "图木舒克市": [ + 79.13, + 39.85 + ], + "五家渠市": [ + 87.53, + 44.17 + ], + "台北市": [ + 121.5, + 25.03 + ], + "高雄市": [ + 120.28, + 22.62 + ], + "基隆市": [ + 121.73, + 25.13 + ], + "台中市": [ + 120.67, + 24.15 + ], + "台南市": [ + 120.2, + 23 + ], + "新竹市": [ + 120.95, + 24.82 + ], + "嘉义市": [ + 120.43, + 23.48 + ], + "台北县": [ + 121.47, + 25.02 + ], + "宜兰县": [ + 121.75, + 24.77 + ], + "桃园县": [ + 121.3, + 24.97 + ], + "苗栗县": [ + 120.8, + 24.53 + ], + "台中县": [ + 120.72, + 24.25 + ], + "彰化县": [ + 120.53, + 24.08 + ], + "南投县": [ + 120.67, + 23.92 + ], + "云林县": [ + 120.53, + 23.72 + ], + "台南县": [ + 120.32, + 23.32 + ], + "高雄县": [ + 120.37, + 22.63 + ], + "屏东县": [ + 120.48, + 22.67 + ], + "台东县": [ + 121.15, + 22.75 + ], + "花莲县": [ + 121.6, + 23.98 + ], + "澎湖县": [ + 119.58, + 23.58 + ], + "北京": [ + 116.407526, + 39.904030 + ], + "天津": [ + 117.200983, + 39.084158 + ], + "河北": [ + 114.468664, + 38.037057 + ], + "山西": [ + 112.562398, + 37.873531 + ], + "内蒙古": [ + 111.765617, + 40.817498 + ], + "辽宁": [ + 123.429440, + 41.835441 + ], + "吉林": [ + 125.325990, + 43.896536 + ], + "黑龙江": [ + 126.661669, + 45.742347 + ], + "上海": [ + 121.473701, + 31.230416 + ], + "江苏": [ + 118.763232, + 32.061707 + ], + "浙江": [ + 120.152791, + 30.267446 + ], + "安徽": [ + 117.284922, + 31.861184 + ], + "福建": [ + 119.295144, + 26.100779 + ], + "江西": [ + 115.909228, + 28.675696 + ], + "山东": [ + 117.020359, + 36.668530 + ], + "河南": [ + 113.753602, + 34.765515 + ], + "湖北": [ + 114.341861, + 30.546498 + ], + "湖南": [ + 112.983810, + 28.112444 + ], + "广东": [ + 113.266530, + 23.132191 + ], + "广西": [ + 108.327546, + 22.815478 + ], + "海南": [ + 110.349228, + 20.017377 + ], + "重庆": [ + 106.551556, + 29.563009 + ], + "四川": [ + 104.075931, + 30.651651 + ], + "贵州": [ + 106.707410, + 26.598194 + ], + "云南": [ + 102.710002, + 25.045806 + ], + "西藏": [ + 91.117212, + 29.646922 + ], + "陕西": [ + 108.954239, + 34.265472 + ], + "甘肃": [ + 103.826308, + 36.059421 + ], + "青海": [ + 101.780199, + 36.620901 + ], + "宁夏": [ + 106.258754, + 38.471317 + ], + "新疆": [ + 87.627704, + 43.793026 + ], + "香港": [ + 114.173355, + 22.320048 + ], + "澳门": [ + 113.549090, + 22.198951 + ], + "台湾": [ + 121.509062, + 25.044332 + ] +} \ No newline at end of file diff --git a/test/fixtures/img.png b/test/fixtures/img.png new file mode 100644 index 0000000000000000000000000000000000000000..4a99260ced1e44af1832dc456bb68e7d593ca352 GIT binary patch literal 443277 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ⅆx<0pEocdv`e_Z;^%!<_Ub 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

SD8U15VOW^#j)R$tl` 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%0D5sAr5kd!|SWPW8{$;szgdx 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%0ADTvrPQsl{8J!gQ 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 z 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

1xG9N<#r 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

}Ml6YBR44K^4bWT$$ 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�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 N002ovPDHLkV1gn#sDl6i literal 0 HcmV?d00001 diff --git a/test/fixtures/img1.jpg b/test/fixtures/img1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9eb8f0a40125b3b08a25e2ddbf0b8634ffadb6a7 GIT binary patch literal 140536 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


>r5mesQCo80drp{R0{0`~$V< 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/les-miserables.json b/test/fixtures/les-miserables.json new file mode 100644 index 000000000..0c3845b4f --- /dev/null +++ b/test/fixtures/les-miserables.json @@ -0,0 +1,2383 @@ +{ + "categories": [ + { + "name": "类目0" + }, + { + "name": "类目1" + }, + { + "name": "类目2" + }, + { + "name": "类目3" + }, + { + "name": "类目4" + }, + { + "name": "类目5" + }, + { + "name": "类目6" + }, + { + "name": "类目7" + }, + { + "name": "类目8" + } + ], + "nodes": [ + { + "id": "0", + "name": "Myriel", + "symbolSize": 19.12381, + "x": -266.82776, + "y": 299.6904, + "value": 28.685715, + "label": { + "normal": { + "show": true + } + }, + "category": 0 + }, + { + "id": "1", + "name": "Napoleon", + "symbolSize": 2.6666666666666665, + "x": -418.08344, + "y": 446.8853, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 0 + }, + { + "id": "2", + "name": "MlleBaptistine", + "symbolSize": 6.323809333333333, + "x": -212.76357, + "y": 245.29176, + "value": 9.485714, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "3", + "name": "MmeMagloire", + "symbolSize": 6.323809333333333, + "x": -242.82404, + "y": 235.26283, + "value": 9.485714, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "4", + "name": "CountessDeLo", + "symbolSize": 2.6666666666666665, + "x": -379.30386, + "y": 429.06424, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 0 + }, + { + "id": "5", + "name": "Geborand", + "symbolSize": 2.6666666666666665, + "x": -417.26337, + "y": 406.03506, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 0 + }, + { + "id": "6", + "name": "Champtercier", + "symbolSize": 2.6666666666666665, + "x": -332.6012, + "y": 485.16974, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 0 + }, + { + "id": "7", + "name": "Cravatte", + "symbolSize": 2.6666666666666665, + "x": -382.69568, + "y": 475.09113, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 0 + }, + { + "id": "8", + "name": "Count", + "symbolSize": 2.6666666666666665, + "x": -320.384, + "y": 387.17325, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 0 + }, + { + "id": "9", + "name": "OldMan", + "symbolSize": 2.6666666666666665, + "x": -344.39832, + "y": 451.16772, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 0 + }, + { + "id": "10", + "name": "Labarre", + "symbolSize": 2.6666666666666665, + "x": -89.34107, + "y": 234.56128, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "11", + "name": "Valjean", + "symbolSize": 66.66666666666667, + "x": -87.93029, + "y": -6.8120565, + "value": 100, + "label": { + "normal": { + "show": true + } + }, + "category": 1 + }, + { + "id": "12", + "name": "Marguerite", + "symbolSize": 4.495239333333333, + "x": -339.77908, + "y": -184.69139, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "13", + "name": "MmeDeR", + "symbolSize": 2.6666666666666665, + "x": -194.31313, + "y": 178.55301, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "14", + "name": "Isabeau", + "symbolSize": 2.6666666666666665, + "x": -158.05168, + "y": 201.99768, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "15", + "name": "Gervais", + "symbolSize": 2.6666666666666665, + "x": -127.701546, + "y": 242.55057, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "16", + "name": "Tholomyes", + "symbolSize": 17.295237333333333, + "x": -385.2226, + "y": -393.5572, + "value": 25.942856, + "label": { + "normal": { + "show": true + } + }, + "category": 2 + }, + { + "id": "17", + "name": "Listolier", + "symbolSize": 13.638097333333334, + "x": -516.55884, + "y": -393.98975, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 2 + }, + { + "id": "18", + "name": "Fameuil", + "symbolSize": 13.638097333333334, + "x": -464.79382, + "y": -493.57944, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 2 + }, + { + "id": "19", + "name": "Blacheville", + "symbolSize": 13.638097333333334, + "x": -515.1624, + "y": -456.9891, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 2 + }, + { + "id": "20", + "name": "Favourite", + "symbolSize": 13.638097333333334, + "x": -408.12122, + "y": -464.5048, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 2 + }, + { + "id": "21", + "name": "Dahlia", + "symbolSize": 13.638097333333334, + "x": -456.44113, + "y": -425.13303, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 2 + }, + { + "id": "22", + "name": "Zephine", + "symbolSize": 13.638097333333334, + "x": -459.1107, + "y": -362.5133, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 2 + }, + { + "id": "23", + "name": "Fantine", + "symbolSize": 28.266666666666666, + "x": -313.42786, + "y": -289.44803, + "value": 42.4, + "label": { + "normal": { + "show": true + } + }, + "category": 2 + }, + { + "id": "24", + "name": "MmeThenardier", + "symbolSize": 20.95238266666667, + "x": 4.6313396, + "y": -273.8517, + "value": 31.428574, + "label": { + "normal": { + "show": true + } + }, + "category": 7 + }, + { + "id": "25", + "name": "Thenardier", + "symbolSize": 30.095235333333335, + "x": 82.80825, + "y": -203.1144, + "value": 45.142853, + "label": { + "normal": { + "show": true + } + }, + "category": 7 + }, + { + "id": "26", + "name": "Cosette", + "symbolSize": 20.95238266666667, + "x": 78.64646, + "y": -31.512747, + "value": 31.428574, + "label": { + "normal": { + "show": true + } + }, + "category": 6 + }, + { + "id": "27", + "name": "Javert", + "symbolSize": 31.923806666666668, + "x": -81.46074, + "y": -204.20204, + "value": 47.88571, + "label": { + "normal": { + "show": true + } + }, + "category": 7 + }, + { + "id": "28", + "name": "Fauchelevent", + "symbolSize": 8.152382000000001, + "x": -225.73984, + "y": 82.41631, + "value": 12.228573, + "label": { + "normal": { + "show": false + } + }, + "category": 4 + }, + { + "id": "29", + "name": "Bamatabois", + "symbolSize": 15.466666666666667, + "x": -385.6842, + "y": -20.206686, + "value": 23.2, + "label": { + "normal": { + "show": true + } + }, + "category": 3 + }, + { + "id": "30", + "name": "Perpetue", + "symbolSize": 4.495239333333333, + "x": -403.92447, + "y": -197.69823, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 2 + }, + { + "id": "31", + "name": "Simplice", + "symbolSize": 8.152382000000001, + "x": -281.4253, + "y": -158.45137, + "value": 12.228573, + "label": { + "normal": { + "show": false + } + }, + "category": 2 + }, + { + "id": "32", + "name": "Scaufflaire", + "symbolSize": 2.6666666666666665, + "x": -122.41348, + "y": 210.37503, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "33", + "name": "Woman1", + "symbolSize": 4.495239333333333, + "x": -234.6001, + "y": -113.15067, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "34", + "name": "Judge", + "symbolSize": 11.809524666666666, + "x": -387.84915, + "y": 58.7059, + "value": 17.714287, + "label": { + "normal": { + "show": true + } + }, + "category": 3 + }, + { + "id": "35", + "name": "Champmathieu", + "symbolSize": 11.809524666666666, + "x": -338.2307, + "y": 87.48405, + "value": 17.714287, + "label": { + "normal": { + "show": true + } + }, + "category": 3 + }, + { + "id": "36", + "name": "Brevet", + "symbolSize": 11.809524666666666, + "x": -453.26874, + "y": 58.94648, + "value": 17.714287, + "label": { + "normal": { + "show": true + } + }, + "category": 3 + }, + { + "id": "37", + "name": "Chenildieu", + "symbolSize": 11.809524666666666, + "x": -386.44904, + "y": 140.05937, + "value": 17.714287, + "label": { + "normal": { + "show": true + } + }, + "category": 3 + }, + { + "id": "38", + "name": "Cochepaille", + "symbolSize": 11.809524666666666, + "x": -446.7876, + "y": 123.38005, + "value": 17.714287, + "label": { + "normal": { + "show": true + } + }, + "category": 3 + }, + { + "id": "39", + "name": "Pontmercy", + "symbolSize": 6.323809333333333, + "x": 336.49738, + "y": -269.55914, + "value": 9.485714, + "label": { + "normal": { + "show": false + } + }, + "category": 6 + }, + { + "id": "40", + "name": "Boulatruelle", + "symbolSize": 2.6666666666666665, + "x": 29.187843, + "y": -460.13132, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 7 + }, + { + "id": "41", + "name": "Eponine", + "symbolSize": 20.95238266666667, + "x": 238.36697, + "y": -210.00926, + "value": 31.428574, + "label": { + "normal": { + "show": true + } + }, + "category": 7 + }, + { + "id": "42", + "name": "Anzelma", + "symbolSize": 6.323809333333333, + "x": 189.69513, + "y": -346.50662, + "value": 9.485714, + "label": { + "normal": { + "show": false + } + }, + "category": 7 + }, + { + "id": "43", + "name": "Woman2", + "symbolSize": 6.323809333333333, + "x": -187.00418, + "y": -145.02663, + "value": 9.485714, + "label": { + "normal": { + "show": false + } + }, + "category": 6 + }, + { + "id": "44", + "name": "MotherInnocent", + "symbolSize": 4.495239333333333, + "x": -252.99521, + "y": 129.87549, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 4 + }, + { + "id": "45", + "name": "Gribier", + "symbolSize": 2.6666666666666665, + "x": -296.07935, + "y": 163.11964, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 4 + }, + { + "id": "46", + "name": "Jondrette", + "symbolSize": 2.6666666666666665, + "x": 550.3201, + "y": 522.4031, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 5 + }, + { + "id": "47", + "name": "MmeBurgon", + "symbolSize": 4.495239333333333, + "x": 488.13535, + "y": 356.8573, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 5 + }, + { + "id": "48", + "name": "Gavroche", + "symbolSize": 41.06667066666667, + "x": 387.89572, + "y": 110.462326, + "value": 61.600006, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "49", + "name": "Gillenormand", + "symbolSize": 13.638097333333334, + "x": 126.4831, + "y": 68.10622, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 6 + }, + { + "id": "50", + "name": "Magnon", + "symbolSize": 4.495239333333333, + "x": 127.07365, + "y": -113.05923, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 6 + }, + { + "id": "51", + "name": "MlleGillenormand", + "symbolSize": 13.638097333333334, + "x": 162.63559, + "y": 117.6565, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 6 + }, + { + "id": "52", + "name": "MmePontmercy", + "symbolSize": 4.495239333333333, + "x": 353.66415, + "y": -205.89165, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 6 + }, + { + "id": "53", + "name": "MlleVaubois", + "symbolSize": 2.6666666666666665, + "x": 165.43939, + "y": 339.7736, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 6 + }, + { + "id": "54", + "name": "LtGillenormand", + "symbolSize": 8.152382000000001, + "x": 137.69348, + "y": 196.1069, + "value": 12.228573, + "label": { + "normal": { + "show": false + } + }, + "category": 6 + }, + { + "id": "55", + "name": "Marius", + "symbolSize": 35.58095333333333, + "x": 206.44687, + "y": -13.805411, + "value": 53.37143, + "label": { + "normal": { + "show": true + } + }, + "category": 6 + }, + { + "id": "56", + "name": "BaronessT", + "symbolSize": 4.495239333333333, + "x": 194.82993, + "y": 224.78036, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 6 + }, + { + "id": "57", + "name": "Mabeuf", + "symbolSize": 20.95238266666667, + "x": 597.6618, + "y": 135.18481, + "value": 31.428574, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "58", + "name": "Enjolras", + "symbolSize": 28.266666666666666, + "x": 355.78366, + "y": -74.882454, + "value": 42.4, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "59", + "name": "Combeferre", + "symbolSize": 20.95238266666667, + "x": 515.2961, + "y": -46.167564, + "value": 31.428574, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "60", + "name": "Prouvaire", + "symbolSize": 17.295237333333333, + "x": 614.29285, + "y": -69.3104, + "value": 25.942856, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "61", + "name": "Feuilly", + "symbolSize": 20.95238266666667, + "x": 550.1917, + "y": -128.17537, + "value": 31.428574, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "62", + "name": "Courfeyrac", + "symbolSize": 24.609526666666667, + "x": 436.17184, + "y": -12.7286825, + "value": 36.91429, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "63", + "name": "Bahorel", + "symbolSize": 22.780953333333333, + "x": 602.55225, + "y": 16.421427, + "value": 34.17143, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "64", + "name": "Bossuet", + "symbolSize": 24.609526666666667, + "x": 455.81955, + "y": -115.45826, + "value": 36.91429, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "65", + "name": "Joly", + "symbolSize": 22.780953333333333, + "x": 516.40784, + "y": 47.242233, + "value": 34.17143, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "66", + "name": "Grantaire", + "symbolSize": 19.12381, + "x": 646.4313, + "y": -151.06331, + "value": 28.685715, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + }, + { + "id": "67", + "name": "MotherPlutarch", + "symbolSize": 2.6666666666666665, + "x": 668.9568, + "y": 204.65488, + "value": 4, + "label": { + "normal": { + "show": false + } + }, + "category": 8 + }, + { + "id": "68", + "name": "Gueulemer", + "symbolSize": 19.12381, + "x": 78.4799, + "y": -347.15146, + "value": 28.685715, + "label": { + "normal": { + "show": true + } + }, + "category": 7 + }, + { + "id": "69", + "name": "Babet", + "symbolSize": 19.12381, + "x": 150.35959, + "y": -298.50797, + "value": 28.685715, + "label": { + "normal": { + "show": true + } + }, + "category": 7 + }, + { + "id": "70", + "name": "Claquesous", + "symbolSize": 19.12381, + "x": 137.3717, + "y": -410.2809, + "value": 28.685715, + "label": { + "normal": { + "show": true + } + }, + "category": 7 + }, + { + "id": "71", + "name": "Montparnasse", + "symbolSize": 17.295237333333333, + "x": 234.87747, + "y": -400.85983, + "value": 25.942856, + "label": { + "normal": { + "show": true + } + }, + "category": 7 + }, + { + "id": "72", + "name": "Toussaint", + "symbolSize": 6.323809333333333, + "x": 40.942253, + "y": 113.78272, + "value": 9.485714, + "label": { + "normal": { + "show": false + } + }, + "category": 1 + }, + { + "id": "73", + "name": "Child1", + "symbolSize": 4.495239333333333, + "x": 437.939, + "y": 291.58234, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 8 + }, + { + "id": "74", + "name": "Child2", + "symbolSize": 4.495239333333333, + "x": 466.04922, + "y": 283.3606, + "value": 6.742859, + "label": { + "normal": { + "show": false + } + }, + "category": 8 + }, + { + "id": "75", + "name": "Brujon", + "symbolSize": 13.638097333333334, + "x": 238.79364, + "y": -314.06345, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 7 + }, + { + "id": "76", + "name": "MmeHucheloup", + "symbolSize": 13.638097333333334, + "x": 712.18353, + "y": 4.8131495, + "value": 20.457146, + "label": { + "normal": { + "show": true + } + }, + "category": 8 + } + ], + "links": [ + { + "id": "0", + "source": "1", + "target": "0" + }, + { + "id": "1", + "source": "2", + "target": "0" + }, + { + "id": "2", + "source": "3", + "target": "0" + }, + { + "id": "3", + "source": "3", + "target": "2" + }, + { + "id": "4", + "source": "4", + "target": "0" + }, + { + "id": "5", + "source": "5", + "target": "0" + }, + { + "id": "6", + "source": "6", + "target": "0" + }, + { + "id": "7", + "source": "7", + "target": "0" + }, + { + "id": "8", + "source": "8", + "target": "0" + }, + { + "id": "9", + "source": "9", + "target": "0" + }, + { + "id": "13", + "source": "11", + "target": "0" + }, + { + "id": null, + "source": "11", + "target": "2" + }, + { + "id": "11", + "source": "11", + "target": "3" + }, + { + "id": "10", + "source": "11", + "target": "10" + }, + { + "id": "14", + "source": "12", + "target": "11" + }, + { + "id": "15", + "source": "13", + "target": "11" + }, + { + "id": "16", + "source": "14", + "target": "11" + }, + { + "id": "17", + "source": "15", + "target": "11" + }, + { + "id": "18", + "source": "17", + "target": "16" + }, + { + "id": "19", + "source": "18", + "target": "16" + }, + { + "id": "20", + "source": "18", + "target": "17" + }, + { + "id": "21", + "source": "19", + "target": "16" + }, + { + "id": "22", + "source": "19", + "target": "17" + }, + { + "id": "23", + "source": "19", + "target": "18" + }, + { + "id": "24", + "source": "20", + "target": "16" + }, + { + "id": "25", + "source": "20", + "target": "17" + }, + { + "id": "26", + "source": "20", + "target": "18" + }, + { + "id": "27", + "source": "20", + "target": "19" + }, + { + "id": "28", + "source": "21", + "target": "16" + }, + { + "id": "29", + "source": "21", + "target": "17" + }, + { + "id": "30", + "source": "21", + "target": "18" + }, + { + "id": "31", + "source": "21", + "target": "19" + }, + { + "id": "32", + "source": "21", + "target": "20" + }, + { + "id": "33", + "source": "22", + "target": "16" + }, + { + "id": "34", + "source": "22", + "target": "17" + }, + { + "id": "35", + "source": "22", + "target": "18" + }, + { + "id": "36", + "source": "22", + "target": "19" + }, + { + "id": "37", + "source": "22", + "target": "20" + }, + { + "id": "38", + "source": "22", + "target": "21" + }, + { + "id": "47", + "source": "23", + "target": "11" + }, + { + "id": "46", + "source": "23", + "target": "12" + }, + { + "id": "39", + "source": "23", + "target": "16" + }, + { + "id": "40", + "source": "23", + "target": "17" + }, + { + "id": "41", + "source": "23", + "target": "18" + }, + { + "id": "42", + "source": "23", + "target": "19" + }, + { + "id": "43", + "source": "23", + "target": "20" + }, + { + "id": "44", + "source": "23", + "target": "21" + }, + { + "id": "45", + "source": "23", + "target": "22" + }, + { + "id": null, + "source": "24", + "target": "11" + }, + { + "id": "48", + "source": "24", + "target": "23" + }, + { + "id": "52", + "source": "25", + "target": "11" + }, + { + "id": "51", + "source": "25", + "target": "23" + }, + { + "id": "50", + "source": "25", + "target": "24" + }, + { + "id": null, + "source": "26", + "target": "11" + }, + { + "id": null, + "source": "26", + "target": "16" + }, + { + "id": "53", + "source": "26", + "target": "24" + }, + { + "id": "56", + "source": "26", + "target": "25" + }, + { + "id": "57", + "source": "27", + "target": "11" + }, + { + "id": "58", + "source": "27", + "target": "23" + }, + { + "id": null, + "source": "27", + "target": "24" + }, + { + "id": "59", + "source": "27", + "target": "25" + }, + { + "id": "61", + "source": "27", + "target": "26" + }, + { + "id": "62", + "source": "28", + "target": "11" + }, + { + "id": "63", + "source": "28", + "target": "27" + }, + { + "id": "66", + "source": "29", + "target": "11" + }, + { + "id": "64", + "source": "29", + "target": "23" + }, + { + "id": "65", + "source": "29", + "target": "27" + }, + { + "id": "67", + "source": "30", + "target": "23" + }, + { + "id": null, + "source": "31", + "target": "11" + }, + { + "id": null, + "source": "31", + "target": "23" + }, + { + "id": null, + "source": "31", + "target": "27" + }, + { + "id": "68", + "source": "31", + "target": "30" + }, + { + "id": "72", + "source": "32", + "target": "11" + }, + { + "id": "73", + "source": "33", + "target": "11" + }, + { + "id": "74", + "source": "33", + "target": "27" + }, + { + "id": "75", + "source": "34", + "target": "11" + }, + { + "id": "76", + "source": "34", + "target": "29" + }, + { + "id": "77", + "source": "35", + "target": "11" + }, + { + "id": null, + "source": "35", + "target": "29" + }, + { + "id": "78", + "source": "35", + "target": "34" + }, + { + "id": "82", + "source": "36", + "target": "11" + }, + { + "id": "83", + "source": "36", + "target": "29" + }, + { + "id": "80", + "source": "36", + "target": "34" + }, + { + "id": "81", + "source": "36", + "target": "35" + }, + { + "id": "87", + "source": "37", + "target": "11" + }, + { + "id": "88", + "source": "37", + "target": "29" + }, + { + "id": "84", + "source": "37", + "target": "34" + }, + { + "id": "85", + "source": "37", + "target": "35" + }, + { + "id": "86", + "source": "37", + "target": "36" + }, + { + "id": "93", + "source": "38", + "target": "11" + }, + { + "id": "94", + "source": "38", + "target": "29" + }, + { + "id": "89", + "source": "38", + "target": "34" + }, + { + "id": "90", + "source": "38", + "target": "35" + }, + { + "id": "91", + "source": "38", + "target": "36" + }, + { + "id": "92", + "source": "38", + "target": "37" + }, + { + "id": "95", + "source": "39", + "target": "25" + }, + { + "id": "96", + "source": "40", + "target": "25" + }, + { + "id": "97", + "source": "41", + "target": "24" + }, + { + "id": "98", + "source": "41", + "target": "25" + }, + { + "id": "101", + "source": "42", + "target": "24" + }, + { + "id": "100", + "source": "42", + "target": "25" + }, + { + "id": "99", + "source": "42", + "target": "41" + }, + { + "id": "102", + "source": "43", + "target": "11" + }, + { + "id": "103", + "source": "43", + "target": "26" + }, + { + "id": "104", + "source": "43", + "target": "27" + }, + { + "id": null, + "source": "44", + "target": "11" + }, + { + "id": "105", + "source": "44", + "target": "28" + }, + { + "id": "107", + "source": "45", + "target": "28" + }, + { + "id": "108", + "source": "47", + "target": "46" + }, + { + "id": "112", + "source": "48", + "target": "11" + }, + { + "id": "110", + "source": "48", + "target": "25" + }, + { + "id": "111", + "source": "48", + "target": "27" + }, + { + "id": "109", + "source": "48", + "target": "47" + }, + { + "id": null, + "source": "49", + "target": "11" + }, + { + "id": "113", + "source": "49", + "target": "26" + }, + { + "id": null, + "source": "50", + "target": "24" + }, + { + "id": "115", + "source": "50", + "target": "49" + }, + { + "id": "119", + "source": "51", + "target": "11" + }, + { + "id": "118", + "source": "51", + "target": "26" + }, + { + "id": "117", + "source": "51", + "target": "49" + }, + { + "id": null, + "source": "52", + "target": "39" + }, + { + "id": "120", + "source": "52", + "target": "51" + }, + { + "id": "122", + "source": "53", + "target": "51" + }, + { + "id": "125", + "source": "54", + "target": "26" + }, + { + "id": "124", + "source": "54", + "target": "49" + }, + { + "id": "123", + "source": "54", + "target": "51" + }, + { + "id": "131", + "source": "55", + "target": "11" + }, + { + "id": "132", + "source": "55", + "target": "16" + }, + { + "id": "133", + "source": "55", + "target": "25" + }, + { + "id": null, + "source": "55", + "target": "26" + }, + { + "id": "128", + "source": "55", + "target": "39" + }, + { + "id": "134", + "source": "55", + "target": "41" + }, + { + "id": "135", + "source": "55", + "target": "48" + }, + { + "id": "127", + "source": "55", + "target": "49" + }, + { + "id": "126", + "source": "55", + "target": "51" + }, + { + "id": "129", + "source": "55", + "target": "54" + }, + { + "id": "136", + "source": "56", + "target": "49" + }, + { + "id": "137", + "source": "56", + "target": "55" + }, + { + "id": null, + "source": "57", + "target": "41" + }, + { + "id": null, + "source": "57", + "target": "48" + }, + { + "id": "138", + "source": "57", + "target": "55" + }, + { + "id": "145", + "source": "58", + "target": "11" + }, + { + "id": null, + "source": "58", + "target": "27" + }, + { + "id": "142", + "source": "58", + "target": "48" + }, + { + "id": "141", + "source": "58", + "target": "55" + }, + { + "id": "144", + "source": "58", + "target": "57" + }, + { + "id": "148", + "source": "59", + "target": "48" + }, + { + "id": "147", + "source": "59", + "target": "55" + }, + { + "id": null, + "source": "59", + "target": "57" + }, + { + "id": "146", + "source": "59", + "target": "58" + }, + { + "id": "150", + "source": "60", + "target": "48" + }, + { + "id": "151", + "source": "60", + "target": "58" + }, + { + "id": "152", + "source": "60", + "target": "59" + }, + { + "id": "153", + "source": "61", + "target": "48" + }, + { + "id": "158", + "source": "61", + "target": "55" + }, + { + "id": "157", + "source": "61", + "target": "57" + }, + { + "id": "154", + "source": "61", + "target": "58" + }, + { + "id": "156", + "source": "61", + "target": "59" + }, + { + "id": "155", + "source": "61", + "target": "60" + }, + { + "id": "164", + "source": "62", + "target": "41" + }, + { + "id": "162", + "source": "62", + "target": "48" + }, + { + "id": "159", + "source": "62", + "target": "55" + }, + { + "id": null, + "source": "62", + "target": "57" + }, + { + "id": "160", + "source": "62", + "target": "58" + }, + { + "id": "161", + "source": "62", + "target": "59" + }, + { + "id": null, + "source": "62", + "target": "60" + }, + { + "id": "165", + "source": "62", + "target": "61" + }, + { + "id": null, + "source": "63", + "target": "48" + }, + { + "id": "174", + "source": "63", + "target": "55" + }, + { + "id": null, + "source": "63", + "target": "57" + }, + { + "id": null, + "source": "63", + "target": "58" + }, + { + "id": "167", + "source": "63", + "target": "59" + }, + { + "id": null, + "source": "63", + "target": "60" + }, + { + "id": "172", + "source": "63", + "target": "61" + }, + { + "id": "169", + "source": "63", + "target": "62" + }, + { + "id": "184", + "source": "64", + "target": "11" + }, + { + "id": null, + "source": "64", + "target": "48" + }, + { + "id": "175", + "source": "64", + "target": "55" + }, + { + "id": "183", + "source": "64", + "target": "57" + }, + { + "id": "179", + "source": "64", + "target": "58" + }, + { + "id": "182", + "source": "64", + "target": "59" + }, + { + "id": "181", + "source": "64", + "target": "60" + }, + { + "id": "180", + "source": "64", + "target": "61" + }, + { + "id": "176", + "source": "64", + "target": "62" + }, + { + "id": "178", + "source": "64", + "target": "63" + }, + { + "id": "187", + "source": "65", + "target": "48" + }, + { + "id": "194", + "source": "65", + "target": "55" + }, + { + "id": "193", + "source": "65", + "target": "57" + }, + { + "id": null, + "source": "65", + "target": "58" + }, + { + "id": "192", + "source": "65", + "target": "59" + }, + { + "id": null, + "source": "65", + "target": "60" + }, + { + "id": "190", + "source": "65", + "target": "61" + }, + { + "id": "188", + "source": "65", + "target": "62" + }, + { + "id": "185", + "source": "65", + "target": "63" + }, + { + "id": "186", + "source": "65", + "target": "64" + }, + { + "id": "200", + "source": "66", + "target": "48" + }, + { + "id": "196", + "source": "66", + "target": "58" + }, + { + "id": "197", + "source": "66", + "target": "59" + }, + { + "id": "203", + "source": "66", + "target": "60" + }, + { + "id": "202", + "source": "66", + "target": "61" + }, + { + "id": "198", + "source": "66", + "target": "62" + }, + { + "id": "201", + "source": "66", + "target": "63" + }, + { + "id": "195", + "source": "66", + "target": "64" + }, + { + "id": "199", + "source": "66", + "target": "65" + }, + { + "id": "204", + "source": "67", + "target": "57" + }, + { + "id": null, + "source": "68", + "target": "11" + }, + { + "id": null, + "source": "68", + "target": "24" + }, + { + "id": "205", + "source": "68", + "target": "25" + }, + { + "id": "208", + "source": "68", + "target": "27" + }, + { + "id": null, + "source": "68", + "target": "41" + }, + { + "id": "209", + "source": "68", + "target": "48" + }, + { + "id": "213", + "source": "69", + "target": "11" + }, + { + "id": "214", + "source": "69", + "target": "24" + }, + { + "id": "211", + "source": "69", + "target": "25" + }, + { + "id": null, + "source": "69", + "target": "27" + }, + { + "id": "217", + "source": "69", + "target": "41" + }, + { + "id": "216", + "source": "69", + "target": "48" + }, + { + "id": "212", + "source": "69", + "target": "68" + }, + { + "id": "221", + "source": "70", + "target": "11" + }, + { + "id": "222", + "source": "70", + "target": "24" + }, + { + "id": "218", + "source": "70", + "target": "25" + }, + { + "id": "223", + "source": "70", + "target": "27" + }, + { + "id": "224", + "source": "70", + "target": "41" + }, + { + "id": "225", + "source": "70", + "target": "58" + }, + { + "id": "220", + "source": "70", + "target": "68" + }, + { + "id": "219", + "source": "70", + "target": "69" + }, + { + "id": "230", + "source": "71", + "target": "11" + }, + { + "id": "233", + "source": "71", + "target": "25" + }, + { + "id": "226", + "source": "71", + "target": "27" + }, + { + "id": "232", + "source": "71", + "target": "41" + }, + { + "id": null, + "source": "71", + "target": "48" + }, + { + "id": "228", + "source": "71", + "target": "68" + }, + { + "id": "227", + "source": "71", + "target": "69" + }, + { + "id": "229", + "source": "71", + "target": "70" + }, + { + "id": "236", + "source": "72", + "target": "11" + }, + { + "id": "234", + "source": "72", + "target": "26" + }, + { + "id": "235", + "source": "72", + "target": "27" + }, + { + "id": "237", + "source": "73", + "target": "48" + }, + { + "id": "238", + "source": "74", + "target": "48" + }, + { + "id": "239", + "source": "74", + "target": "73" + }, + { + "id": "242", + "source": "75", + "target": "25" + }, + { + "id": "244", + "source": "75", + "target": "41" + }, + { + "id": null, + "source": "75", + "target": "48" + }, + { + "id": "241", + "source": "75", + "target": "68" + }, + { + "id": "240", + "source": "75", + "target": "69" + }, + { + "id": "245", + "source": "75", + "target": "70" + }, + { + "id": "246", + "source": "75", + "target": "71" + }, + { + "id": "252", + "source": "76", + "target": "48" + }, + { + "id": "253", + "source": "76", + "target": "58" + }, + { + "id": "251", + "source": "76", + "target": "62" + }, + { + "id": "250", + "source": "76", + "target": "63" + }, + { + "id": "247", + "source": "76", + "target": "64" + }, + { + "id": "248", + "source": "76", + "target": "65" + }, + { + "id": "249", + "source": "76", + "target": "66" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/life-expectancy-table.json b/test/fixtures/life-expectancy-table.json new file mode 100644 index 000000000..9ec4f11be --- /dev/null +++ b/test/fixtures/life-expectancy-table.json @@ -0,0 +1,10782 @@ +[ + [ + "Income", + "Life Expectancy", + "Population", + "Country", + "Year" + ], + [ + 815, + 34.05, + 351014, + "Australia", + 1800 + ], + [ + 1314, + 39, + 645526, + "Canada", + 1800 + ], + [ + 985, + 32, + 321675013, + "China", + 1800 + ], + [ + 864, + 32.2, + 345043, + "Cuba", + 1800 + ], + [ + 1244, + 36.5731262, + 977662, + "Finland", + 1800 + ], + [ + 1803, + 33.96717024, + 29355111, + "France", + 1800 + ], + [ + 1639, + 38.37, + 22886919, + "Germany", + 1800 + ], + [ + 926, + 42.84559912, + 61428, + "Iceland", + 1800 + ], + [ + 1052, + 25.4424, + 168574895, + "India", + 1800 + ], + [ + 1050, + 36.4, + 30294378, + "Japan", + 1800 + ], + [ + 579, + 26, + 4345000, + "North Korea", + 1800 + ], + [ + 576, + 25.8, + 9395000, + "South Korea", + 1800 + ], + [ + 658, + 34.05, + 100000, + "New Zealand", + 1800 + ], + [ + 1278, + 37.91620899, + 868570, + "Norway", + 1800 + ], + [ + 1213, + 35.9, + 9508747, + "Poland", + 1800 + ], + [ + 1430, + 29.5734572, + 31088398, + "Russia", + 1800 + ], + [ + 1221, + 35, + 9773456, + "Turkey", + 1800 + ], + [ + 3431, + 38.6497603, + 12327466, + "United Kingdom", + 1800 + ], + [ + 2128, + 39.41, + 6801854, + "United States", + 1800 + ], + [ + 834, + 34.05, + 342440, + "Australia", + 1810 + ], + [ + 1400, + 39.01496774, + 727603, + "Canada", + 1810 + ], + [ + 985, + 32, + 350542958, + "China", + 1810 + ], + [ + 970, + 33.64, + 470176, + "Cuba", + 1810 + ], + [ + 1267, + 36.9473378, + 1070625, + "Finland", + 1810 + ], + [ + 1839, + 37.4, + 30293172, + "France", + 1810 + ], + [ + 1759, + 38.37, + 23882461, + "Germany", + 1810 + ], + [ + 928, + 43.13915533, + 61428, + "Iceland", + 1810 + ], + [ + 1051, + 25.4424, + 171940819, + "India", + 1810 + ], + [ + 1064, + 36.40397538, + 30645903, + "Japan", + 1810 + ], + [ + 573, + 26, + 4345000, + "North Korea", + 1810 + ], + [ + 570, + 25.8, + 9395000, + "South Korea", + 1810 + ], + [ + 659, + 34.05, + 100000, + "New Zealand", + 1810 + ], + [ + 1299, + 36.47500606, + 918398, + "Norway", + 1810 + ], + [ + 1260, + 35.9, + 9960687, + "Poland", + 1810 + ], + [ + 1447, + 29.5734572, + 31088398, + "Russia", + 1810 + ], + [ + 1223, + 35, + 9923007, + "Turkey", + 1810 + ], + [ + 3575, + 38.34738144, + 14106058, + "United Kingdom", + 1810 + ], + [ + 2283, + 39.41, + 8294928, + "United States", + 1810 + ], + [ + 853, + 34.05, + 334002, + "Australia", + 1820 + ], + [ + 1491, + 39.02993548, + 879432, + "Canada", + 1820 + ], + [ + 985, + 32, + 380055273, + "China", + 1820 + ], + [ + 1090, + 35.04, + 607664, + "Cuba", + 1820 + ], + [ + 1290, + 37.29122269, + 1190807, + "Finland", + 1820 + ], + [ + 1876, + 39.21, + 31549988, + "France", + 1820 + ], + [ + 1887, + 38.37, + 25507768, + "Germany", + 1820 + ], + [ + 929, + 36.56365268, + 62498, + "Iceland", + 1820 + ], + [ + 1050, + 25.4424, + 176225709, + "India", + 1820 + ], + [ + 1079, + 36.40795077, + 30993147, + "Japan", + 1820 + ], + [ + 567, + 26, + 4353556, + "North Korea", + 1820 + ], + [ + 564, + 25.8, + 9408016, + "South Korea", + 1820 + ], + [ + 660, + 34.05, + 100000, + "New Zealand", + 1820 + ], + [ + 1320, + 46.96239815, + 995904, + "Norway", + 1820 + ], + [ + 1309, + 35.9, + 10508375, + "Poland", + 1820 + ], + [ + 1464, + 29.5734572, + 31861526, + "Russia", + 1820 + ], + [ + 1225, + 35, + 10118315, + "Turkey", + 1820 + ], + [ + 3403, + 41.31247671, + 16221883, + "United Kingdom", + 1820 + ], + [ + 2242, + 39.41, + 10361646, + "United States", + 1820 + ], + [ + 1399, + 34.05, + 348143, + "Australia", + 1830 + ], + [ + 1651, + 39.04490323, + 1202146, + "Canada", + 1830 + ], + [ + 986, + 32, + 402373519, + "China", + 1830 + ], + [ + 1224, + 35.74, + 772812, + "Cuba", + 1830 + ], + [ + 1360, + 36.29644969, + 1327905, + "Finland", + 1830 + ], + [ + 1799, + 39.56, + 33174810, + "France", + 1830 + ], + [ + 2024, + 38.37, + 28016571, + "Germany", + 1830 + ], + [ + 1036, + 40.5022162, + 65604, + "Iceland", + 1830 + ], + [ + 1052, + 25.4424, + 182214537, + "India", + 1830 + ], + [ + 1094, + 36.41192615, + 31330455, + "Japan", + 1830 + ], + [ + 561, + 26, + 4377749, + "North Korea", + 1830 + ], + [ + 559, + 25.8, + 9444785, + "South Korea", + 1830 + ], + [ + 661, + 34.05, + 91723, + "New Zealand", + 1830 + ], + [ + 1403, + 45.75400094, + 1115667, + "Norway", + 1830 + ], + [ + 1360, + 35.9, + 11232857, + "Poland", + 1830 + ], + [ + 1562, + 29.5734572, + 34134430, + "Russia", + 1830 + ], + [ + 1292, + 35, + 10398375, + "Turkey", + 1830 + ], + [ + 3661, + 43.01830917, + 18533999, + "United Kingdom", + 1830 + ], + [ + 2552, + 39.41, + 13480460, + "United States", + 1830 + ], + [ + 2269, + 34.05, + 434095, + "Australia", + 1840 + ], + [ + 1922, + 40.19012, + 1745604, + "Canada", + 1840 + ], + [ + 986, + 32, + 411213424, + "China", + 1840 + ], + [ + 1374, + 36.48, + 975565, + "Cuba", + 1840 + ], + [ + 1434, + 41.46900965, + 1467238, + "Finland", + 1840 + ], + [ + 2184, + 40.37, + 34854476, + "France", + 1840 + ], + [ + 2102, + 38.37, + 31016143, + "Germany", + 1840 + ], + [ + 1155, + 31.97, + 70010, + "Iceland", + 1840 + ], + [ + 1053, + 25.4424, + 189298397, + "India", + 1840 + ], + [ + 1110, + 36.41590154, + 31663783, + "Japan", + 1840 + ], + [ + 556, + 26, + 4410700, + "North Korea", + 1840 + ], + [ + 553, + 25.8, + 9494784, + "South Korea", + 1840 + ], + [ + 662, + 34.05, + 82479, + "New Zealand", + 1840 + ], + [ + 1604, + 45.61661054, + 1252476, + "Norway", + 1840 + ], + [ + 1413, + 35.9, + 12090161, + "Poland", + 1840 + ], + [ + 1666, + 29.5734572, + 37420913, + "Russia", + 1840 + ], + [ + 1362, + 35, + 10731241, + "Turkey", + 1840 + ], + [ + 4149, + 39.92715263, + 20737251, + "United Kingdom", + 1840 + ], + [ + 2792, + 39.41, + 17942443, + "United States", + 1840 + ], + [ + 3267, + 34.05, + 742619, + "Australia", + 1850 + ], + [ + 2202, + 40.985432, + 2487811, + "Canada", + 1850 + ], + [ + 985, + 32, + 402711280, + "China", + 1850 + ], + [ + 1543, + 36.26, + 1181650, + "Cuba", + 1850 + ], + [ + 1512, + 37.35415172, + 1607810, + "Finland", + 1850 + ], + [ + 2146, + 43.28, + 36277905, + "France", + 1850 + ], + [ + 2182, + 38.37, + 33663143, + "Germany", + 1850 + ], + [ + 1287, + 36.61, + 74711, + "Iceland", + 1850 + ], + [ + 1055, + 25.4424, + 196657653, + "India", + 1850 + ], + [ + 1125, + 36.41987692, + 32223184, + "Japan", + 1850 + ], + [ + 550, + 26, + 4443898, + "North Korea", + 1850 + ], + [ + 547, + 25.8, + 9558873, + "South Korea", + 1850 + ], + [ + 1898, + 34.05, + 94934, + "New Zealand", + 1850 + ], + [ + 1675, + 49.53, + 1401619, + "Norway", + 1850 + ], + [ + 1468, + 35.9, + 13219914, + "Poland", + 1850 + ], + [ + 1778, + 29.5734572, + 41023821, + "Russia", + 1850 + ], + [ + 1436, + 35, + 11074762, + "Turkey", + 1850 + ], + [ + 4480, + 42.8, + 22623571, + "United Kingdom", + 1850 + ], + [ + 3059, + 39.41, + 24136293, + "United States", + 1850 + ], + [ + 4795, + 34.05, + 1256048, + "Australia", + 1860 + ], + [ + 2406, + 41.541504, + 3231465, + "Canada", + 1860 + ], + [ + 1023, + 28.85, + 380047548, + "China", + 1860 + ], + [ + 1733, + 36.24, + 1324000, + "Cuba", + 1860 + ], + [ + 1594, + 38.15099864, + 1734254, + "Finland", + 1860 + ], + [ + 3086, + 43.33, + 37461341, + "France", + 1860 + ], + [ + 2509, + 38.37, + 36383150, + "Germany", + 1860 + ], + [ + 1435, + 19.76, + 79662, + "Iceland", + 1860 + ], + [ + 1056, + 23, + 204966302, + "India", + 1860 + ], + [ + 1168, + 36.42385231, + 33176900, + "Japan", + 1860 + ], + [ + 545, + 26, + 4542395, + "North Korea", + 1860 + ], + [ + 542, + 25.8, + 9650608, + "South Korea", + 1860 + ], + [ + 3674, + 34.05, + 157114, + "New Zealand", + 1860 + ], + [ + 2033, + 50, + 1580366, + "Norway", + 1860 + ], + [ + 1525, + 35.9, + 14848599, + "Poland", + 1860 + ], + [ + 1896, + 29.5734572, + 44966686, + "Russia", + 1860 + ], + [ + 1514, + 35, + 11428718, + "Turkey", + 1860 + ], + [ + 5268, + 43.01, + 24783522, + "United Kingdom", + 1860 + ], + [ + 3714, + 39.41, + 31936643, + "United States", + 1860 + ], + [ + 5431, + 34.05, + 1724213, + "Australia", + 1870 + ], + [ + 2815, + 42.460624, + 3817167, + "Canada", + 1870 + ], + [ + 1099, + 31.95714286, + 363661158, + "China", + 1870 + ], + [ + 1946, + 29.66, + 1424672, + "Cuba", + 1870 + ], + [ + 1897, + 45.66140699, + 1847468, + "Finland", + 1870 + ], + [ + 3297, + 36.41, + 38170355, + "France", + 1870 + ], + [ + 2819, + 38.37, + 39702235, + "Germany", + 1870 + ], + [ + 1599, + 38.37, + 84941, + "Iceland", + 1870 + ], + [ + 1058, + 25.4424, + 213725049, + "India", + 1870 + ], + [ + 1213, + 36.59264, + 34638021, + "Japan", + 1870 + ], + [ + 539, + 26, + 4656353, + "North Korea", + 1870 + ], + [ + 536, + 25.8, + 9741935, + "South Korea", + 1870 + ], + [ + 5156, + 34.05, + 301045, + "New Zealand", + 1870 + ], + [ + 2483, + 50.86, + 1746718, + "Norway", + 1870 + ], + [ + 1584, + 35.9, + 17013787, + "Poland", + 1870 + ], + [ + 2023, + 31.12082604, + 49288504, + "Russia", + 1870 + ], + [ + 1597, + 35, + 11871788, + "Turkey", + 1870 + ], + [ + 6046, + 40.95, + 27651628, + "United Kingdom", + 1870 + ], + [ + 4058, + 39.41, + 40821569, + "United States", + 1870 + ], + [ + 7120, + 39.34215686, + 2253007, + "Australia", + 1880 + ], + [ + 3021, + 44.512464, + 4360348, + "Canada", + 1880 + ], + [ + 1015, + 32, + 365544192, + "China", + 1880 + ], + [ + 2185, + 36.84, + 1555081, + "Cuba", + 1880 + ], + [ + 1925, + 39.67, + 2047577, + "Finland", + 1880 + ], + [ + 3555, + 42.73, + 39014053, + "France", + 1880 + ], + [ + 3057, + 38.905, + 43577358, + "Germany", + 1880 + ], + [ + 2035, + 42.32, + 90546, + "Iceland", + 1880 + ], + [ + 1084, + 25.4424, + 223020377, + "India", + 1880 + ], + [ + 1395, + 37.03648, + 36826469, + "Japan", + 1880 + ], + [ + 534, + 26, + 4798574, + "North Korea", + 1880 + ], + [ + 531, + 25.8, + 9806394, + "South Korea", + 1880 + ], + [ + 6241, + 38.51282051, + 505065, + "New Zealand", + 1880 + ], + [ + 2827, + 51.91, + 1883716, + "Norway", + 1880 + ], + [ + 1848, + 35.9, + 19669587, + "Poland", + 1880 + ], + [ + 2158, + 30.20106663, + 53996807, + "Russia", + 1880 + ], + [ + 1535, + 35, + 12474351, + "Turkey", + 1880 + ], + [ + 6553, + 43.78, + 30849957, + "United Kingdom", + 1880 + ], + [ + 5292, + 39.41, + 51256498, + "United States", + 1880 + ], + [ + 7418, + 44.63431373, + 3088808, + "Australia", + 1890 + ], + [ + 3963, + 45.12972, + 4908078, + "Canada", + 1890 + ], + [ + 918, + 32, + 377135349, + "China", + 1890 + ], + [ + 2454, + 39.54, + 1658274, + "Cuba", + 1890 + ], + [ + 2305, + 44.61, + 2358344, + "Finland", + 1890 + ], + [ + 3639, + 43.36, + 40015501, + "France", + 1890 + ], + [ + 3733, + 40.91, + 48211294, + "Germany", + 1890 + ], + [ + 2009, + 36.58, + 96517, + "Iceland", + 1890 + ], + [ + 1163, + 24.384, + 232819584, + "India", + 1890 + ], + [ + 1606, + 37.67568, + 39878734, + "Japan", + 1890 + ], + [ + 528, + 26, + 4959044, + "North Korea", + 1890 + ], + [ + 526, + 25.8, + 9856047, + "South Korea", + 1890 + ], + [ + 6265, + 42.97564103, + 669985, + "New Zealand", + 1890 + ], + [ + 3251, + 48.6, + 2003954, + "Norway", + 1890 + ], + [ + 2156, + 37.41086957, + 22618933, + "Poland", + 1890 + ], + [ + 2233, + 29.93047652, + 59151534, + "Russia", + 1890 + ], + [ + 1838, + 35, + 13188522, + "Turkey", + 1890 + ], + [ + 7169, + 44.75, + 34215580, + "United Kingdom", + 1890 + ], + [ + 5646, + 45.21, + 63810074, + "United States", + 1890 + ], + [ + 6688, + 49.92647059, + 3743708, + "Australia", + 1900 + ], + [ + 4858, + 48.288448, + 5530806, + "Canada", + 1900 + ], + [ + 894, + 32, + 395184556, + "China", + 1900 + ], + [ + 2756, + 33.11248, + 1762227, + "Cuba", + 1900 + ], + [ + 2789, + 41.8, + 2633389, + "Finland", + 1900 + ], + [ + 4314, + 45.08, + 40628638, + "France", + 1900 + ], + [ + 4596, + 43.915, + 55293434, + "Germany", + 1900 + ], + [ + 2352, + 46.64, + 102913, + "Iceland", + 1900 + ], + [ + 1194, + 18.35, + 243073946, + "India", + 1900 + ], + [ + 1840, + 38.6, + 44040263, + "Japan", + 1900 + ], + [ + 523, + 26, + 5124044, + "North Korea", + 1900 + ], + [ + 520, + 25.8, + 9926633, + "South Korea", + 1900 + ], + [ + 7181, + 47.43846154, + 815519, + "New Zealand", + 1900 + ], + [ + 3643, + 53.47, + 2214923, + "Norway", + 1900 + ], + [ + 2583, + 40.4326087, + 24700965, + "Poland", + 1900 + ], + [ + 3087, + 30.74960789, + 64836675, + "Russia", + 1900 + ], + [ + 1985, + 35, + 13946634, + "Turkey", + 1900 + ], + [ + 8013, + 46.32, + 37995759, + "United Kingdom", + 1900 + ], + [ + 6819, + 48.92818182, + 77415610, + "United States", + 1900 + ], + [ + 8695, + 55.21862745, + 4408209, + "Australia", + 1910 + ], + [ + 6794, + 52.123024, + 7181200, + "Canada", + 1910 + ], + [ + 991, + 32, + 417830774, + "China", + 1910 + ], + [ + 3095, + 35.21936, + 2268558, + "Cuba", + 1910 + ], + [ + 3192, + 48.53, + 2930441, + "Finland", + 1910 + ], + [ + 4542, + 51.37, + 41294572, + "France", + 1910 + ], + [ + 5162, + 48.40833333, + 64064129, + "Germany", + 1910 + ], + [ + 3012, + 52.67, + 109714, + "Iceland", + 1910 + ], + [ + 1391, + 23.18032, + 253761202, + "India", + 1910 + ], + [ + 1998, + 39.9736, + 49314848, + "Japan", + 1910 + ], + [ + 544, + 24.097344, + 5293486, + "North Korea", + 1910 + ], + [ + 538, + 24.097344, + 10193929, + "South Korea", + 1910 + ], + [ + 8896, + 51.90128205, + 1044340, + "New Zealand", + 1910 + ], + [ + 4332, + 57.99, + 2383631, + "Norway", + 1910 + ], + [ + 2846, + 43.45434783, + 26493422, + "Poland", + 1910 + ], + [ + 3487, + 31.40217766, + 71044207, + "Russia", + 1910 + ], + [ + 2144, + 35, + 14746479, + "Turkey", + 1910 + ], + [ + 8305, + 53.99, + 41804912, + "United Kingdom", + 1910 + ], + [ + 8287, + 51.8, + 93559186, + "United States", + 1910 + ], + [ + 7867, + 60.51078431, + 5345428, + "Australia", + 1920 + ], + [ + 6430, + 56.569064, + 8764205, + "Canada", + 1920 + ], + [ + 1012, + 32, + 462750597, + "China", + 1920 + ], + [ + 4042, + 37.38208, + 3067116, + "Cuba", + 1920 + ], + [ + 3097, + 47.55, + 3140763, + "Finland", + 1920 + ], + [ + 4550, + 51.6, + 39069937, + "France", + 1920 + ], + [ + 4482, + 53.5, + 62277173, + "Germany", + 1920 + ], + [ + 2514, + 54.58, + 117013, + "Iceland", + 1920 + ], + [ + 1197, + 24.71866667, + 267795301, + "India", + 1920 + ], + [ + 2496, + 42.04432, + 55545937, + "Japan", + 1920 + ], + [ + 779, + 27.99984, + 6117873, + "North Korea", + 1920 + ], + [ + 756, + 27.99984, + 11839704, + "South Korea", + 1920 + ], + [ + 9453, + 56.36410256, + 1236395, + "New Zealand", + 1920 + ], + [ + 5483, + 58.89, + 2634635, + "Norway", + 1920 + ], + [ + 3276, + 46.47608696, + 24166006, + "Poland", + 1920 + ], + [ + 1489, + 20.5, + 77871987, + "Russia", + 1920 + ], + [ + 1525, + 29, + 14200404, + "Turkey", + 1920 + ], + [ + 8316, + 56.6, + 43825720, + "United Kingdom", + 1920 + ], + [ + 9181, + 55.4, + 108441644, + "United States", + 1920 + ], + [ + 7714, + 64.998, + 6473803, + "Australia", + 1930 + ], + [ + 7976, + 58.94, + 10450983, + "Canada", + 1930 + ], + [ + 1055, + 33.26984, + 481222579, + "China", + 1930 + ], + [ + 5027, + 42.03308, + 3918827, + "Cuba", + 1930 + ], + [ + 4489, + 54.438, + 3450505, + "Finland", + 1930 + ], + [ + 6835, + 56.938, + 41662571, + "France", + 1930 + ], + [ + 6791, + 59.4991686, + 66439556, + "Germany", + 1930 + ], + [ + 4444, + 60.228, + 124871, + "Iceland", + 1930 + ], + [ + 1244, + 28.8016, + 285470839, + "India", + 1930 + ], + [ + 2592, + 46.65403, + 63863524, + "Japan", + 1930 + ], + [ + 829, + 33.867168, + 7366694, + "North Korea", + 1930 + ], + [ + 784, + 35.244168, + 13929869, + "South Korea", + 1930 + ], + [ + 8359, + 60.86092308, + 1491937, + "New Zealand", + 1930 + ], + [ + 7369, + 64.074, + 2807922, + "Norway", + 1930 + ], + [ + 3591, + 49.52382609, + 28169922, + "Poland", + 1930 + ], + [ + 3779, + 36.428, + 85369549, + "Russia", + 1930 + ], + [ + 2323, + 35.7818, + 14930772, + "Turkey", + 1930 + ], + [ + 8722, + 60.85, + 45957969, + "United Kingdom", + 1930 + ], + [ + 10139, + 59.556, + 125055606, + "United States", + 1930 + ], + [ + 10057, + 66.336, + 7052012, + "Australia", + 1940 + ], + [ + 8871, + 63.99, + 11655920, + "Canada", + 1940 + ], + [ + 841, + 33.30311174, + 509858820, + "China", + 1940 + ], + [ + 4631, + 48.5472, + 4672303, + "Cuba", + 1940 + ], + [ + 5439, + 46.586, + 3696232, + "Finland", + 1940 + ], + [ + 4821, + 49.586, + 40927546, + "France", + 1940 + ], + [ + 9711, + 60.73821096, + 71244059, + "Germany", + 1940 + ], + [ + 5373, + 65.786, + 133257, + "Iceland", + 1940 + ], + [ + 1081, + 32.13056, + 324372335, + "India", + 1940 + ], + [ + 3888, + 49.052, + 72709185, + "Japan", + 1940 + ], + [ + 1418, + 41.22756, + 8870433, + "North Korea", + 1940 + ], + [ + 1322, + 43.98156, + 15684579, + "South Korea", + 1940 + ], + [ + 10673, + 65.35774359, + 1629869, + "New Zealand", + 1940 + ], + [ + 8349, + 65.818, + 2971546, + "Norway", + 1940 + ], + [ + 3696, + 44.752, + 30041062, + "Poland", + 1940 + ], + [ + 5632, + 41.056, + 93588981, + "Russia", + 1940 + ], + [ + 3163, + 34.5396, + 17777172, + "Turkey", + 1940 + ], + [ + 10935, + 60.89, + 48235963, + "United Kingdom", + 1940 + ], + [ + 11320, + 63.192, + 134354133, + "United States", + 1940 + ], + [ + 12073, + 69.134, + 8177344, + "Australia", + 1950 + ], + [ + 12022, + 68.25, + 13736997, + "Canada", + 1950 + ], + [ + 535, + 39.9994, + 544112923, + "China", + 1950 + ], + [ + 8630, + 59.8384, + 5919997, + "Cuba", + 1950 + ], + [ + 7198, + 64.144, + 4008299, + "Finland", + 1950 + ], + [ + 7914, + 66.594, + 41879607, + "France", + 1950 + ], + [ + 7251, + 67.0215058, + 69786246, + "Germany", + 1950 + ], + [ + 8670, + 71.004, + 142656, + "Iceland", + 1950 + ], + [ + 908, + 34.6284, + 376325205, + "India", + 1950 + ], + [ + 2549, + 59.378, + 82199470, + "Japan", + 1950 + ], + [ + 868, + 32.2464, + 10549469, + "North Korea", + 1950 + ], + [ + 807, + 43.3774, + 19211386, + "South Korea", + 1950 + ], + [ + 14391, + 69.392, + 1908001, + "New Zealand", + 1950 + ], + [ + 11452, + 71.492, + 3265278, + "Norway", + 1950 + ], + [ + 4670, + 59.123, + 24824013, + "Poland", + 1950 + ], + [ + 7514, + 57.084, + 102798657, + "Russia", + 1950 + ], + [ + 3103, + 42.5164, + 21238496, + "Turkey", + 1950 + ], + [ + 11135, + 68.58, + 50616012, + "United Kingdom", + 1950 + ], + [ + 15319, + 67.988, + 157813040, + "United States", + 1950 + ], + [ + 12229, + 68.8378, + 8417640, + "Australia", + 1951 + ], + [ + 12419, + 68.519, + 14099994, + "Canada", + 1951 + ], + [ + 582, + 40.936264, + 558820362, + "China", + 1951 + ], + [ + 9245, + 60.18618, + 6051290, + "Cuba", + 1951 + ], + [ + 7738, + 65.5708, + 4049689, + "Finland", + 1951 + ], + [ + 8301, + 66.3308, + 42071027, + "France", + 1951 + ], + [ + 7884, + 67.18742266, + 70111671, + "Germany", + 1951 + ], + [ + 8350, + 71.0438, + 144928, + "Iceland", + 1951 + ], + [ + 908, + 34.95868, + 382231042, + "India", + 1951 + ], + [ + 2728, + 61.0706, + 83794452, + "Japan", + 1951 + ], + [ + 729, + 23.12128, + 10248496, + "North Korea", + 1951 + ], + [ + 753, + 40.88998, + 19304737, + "South Korea", + 1951 + ], + [ + 13032, + 69.2654, + 1947802, + "New Zealand", + 1951 + ], + [ + 11986, + 72.4284, + 3300422, + "Norway", + 1951 + ], + [ + 4801, + 59.7336, + 25264029, + "Poland", + 1951 + ], + [ + 7424, + 57.5768, + 104306354, + "Russia", + 1951 + ], + [ + 3701, + 42.78358, + 21806355, + "Turkey", + 1951 + ], + [ + 11416, + 68.176, + 50620538, + "United Kingdom", + 1951 + ], + [ + 16198, + 68.0836, + 159880756, + "United States", + 1951 + ], + [ + 12084, + 69.2416, + 8627052, + "Australia", + 1952 + ], + [ + 12911, + 68.718, + 14481497, + "Canada", + 1952 + ], + [ + 631, + 41.873128, + 570764965, + "China", + 1952 + ], + [ + 9446, + 60.82796, + 6180031, + "Cuba", + 1952 + ], + [ + 7914, + 66.4476, + 4095130, + "Finland", + 1952 + ], + [ + 8446, + 67.6276, + 42365756, + "France", + 1952 + ], + [ + 8561, + 67.51033952, + 70421462, + "Germany", + 1952 + ], + [ + 8120, + 72.4836, + 147681, + "Iceland", + 1952 + ], + [ + 912, + 35.62796, + 388515758, + "India", + 1952 + ], + [ + 3015, + 63.1132, + 85174909, + "Japan", + 1952 + ], + [ + 784, + 20.99616, + 10049026, + "North Korea", + 1952 + ], + [ + 809, + 40.40256, + 19566860, + "South Korea", + 1952 + ], + [ + 13281, + 69.4988, + 1992619, + "New Zealand", + 1952 + ], + [ + 12316, + 72.5548, + 3333895, + "Norway", + 1952 + ], + [ + 4832, + 60.9112, + 25738253, + "Poland", + 1952 + ], + [ + 7775, + 57.9696, + 105969442, + "Russia", + 1952 + ], + [ + 3963, + 43.25976, + 22393931, + "Turkey", + 1952 + ], + [ + 11367, + 69.472, + 50683596, + "United Kingdom", + 1952 + ], + [ + 16508, + 68.2992, + 162280405, + "United States", + 1952 + ], + [ + 12228, + 69.8254, + 8821938, + "Australia", + 1953 + ], + [ + 13158, + 69.097, + 14882050, + "Canada", + 1953 + ], + [ + 692, + 42.809992, + 580886559, + "China", + 1953 + ], + [ + 8192, + 61.46974, + 6304524, + "Cuba", + 1953 + ], + [ + 7877, + 66.5044, + 4142353, + "Finland", + 1953 + ], + [ + 8622, + 67.5644, + 42724452, + "France", + 1953 + ], + [ + 9252, + 67.82125638, + 70720721, + "Germany", + 1953 + ], + [ + 9169, + 72.3034, + 150779, + "Iceland", + 1953 + ], + [ + 947, + 36.30024, + 395137696, + "India", + 1953 + ], + [ + 3168, + 63.4558, + 86378004, + "Japan", + 1953 + ], + [ + 1018, + 27.87104, + 9957244, + "North Korea", + 1953 + ], + [ + 1051, + 45.41514, + 19979069, + "South Korea", + 1953 + ], + [ + 13388, + 70.3522, + 2040015, + "New Zealand", + 1953 + ], + [ + 12707, + 73.0312, + 3366281, + "Norway", + 1953 + ], + [ + 5027, + 62.0038, + 26236679, + "Poland", + 1953 + ], + [ + 7981, + 58.7624, + 107729541, + "Russia", + 1953 + ], + [ + 4361, + 43.77694, + 22999018, + "Turkey", + 1953 + ], + [ + 11751, + 69.738, + 50792671, + "United Kingdom", + 1953 + ], + [ + 16974, + 68.6448, + 164941716, + "United States", + 1953 + ], + [ + 12694, + 69.9792, + 9014508, + "Australia", + 1954 + ], + [ + 12687, + 69.956, + 15300472, + "Canada", + 1954 + ], + [ + 694, + 44.663056, + 589955812, + "China", + 1954 + ], + [ + 8492, + 62.11152, + 6424173, + "Cuba", + 1954 + ], + [ + 8470, + 67.4612, + 4189559, + "Finland", + 1954 + ], + [ + 9006, + 68.4412, + 43118110, + "France", + 1954 + ], + [ + 9926, + 68.12117324, + 71015688, + "Germany", + 1954 + ], + [ + 9821, + 73.3532, + 154110, + "Iceland", + 1954 + ], + [ + 962, + 36.97552, + 402065915, + "India", + 1954 + ], + [ + 3280, + 64.6984, + 87438747, + "Japan", + 1954 + ], + [ + 1080, + 38.68292, + 9972437, + "North Korea", + 1954 + ], + [ + 1070, + 48.42772, + 20520601, + "South Korea", + 1954 + ], + [ + 14907, + 70.4656, + 2088194, + "New Zealand", + 1954 + ], + [ + 13247, + 73.1076, + 3398028, + "Norway", + 1954 + ], + [ + 5224, + 63.0134, + 26750026, + "Poland", + 1954 + ], + [ + 8234, + 60.7552, + 109537868, + "Russia", + 1954 + ], + [ + 3892, + 44.33512, + 23619469, + "Turkey", + 1954 + ], + [ + 12173, + 70.104, + 50938227, + "United Kingdom", + 1954 + ], + [ + 16558, + 69.4304, + 167800046, + "United States", + 1954 + ], + [ + 13082, + 70.303, + 9212824, + "Australia", + 1955 + ], + [ + 13513, + 70.015, + 15733858, + "Canada", + 1955 + ], + [ + 706, + 46.1666, + 598574241, + "China", + 1955 + ], + [ + 8757, + 62.7523, + 6539470, + "Cuba", + 1955 + ], + [ + 8802, + 67.258, + 4235423, + "Finland", + 1955 + ], + [ + 9453, + 68.708, + 43528065, + "France", + 1955 + ], + [ + 10998, + 68.4080901, + 71313740, + "Germany", + 1955 + ], + [ + 10548, + 73.293, + 157584, + "Iceland", + 1955 + ], + [ + 963, + 37.6538, + 409280196, + "India", + 1955 + ], + [ + 3464, + 65.861, + 88389994, + "Japan", + 1955 + ], + [ + 1146, + 42.6208, + 10086993, + "North Korea", + 1955 + ], + [ + 1139, + 49.9673, + 21168611, + "South Korea", + 1955 + ], + [ + 14883, + 70.599, + 2136000, + "New Zealand", + 1955 + ], + [ + 13438, + 73.314, + 3429431, + "Norway", + 1955 + ], + [ + 5386, + 63.939, + 27269745, + "Poland", + 1955 + ], + [ + 8787, + 63.148, + 111355224, + "Russia", + 1955 + ], + [ + 4156, + 44.9343, + 24253200, + "Turkey", + 1955 + ], + [ + 12531, + 70.07, + 51113711, + "United Kingdom", + 1955 + ], + [ + 17409, + 69.476, + 170796378, + "United States", + 1955 + ], + [ + 13217, + 70.1868, + 9420602, + "Australia", + 1956 + ], + [ + 14253, + 70.004, + 16177451, + "Canada", + 1956 + ], + [ + 736, + 48.536704, + 607167524, + "China", + 1956 + ], + [ + 9424, + 63.39308, + 6652086, + "Cuba", + 1956 + ], + [ + 8971, + 67.8748, + 4279108, + "Finland", + 1956 + ], + [ + 9907, + 68.7448, + 43946534, + "France", + 1956 + ], + [ + 11751, + 68.70345102, + 71623569, + "Germany", + 1956 + ], + [ + 10575, + 72.9728, + 161136, + "Iceland", + 1956 + ], + [ + 993, + 38.33608, + 416771502, + "India", + 1956 + ], + [ + 3646, + 65.7236, + 89262489, + "Japan", + 1956 + ], + [ + 1208, + 43.99568, + 10285936, + "North Korea", + 1956 + ], + [ + 1130, + 50.64688, + 21897911, + "South Korea", + 1956 + ], + [ + 15358, + 70.8624, + 2182943, + "New Zealand", + 1956 + ], + [ + 14054, + 73.3604, + 3460640, + "Norway", + 1956 + ], + [ + 5530, + 64.7816, + 27787997, + "Poland", + 1956 + ], + [ + 9465, + 64.6408, + 113152347, + "Russia", + 1956 + ], + [ + 4122, + 45.57448, + 24898170, + "Turkey", + 1956 + ], + [ + 12572, + 70.336, + 51315724, + "United Kingdom", + 1956 + ], + [ + 17428, + 69.5516, + 173877321, + "United States", + 1956 + ], + [ + 13191, + 70.4706, + 9637408, + "Australia", + 1957 + ], + [ + 14177, + 69.923, + 16624767, + "Canada", + 1957 + ], + [ + 780, + 48.587368, + 615992182, + "China", + 1957 + ], + [ + 10636, + 64.03586, + 6764787, + "Cuba", + 1957 + ], + [ + 9302, + 67.3716, + 4320250, + "Finland", + 1957 + ], + [ + 10442, + 69.1816, + 44376073, + "France", + 1957 + ], + [ + 12385, + 68.62532856, + 71955005, + "Germany", + 1957 + ], + [ + 10295, + 73.4626, + 164721, + "Iceland", + 1957 + ], + [ + 959, + 39.02236, + 424541513, + "India", + 1957 + ], + [ + 3843, + 65.5962, + 90084818, + "Japan", + 1957 + ], + [ + 1322, + 44.87056, + 10547389, + "North Korea", + 1957 + ], + [ + 1226, + 51.33946, + 22681233, + "South Korea", + 1957 + ], + [ + 15441, + 70.3858, + 2229176, + "New Zealand", + 1957 + ], + [ + 14379, + 73.3068, + 3491657, + "Norway", + 1957 + ], + [ + 5730, + 65.5442, + 28297669, + "Poland", + 1957 + ], + [ + 9496, + 63.7336, + 114909562, + "Russia", + 1957 + ], + [ + 4943, + 46.25466, + 25552398, + "Turkey", + 1957 + ], + [ + 12702, + 70.452, + 51543847, + "United Kingdom", + 1957 + ], + [ + 17430, + 69.3272, + 176995108, + "United States", + 1957 + ], + [ + 13545, + 71.0244, + 9859257, + "Australia", + 1958 + ], + [ + 14056, + 70.582, + 17067983, + "Canada", + 1958 + ], + [ + 889, + 48.143792, + 625155626, + "China", + 1958 + ], + [ + 10501, + 64.67964, + 6881209, + "Cuba", + 1958 + ], + [ + 9276, + 68.5084, + 4358901, + "Finland", + 1958 + ], + [ + 10681, + 70.4184, + 44827950, + "France", + 1958 + ], + [ + 12884, + 69.36929231, + 72318498, + "Germany", + 1958 + ], + [ + 10896, + 73.4224, + 168318, + "Iceland", + 1958 + ], + [ + 1005, + 39.71364, + 432601236, + "India", + 1958 + ], + [ + 3996, + 67.2188, + 90883290, + "Japan", + 1958 + ], + [ + 1498, + 45.33644, + 10843979, + "North Korea", + 1958 + ], + [ + 1233, + 52.04404, + 23490027, + "South Korea", + 1958 + ], + [ + 15688, + 71.0192, + 2275392, + "New Zealand", + 1958 + ], + [ + 14285, + 73.2932, + 3522361, + "Norway", + 1958 + ], + [ + 5923, + 66.0188, + 28792427, + "Poland", + 1958 + ], + [ + 10037, + 66.6264, + 116615781, + "Russia", + 1958 + ], + [ + 5252, + 46.97084, + 26214022, + "Turkey", + 1958 + ], + [ + 12672, + 70.628, + 51800117, + "United Kingdom", + 1958 + ], + [ + 16961, + 69.5928, + 180107612, + "United States", + 1958 + ], + [ + 14076, + 70.5982, + 10079604, + "Australia", + 1959 + ], + [ + 14289, + 70.621, + 17498573, + "Canada", + 1959 + ], + [ + 958, + 36.336856, + 634649557, + "China", + 1959 + ], + [ + 9234, + 65.32842, + 7005486, + "Cuba", + 1959 + ], + [ + 9751, + 68.6852, + 4395427, + "Finland", + 1959 + ], + [ + 10911, + 70.4552, + 45319442, + "France", + 1959 + ], + [ + 13759, + 69.48021979, + 72724260, + "Germany", + 1959 + ], + [ + 10865, + 72.6522, + 171919, + "Iceland", + 1959 + ], + [ + 1002, + 40.41292, + 440968677, + "India", + 1959 + ], + [ + 4288, + 67.6114, + 91681713, + "Japan", + 1959 + ], + [ + 1452, + 45.93132, + 11145152, + "North Korea", + 1959 + ], + [ + 1212, + 52.76062, + 24295786, + "South Korea", + 1959 + ], + [ + 16454, + 70.9326, + 2322669, + "New Zealand", + 1959 + ], + [ + 14797, + 73.4196, + 3552545, + "Norway", + 1959 + ], + [ + 6009, + 65.6314, + 29266789, + "Poland", + 1959 + ], + [ + 9755, + 67.3692, + 118266807, + "Russia", + 1959 + ], + [ + 4869, + 47.72102, + 26881379, + "Turkey", + 1959 + ], + [ + 13122, + 70.724, + 52088147, + "United Kingdom", + 1959 + ], + [ + 17909, + 69.8084, + 183178348, + "United States", + 1959 + ], + [ + 14346, + 71.042, + 10292328, + "Australia", + 1960 + ], + [ + 14414, + 71, + 17909232, + "Canada", + 1960 + ], + [ + 889, + 29.51112, + 644450173, + "China", + 1960 + ], + [ + 9213, + 65.9852, + 7141129, + "Cuba", + 1960 + ], + [ + 10560, + 68.882, + 4430228, + "Finland", + 1960 + ], + [ + 11642, + 70.672, + 45865699, + "France", + 1960 + ], + [ + 14808, + 69.40190727, + 73179665, + "Germany", + 1960 + ], + [ + 10993, + 74.082, + 175520, + "Iceland", + 1960 + ], + [ + 1048, + 41.1222, + 449661874, + "India", + 1960 + ], + [ + 4756, + 67.904, + 92500754, + "Japan", + 1960 + ], + [ + 1544, + 46.2922, + 11424179, + "North Korea", + 1960 + ], + [ + 1178, + 53.4912, + 25074028, + "South Korea", + 1960 + ], + [ + 16179, + 71.396, + 2371999, + "New Zealand", + 1960 + ], + [ + 15542, + 73.436, + 3582016, + "Norway", + 1960 + ], + [ + 6248, + 67.964, + 29716363, + "Poland", + 1960 + ], + [ + 10496, + 68.382, + 119860289, + "Russia", + 1960 + ], + [ + 4735, + 48.4992, + 27553280, + "Turkey", + 1960 + ], + [ + 13697, + 70.94, + 52410496, + "United Kingdom", + 1960 + ], + [ + 18059, + 69.734, + 186176524, + "United States", + 1960 + ], + [ + 14126, + 71.3158, + 10494911, + "Australia", + 1961 + ], + [ + 14545, + 71.229, + 18295922, + "Canada", + 1961 + ], + [ + 558, + 31.930824, + 654625069, + "China", + 1961 + ], + [ + 9248, + 66.64998, + 7289828, + "Cuba", + 1961 + ], + [ + 11286, + 68.9088, + 4463432, + "Finland", + 1961 + ], + [ + 12168, + 71.2588, + 46471083, + "France", + 1961 + ], + [ + 15317, + 69.99702797, + 73686490, + "Germany", + 1961 + ], + [ + 10801, + 73.4618, + 179106, + "Iceland", + 1961 + ], + [ + 1051, + 41.84348, + 458691457, + "India", + 1961 + ], + [ + 5276, + 68.5566, + 93357259, + "Japan", + 1961 + ], + [ + 1624, + 46.54408, + 11665593, + "North Korea", + 1961 + ], + [ + 1201, + 54.23578, + 25808542, + "South Korea", + 1961 + ], + [ + 16664, + 71.1194, + 2423769, + "New Zealand", + 1961 + ], + [ + 16425, + 73.4424, + 3610710, + "Norway", + 1961 + ], + [ + 6669, + 68.0866, + 30138099, + "Poland", + 1961 + ], + [ + 10908, + 68.6248, + 121390327, + "Russia", + 1961 + ], + [ + 4691, + 49.30038, + 28229291, + "Turkey", + 1961 + ], + [ + 13887, + 70.686, + 52765864, + "United Kingdom", + 1961 + ], + [ + 18170, + 70.1396, + 189077076, + "United States", + 1961 + ], + [ + 14742, + 71.0896, + 10691220, + "Australia", + 1962 + ], + [ + 15276, + 71.258, + 18659663, + "Canada", + 1962 + ], + [ + 567, + 42.274688, + 665426760, + "China", + 1962 + ], + [ + 9273, + 67.32476, + 7450404, + "Cuba", + 1962 + ], + [ + 11560, + 68.6156, + 4494623, + "Finland", + 1962 + ], + [ + 12767, + 70.7956, + 47121575, + "France", + 1962 + ], + [ + 15872, + 70.16889372, + 74238494, + "Germany", + 1962 + ], + [ + 11489, + 73.6716, + 182640, + "Iceland", + 1962 + ], + [ + 1046, + 42.57776, + 468054145, + "India", + 1962 + ], + [ + 5686, + 68.8392, + 94263646, + "Japan", + 1962 + ], + [ + 1592, + 46.82096, + 11871720, + "North Korea", + 1962 + ], + [ + 1182, + 54.99436, + 26495107, + "South Korea", + 1962 + ], + [ + 16646, + 71.3828, + 2477328, + "New Zealand", + 1962 + ], + [ + 16793, + 73.3188, + 3638791, + "Norway", + 1962 + ], + [ + 6511, + 67.7492, + 30530513, + "Poland", + 1962 + ], + [ + 11027, + 68.2776, + 122842753, + "Russia", + 1962 + ], + [ + 4849, + 50.11556, + 28909985, + "Turkey", + 1962 + ], + [ + 13897, + 70.752, + 53146634, + "United Kingdom", + 1962 + ], + [ + 18966, + 70.0252, + 191860710, + "United States", + 1962 + ], + [ + 15357, + 71.1534, + 10892700, + "Australia", + 1963 + ], + [ + 15752, + 71.267, + 19007305, + "Canada", + 1963 + ], + [ + 635, + 49.619432, + 677332765, + "China", + 1963 + ], + [ + 9244, + 68.00654, + 7618359, + "Cuba", + 1963 + ], + [ + 11858, + 69.0224, + 4522727, + "Finland", + 1963 + ], + [ + 13235, + 70.6524, + 47781535, + "France", + 1963 + ], + [ + 16221, + 70.26131586, + 74820389, + "Germany", + 1963 + ], + [ + 12447, + 72.9714, + 186056, + "Iceland", + 1963 + ], + [ + 1071, + 43.32404, + 477729958, + "India", + 1963 + ], + [ + 6106, + 69.9218, + 95227653, + "Japan", + 1963 + ], + [ + 1577, + 47.22984, + 12065470, + "North Korea", + 1963 + ], + [ + 1305, + 55.76694, + 27143075, + "South Korea", + 1963 + ], + [ + 17340, + 71.4562, + 2530791, + "New Zealand", + 1963 + ], + [ + 17347, + 72.9552, + 3666690, + "Norway", + 1963 + ], + [ + 6836, + 68.6818, + 30893775, + "Poland", + 1963 + ], + [ + 10620, + 68.7404, + 124193114, + "Russia", + 1963 + ], + [ + 5188, + 50.93674, + 29597047, + "Turkey", + 1963 + ], + [ + 14393, + 70.658, + 53537821, + "United Kingdom", + 1963 + ], + [ + 19497, + 69.8508, + 194513911, + "United States", + 1963 + ], + [ + 16098, + 70.8172, + 11114995, + "Australia", + 1964 + ], + [ + 16464, + 71.646, + 19349346, + "Canada", + 1964 + ], + [ + 713, + 50.988016, + 690932043, + "China", + 1964 + ], + [ + 9179, + 68.69332, + 7787149, + "Cuba", + 1964 + ], + [ + 12389, + 69.2292, + 4546343, + "Finland", + 1964 + ], + [ + 13969, + 71.6192, + 48402900, + "France", + 1964 + ], + [ + 17100, + 70.82344196, + 75410766, + "Germany", + 1964 + ], + [ + 13450, + 73.5612, + 189276, + "Iceland", + 1964 + ], + [ + 1125, + 44.07932, + 487690114, + "India", + 1964 + ], + [ + 6741, + 70.3944, + 96253064, + "Japan", + 1964 + ], + [ + 1592, + 47.82972, + 12282421, + "North Korea", + 1964 + ], + [ + 1380, + 56.55352, + 27770874, + "South Korea", + 1964 + ], + [ + 17837, + 71.4996, + 2581578, + "New Zealand", + 1964 + ], + [ + 18118, + 73.4516, + 3694987, + "Norway", + 1964 + ], + [ + 7078, + 68.9144, + 31229448, + "Poland", + 1964 + ], + [ + 11836, + 69.5332, + 125412397, + "Russia", + 1964 + ], + [ + 5296, + 51.75292, + 30292969, + "Turkey", + 1964 + ], + [ + 15067, + 71.444, + 53920055, + "United Kingdom", + 1964 + ], + [ + 20338, + 70.1364, + 197028908, + "United States", + 1964 + ], + [ + 16601, + 71.151, + 11368011, + "Australia", + 1965 + ], + [ + 17243, + 71.745, + 19693538, + "Canada", + 1965 + ], + [ + 772, + 53.26108, + 706590947, + "China", + 1965 + ], + [ + 9116, + 69.3761, + 7951928, + "Cuba", + 1965 + ], + [ + 13006, + 68.986, + 4564690, + "Finland", + 1965 + ], + [ + 14514, + 71.456, + 48952283, + "France", + 1965 + ], + [ + 17838, + 70.81075623, + 75990737, + "Germany", + 1965 + ], + [ + 14173, + 73.831, + 192251, + "Iceland", + 1965 + ], + [ + 1053, + 44.8386, + 497920270, + "India", + 1965 + ], + [ + 7048, + 70.447, + 97341852, + "Japan", + 1965 + ], + [ + 1630, + 48.6336, + 12547524, + "North Korea", + 1965 + ], + [ + 1416, + 57.3651, + 28392722, + "South Korea", + 1965 + ], + [ + 18632, + 71.433, + 2628003, + "New Zealand", + 1965 + ], + [ + 18980, + 73.568, + 3724065, + "Norway", + 1965 + ], + [ + 7409, + 69.617, + 31539695, + "Poland", + 1965 + ], + [ + 12363, + 69.116, + 126483874, + "Russia", + 1965 + ], + [ + 5309, + 52.5551, + 31000167, + "Turkey", + 1965 + ], + [ + 15292, + 71.43, + 54278349, + "United Kingdom", + 1965 + ], + [ + 21361, + 70.212, + 199403532, + "United States", + 1965 + ], + [ + 16756, + 70.9948, + 11657281, + "Australia", + 1966 + ], + [ + 18022, + 71.874, + 20041006, + "Canada", + 1966 + ], + [ + 826, + 54.364464, + 724490033, + "China", + 1966 + ], + [ + 9436, + 70.04688, + 8110428, + "Cuba", + 1966 + ], + [ + 13269, + 69.5028, + 4577033, + "Finland", + 1966 + ], + [ + 15158, + 71.8728, + 49411342, + "France", + 1966 + ], + [ + 18262, + 70.92828395, + 76558016, + "Germany", + 1966 + ], + [ + 15166, + 73.2208, + 194935, + "Iceland", + 1966 + ], + [ + 1037, + 45.59388, + 508402908, + "India", + 1966 + ], + [ + 7724, + 71.2596, + 98494630, + "Japan", + 1966 + ], + [ + 1616, + 49.60048, + 12864683, + "North Korea", + 1966 + ], + [ + 1563, + 58.21268, + 29006181, + "South Korea", + 1966 + ], + [ + 19467, + 71.2964, + 2668590, + "New Zealand", + 1966 + ], + [ + 19588, + 73.8444, + 3754010, + "Norway", + 1966 + ], + [ + 7818, + 70.0296, + 31824145, + "Poland", + 1966 + ], + [ + 12823, + 69.1788, + 127396324, + "Russia", + 1966 + ], + [ + 5906, + 53.33228, + 31718266, + "Turkey", + 1966 + ], + [ + 15494, + 71.346, + 54606608, + "United Kingdom", + 1966 + ], + [ + 22495, + 70.2276, + 201629471, + "United States", + 1966 + ], + [ + 17570, + 71.2786, + 11975795, + "Australia", + 1967 + ], + [ + 18240, + 72.083, + 20389445, + "Canada", + 1967 + ], + [ + 719, + 55.889368, + 744365635, + "China", + 1967 + ], + [ + 10372, + 70.69866, + 8263547, + "Cuba", + 1967 + ], + [ + 13477, + 69.6796, + 4584264, + "Finland", + 1967 + ], + [ + 15759, + 71.8696, + 49791771, + "France", + 1967 + ], + [ + 18311, + 71.15404398, + 77106876, + "Germany", + 1967 + ], + [ + 14734, + 73.7206, + 197356, + "Iceland", + 1967 + ], + [ + 1096, + 46.33916, + 519162069, + "India", + 1967 + ], + [ + 8454, + 71.5522, + 99711082, + "Japan", + 1967 + ], + [ + 1646, + 50.62536, + 13221826, + "North Korea", + 1967 + ], + [ + 1621, + 59.09526, + 29606633, + "South Korea", + 1967 + ], + [ + 18309, + 71.6798, + 2704205, + "New Zealand", + 1967 + ], + [ + 20686, + 73.9108, + 3784579, + "Norway", + 1967 + ], + [ + 8044, + 69.7322, + 32085011, + "Poland", + 1967 + ], + [ + 13256, + 68.9616, + 128165823, + "Russia", + 1967 + ], + [ + 6020, + 54.08346, + 32448404, + "Turkey", + 1967 + ], + [ + 15777, + 71.972, + 54904680, + "United Kingdom", + 1967 + ], + [ + 22803, + 70.5532, + 203713082, + "United States", + 1967 + ], + [ + 18261, + 70.9124, + 12305530, + "Australia", + 1968 + ], + [ + 18900, + 72.242, + 20739031, + "Canada", + 1968 + ], + [ + 669, + 56.860432, + 765570668, + "China", + 1968 + ], + [ + 9626, + 71.32644, + 8413329, + "Cuba", + 1968 + ], + [ + 13726, + 69.6364, + 4589226, + "Finland", + 1968 + ], + [ + 16321, + 71.8664, + 50126895, + "France", + 1968 + ], + [ + 19254, + 70.80345367, + 77611000, + "Germany", + 1968 + ], + [ + 13752, + 73.9304, + 199634, + "Iceland", + 1968 + ], + [ + 1095, + 47.07144, + 530274729, + "India", + 1968 + ], + [ + 9439, + 71.8748, + 100988866, + "Japan", + 1968 + ], + [ + 1673, + 51.61924, + 13608611, + "North Korea", + 1968 + ], + [ + 1774, + 60.00184, + 30204127, + "South Korea", + 1968 + ], + [ + 18082, + 71.3432, + 2738283, + "New Zealand", + 1968 + ], + [ + 21022, + 73.7872, + 3815399, + "Norway", + 1968 + ], + [ + 8473, + 70.3748, + 32330582, + "Poland", + 1968 + ], + [ + 13902, + 68.9144, + 128837792, + "Russia", + 1968 + ], + [ + 6295, + 54.80964, + 33196289, + "Turkey", + 1968 + ], + [ + 16357, + 71.598, + 55171084, + "United Kingdom", + 1968 + ], + [ + 23647, + 70.2088, + 205687611, + "United States", + 1968 + ], + [ + 18949, + 71.3262, + 12621240, + "Australia", + 1969 + ], + [ + 19614, + 72.401, + 21089228, + "Canada", + 1969 + ], + [ + 732, + 58.367416, + 787191243, + "China", + 1969 + ], + [ + 9377, + 71.92622, + 8563191, + "Cuba", + 1969 + ], + [ + 15058, + 69.5132, + 4595807, + "Finland", + 1969 + ], + [ + 17339, + 71.6032, + 50466183, + "France", + 1969 + ], + [ + 20409, + 70.65682236, + 78038271, + "Germany", + 1969 + ], + [ + 13983, + 73.7002, + 201941, + "Iceland", + 1969 + ], + [ + 1141, + 47.78972, + 541844848, + "India", + 1969 + ], + [ + 10548, + 72.1074, + 102323674, + "Japan", + 1969 + ], + [ + 1643, + 52.55012, + 14009168, + "North Korea", + 1969 + ], + [ + 1998, + 60.91542, + 30811523, + "South Korea", + 1969 + ], + [ + 19745, + 71.7166, + 2775684, + "New Zealand", + 1969 + ], + [ + 21845, + 73.4936, + 3845932, + "Norway", + 1969 + ], + [ + 8331, + 69.8674, + 32571673, + "Poland", + 1969 + ], + [ + 13972, + 68.3872, + 129475269, + "Russia", + 1969 + ], + [ + 6470, + 55.51382, + 33969201, + "Turkey", + 1969 + ], + [ + 16616, + 71.554, + 55406435, + "United Kingdom", + 1969 + ], + [ + 24147, + 70.4444, + 207599308, + "United States", + 1969 + ], + [ + 19719, + 71, + 12904760, + "Australia", + 1970 + ], + [ + 19842, + 72.6, + 21439200, + "Canada", + 1970 + ], + [ + 848, + 60, + 808510713, + "China", + 1970 + ], + [ + 8918, + 72.5, + 8715123, + "Cuba", + 1970 + ], + [ + 16245, + 70.2, + 4606740, + "Finland", + 1970 + ], + [ + 18185, + 72.5, + 50843830, + "France", + 1970 + ], + [ + 21218, + 70.9, + 78366605, + "Germany", + 1970 + ], + [ + 14937, + 73.8, + 204392, + "Iceland", + 1970 + ], + [ + 1170, + 48.5, + 553943226, + "India", + 1970 + ], + [ + 14203, + 72.2, + 103707537, + "Japan", + 1970 + ], + [ + 1697, + 53.4, + 14410400, + "North Korea", + 1970 + ], + [ + 2142, + 61.8, + 31437141, + "South Korea", + 1970 + ], + [ + 19200, + 71.5, + 2819548, + "New Zealand", + 1970 + ], + [ + 22186, + 73.9, + 3875719, + "Norway", + 1970 + ], + [ + 8705, + 70, + 32816751, + "Poland", + 1970 + ], + [ + 14915, + 68.5, + 130126383, + "Russia", + 1970 + ], + [ + 6740, + 56.2, + 34772031, + "Turkey", + 1970 + ], + [ + 16933, + 71.8, + 55611401, + "United Kingdom", + 1970 + ], + [ + 23908, + 70.7, + 209485807, + "United States", + 1970 + ], + [ + 20176, + 71.3, + 13150591, + "Australia", + 1971 + ], + [ + 20688, + 72.9, + 21790338, + "Canada", + 1971 + ], + [ + 876, + 60.6, + 829367784, + "China", + 1971 + ], + [ + 9471, + 73.2, + 8869961, + "Cuba", + 1971 + ], + [ + 16564, + 70.5, + 4623389, + "Finland", + 1971 + ], + [ + 18891, + 72.6, + 51273975, + "France", + 1971 + ], + [ + 21695, + 71, + 78584779, + "Germany", + 1971 + ], + [ + 16687, + 73.8, + 207050, + "Iceland", + 1971 + ], + [ + 1154, + 48.9, + 566605402, + "India", + 1971 + ], + [ + 14673, + 72.8, + 105142875, + "Japan", + 1971 + ], + [ + 1699, + 54.6, + 14812363, + "North Korea", + 1971 + ], + [ + 2427, + 62.3, + 32087884, + "South Korea", + 1971 + ], + [ + 19871, + 71.6, + 2871810, + "New Zealand", + 1971 + ], + [ + 23239, + 74.1, + 3904750, + "Norway", + 1971 + ], + [ + 9256, + 70.2, + 33068997, + "Poland", + 1971 + ], + [ + 15170, + 68.6, + 130808492, + "Russia", + 1971 + ], + [ + 6765, + 56.9, + 35608079, + "Turkey", + 1971 + ], + [ + 17207, + 72, + 55785325, + "United Kingdom", + 1971 + ], + [ + 24350, + 71, + 211357912, + "United States", + 1971 + ], + [ + 20385, + 71.7, + 13364238, + "Australia", + 1972 + ], + [ + 21532, + 72.9, + 22141998, + "Canada", + 1972 + ], + [ + 843, + 61.1, + 849787991, + "China", + 1972 + ], + [ + 9745, + 73.9, + 9025299, + "Cuba", + 1972 + ], + [ + 17722, + 70.9, + 4644847, + "Finland", + 1972 + ], + [ + 19570, + 72.8, + 51741044, + "France", + 1972 + ], + [ + 22497, + 71.2, + 78700104, + "Germany", + 1972 + ], + [ + 17413, + 73.9, + 209868, + "Iceland", + 1972 + ], + [ + 1125, + 49.3, + 579800632, + "India", + 1972 + ], + [ + 15694, + 73.2, + 106616535, + "Japan", + 1972 + ], + [ + 1730, + 55.7, + 15214615, + "North Korea", + 1972 + ], + [ + 2760, + 62.8, + 32759447, + "South Korea", + 1972 + ], + [ + 20349, + 71.8, + 2930469, + "New Zealand", + 1972 + ], + [ + 24308, + 74.3, + 3932945, + "Norway", + 1972 + ], + [ + 9854, + 70.6, + 33328713, + "Poland", + 1972 + ], + [ + 15113, + 68.7, + 131517584, + "Russia", + 1972 + ], + [ + 7186, + 57.7, + 36475356, + "Turkey", + 1972 + ], + [ + 17793, + 72, + 55927492, + "United Kingdom", + 1972 + ], + [ + 25374, + 71.3, + 213219515, + "United States", + 1972 + ], + [ + 21185, + 72, + 13552190, + "Australia", + 1973 + ], + [ + 22797, + 73.1, + 22488744, + "Canada", + 1973 + ], + [ + 894, + 61.7, + 869474823, + "China", + 1973 + ], + [ + 10439, + 74.1, + 9176051, + "Cuba", + 1973 + ], + [ + 18804, + 71.3, + 4668813, + "Finland", + 1973 + ], + [ + 20486, + 73.1, + 52214014, + "France", + 1973 + ], + [ + 23461, + 71.5, + 78732884, + "Germany", + 1973 + ], + [ + 18360, + 74.1, + 212731, + "Iceland", + 1973 + ], + [ + 1151, + 49.9, + 593451889, + "India", + 1973 + ], + [ + 16731, + 73.5, + 108085729, + "Japan", + 1973 + ], + [ + 1751, + 56.8, + 15603001, + "North Korea", + 1973 + ], + [ + 3326, + 63.3, + 33435268, + "South Korea", + 1973 + ], + [ + 21342, + 71.8, + 2989985, + "New Zealand", + 1973 + ], + [ + 25278, + 74.5, + 3959705, + "Norway", + 1973 + ], + [ + 10504, + 70.9, + 33597810, + "Poland", + 1973 + ], + [ + 16236, + 68.7, + 132254362, + "Russia", + 1973 + ], + [ + 7442, + 58.3, + 37366922, + "Turkey", + 1973 + ], + [ + 19043, + 72, + 56039166, + "United Kingdom", + 1973 + ], + [ + 26567, + 71.6, + 215092900, + "United States", + 1973 + ], + [ + 21383, + 72.1, + 13725400, + "Australia", + 1974 + ], + [ + 23405, + 73.2, + 22823272, + "Canada", + 1974 + ], + [ + 888, + 62.1, + 888132761, + "China", + 1974 + ], + [ + 10805, + 74.3, + 9315371, + "Cuba", + 1974 + ], + [ + 19273, + 71.4, + 4691818, + "Finland", + 1974 + ], + [ + 20997, + 73.3, + 52647616, + "France", + 1974 + ], + [ + 23662, + 71.8, + 78713928, + "Germany", + 1974 + ], + [ + 19123, + 74.3, + 215465, + "Iceland", + 1974 + ], + [ + 1139, + 50.4, + 607446519, + "India", + 1974 + ], + [ + 16320, + 73.9, + 109495053, + "Japan", + 1974 + ], + [ + 1782, + 57.9, + 15960127, + "North Korea", + 1974 + ], + [ + 3673, + 63.9, + 34091816, + "South Korea", + 1974 + ], + [ + 22131, + 72, + 3042573, + "New Zealand", + 1974 + ], + [ + 26252, + 74.7, + 3984291, + "Norway", + 1974 + ], + [ + 11020, + 71.2, + 33877397, + "Poland", + 1974 + ], + [ + 16594, + 68.6, + 133012558, + "Russia", + 1974 + ], + [ + 7991, + 58.9, + 38272701, + "Turkey", + 1974 + ], + [ + 18801, + 72.3, + 56122405, + "United Kingdom", + 1974 + ], + [ + 26258, + 72.1, + 217001865, + "United States", + 1974 + ], + [ + 21708, + 72.5, + 13892674, + "Australia", + 1975 + ], + [ + 23593, + 73.6, + 23140609, + "Canada", + 1975 + ], + [ + 920, + 62.6, + 905580445, + "China", + 1975 + ], + [ + 11176, + 74.6, + 9438445, + "Cuba", + 1975 + ], + [ + 19409, + 71.6, + 4711459, + "Finland", + 1975 + ], + [ + 20851, + 73.2, + 53010727, + "France", + 1975 + ], + [ + 23630, + 71.9, + 78667327, + "Germany", + 1975 + ], + [ + 19023, + 74.7, + 217958, + "Iceland", + 1975 + ], + [ + 1212, + 50.9, + 621703641, + "India", + 1975 + ], + [ + 16632, + 74.4, + 110804519, + "Japan", + 1975 + ], + [ + 1844, + 58.9, + 16274740, + "North Korea", + 1975 + ], + [ + 4108, + 64.4, + 34713078, + "South Korea", + 1975 + ], + [ + 21467, + 72.1, + 3082883, + "New Zealand", + 1975 + ], + [ + 27553, + 74.8, + 4006221, + "Norway", + 1975 + ], + [ + 11430, + 70.9, + 34168112, + "Poland", + 1975 + ], + [ + 16530, + 68.2, + 133788113, + "Russia", + 1975 + ], + [ + 8381, + 59.5, + 39185637, + "Turkey", + 1975 + ], + [ + 18699, + 72.6, + 56179925, + "United Kingdom", + 1975 + ], + [ + 25934, + 72.6, + 218963561, + "United States", + 1975 + ], + [ + 22372, + 73, + 14054956, + "Australia", + 1976 + ], + [ + 24563, + 73.9, + 23439940, + "Canada", + 1976 + ], + [ + 891, + 62.4, + 921688199, + "China", + 1976 + ], + [ + 11334, + 74.6, + 9544268, + "Cuba", + 1976 + ], + [ + 19268, + 72, + 4726803, + "Finland", + 1976 + ], + [ + 21661, + 73.4, + 53293030, + "France", + 1976 + ], + [ + 24904, + 72.3, + 78604473, + "Germany", + 1976 + ], + [ + 19978, + 75.2, + 220162, + "Iceland", + 1976 + ], + [ + 1201, + 51.4, + 636182810, + "India", + 1976 + ], + [ + 17117, + 74.9, + 111992858, + "Japan", + 1976 + ], + [ + 1851, + 59.8, + 16539029, + "North Korea", + 1976 + ], + [ + 4614, + 64.9, + 35290737, + "South Korea", + 1976 + ], + [ + 21749, + 72.3, + 3108745, + "New Zealand", + 1976 + ], + [ + 29117, + 75, + 4025297, + "Norway", + 1976 + ], + [ + 11605, + 70.8, + 34468877, + "Poland", + 1976 + ], + [ + 17192, + 68, + 134583945, + "Russia", + 1976 + ], + [ + 9142, + 60, + 40100696, + "Turkey", + 1976 + ], + [ + 19207, + 72.9, + 56212943, + "United Kingdom", + 1976 + ], + [ + 27041, + 72.9, + 220993166, + "United States", + 1976 + ], + [ + 22373, + 73.4, + 14211657, + "Australia", + 1977 + ], + [ + 25095, + 74.2, + 23723801, + "Canada", + 1977 + ], + [ + 904, + 63.3, + 936554514, + "China", + 1977 + ], + [ + 11712, + 74.4, + 9634677, + "Cuba", + 1977 + ], + [ + 19261, + 72.4, + 4738949, + "Finland", + 1977 + ], + [ + 22270, + 73.8, + 53509578, + "France", + 1977 + ], + [ + 25678, + 72.6, + 78524727, + "Germany", + 1977 + ], + [ + 21583, + 75.6, + 222142, + "Iceland", + 1977 + ], + [ + 1266, + 52, + 650907559, + "India", + 1977 + ], + [ + 17705, + 75.3, + 113067848, + "Japan", + 1977 + ], + [ + 1884, + 60.7, + 16758826, + "North Korea", + 1977 + ], + [ + 4964, + 65.4, + 35832213, + "South Korea", + 1977 + ], + [ + 20623, + 72.4, + 3122551, + "New Zealand", + 1977 + ], + [ + 30319, + 75.2, + 4041789, + "Norway", + 1977 + ], + [ + 11713, + 70.6, + 34779313, + "Poland", + 1977 + ], + [ + 17487, + 67.8, + 135406786, + "Russia", + 1977 + ], + [ + 8863, + 60.9, + 41020211, + "Turkey", + 1977 + ], + [ + 19684, + 73.1, + 56224944, + "United Kingdom", + 1977 + ], + [ + 27990, + 73.2, + 223090871, + "United States", + 1977 + ], + [ + 22763, + 73.8, + 14368543, + "Australia", + 1978 + ], + [ + 25853, + 74.4, + 23994948, + "Canada", + 1978 + ], + [ + 1016, + 63.7, + 950537317, + "China", + 1978 + ], + [ + 12312, + 74.5, + 9711393, + "Cuba", + 1978 + ], + [ + 19608, + 72.9, + 4749940, + "Finland", + 1978 + ], + [ + 22928, + 74.1, + 53685486, + "France", + 1978 + ], + [ + 26444, + 72.7, + 78426715, + "Germany", + 1978 + ], + [ + 22659, + 76, + 224019, + "Iceland", + 1978 + ], + [ + 1305, + 52.6, + 665936435, + "India", + 1978 + ], + [ + 18484, + 75.7, + 114054587, + "Japan", + 1978 + ], + [ + 1809, + 61.5, + 16953621, + "North Korea", + 1978 + ], + [ + 5373, + 66, + 36356187, + "South Korea", + 1978 + ], + [ + 20707, + 72.7, + 3129098, + "New Zealand", + 1978 + ], + [ + 31348, + 75.3, + 4056280, + "Norway", + 1978 + ], + [ + 12033, + 70.7, + 35100942, + "Poland", + 1978 + ], + [ + 17818, + 67.7, + 136259517, + "Russia", + 1978 + ], + [ + 8400, + 61.4, + 41953105, + "Turkey", + 1978 + ], + [ + 20337, + 73, + 56223974, + "United Kingdom", + 1978 + ], + [ + 29281, + 73.5, + 225239456, + "United States", + 1978 + ], + [ + 23697, + 74.2, + 14532401, + "Australia", + 1979 + ], + [ + 26665, + 74.7, + 24257594, + "Canada", + 1979 + ], + [ + 1059, + 64, + 964155176, + "China", + 1979 + ], + [ + 12519, + 74.6, + 9777287, + "Cuba", + 1979 + ], + [ + 20918, + 73.3, + 4762758, + "Finland", + 1979 + ], + [ + 23647, + 74.3, + 53857610, + "France", + 1979 + ], + [ + 27515, + 72.9, + 78305017, + "Germany", + 1979 + ], + [ + 23523, + 76.4, + 225972, + "Iceland", + 1979 + ], + [ + 1211, + 53.1, + 681358553, + "India", + 1979 + ], + [ + 19346, + 76.1, + 114993274, + "Japan", + 1979 + ], + [ + 2015, + 62.2, + 17151321, + "North Korea", + 1979 + ], + [ + 5505, + 66.5, + 36889651, + "South Korea", + 1979 + ], + [ + 21144, + 73, + 3135453, + "New Zealand", + 1979 + ], + [ + 32737, + 75.5, + 4069626, + "Norway", + 1979 + ], + [ + 11703, + 70.7, + 35435627, + "Poland", + 1979 + ], + [ + 17632, + 67.4, + 137144808, + "Russia", + 1979 + ], + [ + 8160, + 62, + 42912350, + "Turkey", + 1979 + ], + [ + 20871, + 73.1, + 56220089, + "United Kingdom", + 1979 + ], + [ + 29951, + 73.7, + 227411604, + "United States", + 1979 + ], + [ + 23872, + 74.5, + 14708323, + "Australia", + 1980 + ], + [ + 26678, + 75, + 24515788, + "Canada", + 1980 + ], + [ + 1073, + 64.5, + 977837433, + "China", + 1980 + ], + [ + 12284, + 74.6, + 9835177, + "Cuba", + 1980 + ], + [ + 21965, + 73.7, + 4779454, + "Finland", + 1980 + ], + [ + 23962, + 74.5, + 54053224, + "France", + 1980 + ], + [ + 27765, + 73.1, + 78159527, + "Germany", + 1980 + ], + [ + 24580, + 76.7, + 228127, + "Iceland", + 1980 + ], + [ + 1270, + 53.6, + 697229745, + "India", + 1980 + ], + [ + 19741, + 76.3, + 115912104, + "Japan", + 1980 + ], + [ + 1887, + 62.9, + 17372167, + "North Korea", + 1980 + ], + [ + 4899, + 66.9, + 37451085, + "South Korea", + 1980 + ], + [ + 21259, + 73.2, + 3146771, + "New Zealand", + 1980 + ], + [ + 34346, + 75.7, + 4082525, + "Norway", + 1980 + ], + [ + 11307, + 70.6, + 35782855, + "Poland", + 1980 + ], + [ + 17557, + 67.3, + 138063062, + "Russia", + 1980 + ], + [ + 7828, + 62.7, + 43905790, + "Turkey", + 1980 + ], + [ + 20417, + 73.4, + 56221513, + "United Kingdom", + 1980 + ], + [ + 29619, + 73.8, + 229588208, + "United States", + 1980 + ], + [ + 24308, + 74.8, + 14898019, + "Australia", + 1981 + ], + [ + 27171, + 75.4, + 24768525, + "Canada", + 1981 + ], + [ + 1099, + 64.8, + 991553829, + "China", + 1981 + ], + [ + 13224, + 74.6, + 9884219, + "Cuba", + 1981 + ], + [ + 22279, + 74, + 4800899, + "Finland", + 1981 + ], + [ + 24186, + 74.8, + 54279038, + "France", + 1981 + ], + [ + 27846, + 73.4, + 77990369, + "Germany", + 1981 + ], + [ + 25312, + 76.9, + 230525, + "Iceland", + 1981 + ], + [ + 1322, + 54.2, + 713561406, + "India", + 1981 + ], + [ + 20413, + 76.7, + 116821569, + "Japan", + 1981 + ], + [ + 2073, + 63.6, + 17623335, + "North Korea", + 1981 + ], + [ + 5159, + 67.5, + 38046253, + "South Korea", + 1981 + ], + [ + 22191, + 73.5, + 3164965, + "New Zealand", + 1981 + ], + [ + 34659, + 75.8, + 4095177, + "Norway", + 1981 + ], + [ + 10610, + 71, + 36145211, + "Poland", + 1981 + ], + [ + 17619, + 67.5, + 139006739, + "Russia", + 1981 + ], + [ + 8518, + 63.2, + 44936836, + "Turkey", + 1981 + ], + [ + 20149, + 73.8, + 56231020, + "United Kingdom", + 1981 + ], + [ + 30070, + 74, + 231765783, + "United States", + 1981 + ], + [ + 23884, + 75, + 15101227, + "Australia", + 1982 + ], + [ + 26031, + 75.8, + 25017501, + "Canada", + 1982 + ], + [ + 1175, + 65.2, + 1005328574, + "China", + 1982 + ], + [ + 13421, + 74.7, + 9925618, + "Cuba", + 1982 + ], + [ + 22873, + 74.3, + 4826135, + "Finland", + 1982 + ], + [ + 24753, + 75, + 54528408, + "France", + 1982 + ], + [ + 27645, + 73.6, + 77812348, + "Germany", + 1982 + ], + [ + 25455, + 77.1, + 233121, + "Iceland", + 1982 + ], + [ + 1334, + 54.6, + 730303461, + "India", + 1982 + ], + [ + 20951, + 77, + 117708919, + "Japan", + 1982 + ], + [ + 2180, + 64.2, + 17899236, + "North Korea", + 1982 + ], + [ + 5483, + 67.9, + 38665964, + "South Korea", + 1982 + ], + [ + 22436, + 73.7, + 3188664, + "New Zealand", + 1982 + ], + [ + 34704, + 75.9, + 4107655, + "Norway", + 1982 + ], + [ + 10420, + 71.2, + 36517072, + "Poland", + 1982 + ], + [ + 17951, + 67.9, + 139969243, + "Russia", + 1982 + ], + [ + 8323, + 63.7, + 45997940, + "Turkey", + 1982 + ], + [ + 20607, + 74.1, + 56250124, + "United Kingdom", + 1982 + ], + [ + 29230, + 74.4, + 233953874, + "United States", + 1982 + ], + [ + 23584, + 75.3, + 15318254, + "Australia", + 1983 + ], + [ + 26525, + 76.1, + 25272656, + "Canada", + 1983 + ], + [ + 1229, + 65.6, + 1019698475, + "China", + 1983 + ], + [ + 13669, + 74.6, + 9966733, + "Cuba", + 1983 + ], + [ + 23351, + 74.5, + 4853196, + "Finland", + 1983 + ], + [ + 25188, + 75.2, + 54799049, + "France", + 1983 + ], + [ + 28227, + 74, + 77657451, + "Germany", + 1983 + ], + [ + 24594, + 77.3, + 235860, + "Iceland", + 1983 + ], + [ + 1412, + 55.1, + 747374856, + "India", + 1983 + ], + [ + 21446, + 77.1, + 118552097, + "Japan", + 1983 + ], + [ + 2138, + 64.8, + 18191881, + "North Korea", + 1983 + ], + [ + 6078, + 68.4, + 39295418, + "South Korea", + 1983 + ], + [ + 22808, + 73.9, + 3215826, + "New Zealand", + 1983 + ], + [ + 35932, + 76, + 4120386, + "Norway", + 1983 + ], + [ + 10835, + 71.1, + 36879742, + "Poland", + 1983 + ], + [ + 18417, + 67.7, + 140951400, + "Russia", + 1983 + ], + [ + 8535, + 64.2, + 47072603, + "Turkey", + 1983 + ], + [ + 21357, + 74.3, + 56283959, + "United Kingdom", + 1983 + ], + [ + 30185, + 74.6, + 236161961, + "United States", + 1983 + ], + [ + 24934, + 75.5, + 15548591, + "Australia", + 1984 + ], + [ + 27781, + 76.4, + 25546736, + "Canada", + 1984 + ], + [ + 1456, + 66, + 1035328572, + "China", + 1984 + ], + [ + 14019, + 74.4, + 10017061, + "Cuba", + 1984 + ], + [ + 23926, + 74.6, + 4879222, + "Finland", + 1984 + ], + [ + 25497, + 75.5, + 55084677, + "France", + 1984 + ], + [ + 29135, + 74.4, + 77566776, + "Germany", + 1984 + ], + [ + 25356, + 77.4, + 238647, + "Iceland", + 1984 + ], + [ + 1436, + 55.5, + 764664278, + "India", + 1984 + ], + [ + 22268, + 77.4, + 119318921, + "Japan", + 1984 + ], + [ + 2205, + 65.4, + 18487997, + "North Korea", + 1984 + ], + [ + 6612, + 69, + 39912900, + "South Korea", + 1984 + ], + [ + 23698, + 74.1, + 3243078, + "New Zealand", + 1984 + ], + [ + 38057, + 76.1, + 4133833, + "Norway", + 1984 + ], + [ + 11138, + 70.8, + 37208529, + "Poland", + 1984 + ], + [ + 18527, + 67.4, + 141955200, + "Russia", + 1984 + ], + [ + 8798, + 64.8, + 48138191, + "Turkey", + 1984 + ], + [ + 21904, + 74.6, + 56337848, + "United Kingdom", + 1984 + ], + [ + 32110, + 74.8, + 238404223, + "United States", + 1984 + ], + [ + 25875, + 75.7, + 15791043, + "Australia", + 1985 + ], + [ + 29016, + 76.5, + 25848173, + "Canada", + 1985 + ], + [ + 1557, + 66.4, + 1052622410, + "China", + 1985 + ], + [ + 14135, + 74.3, + 10082990, + "Cuba", + 1985 + ], + [ + 24630, + 74.7, + 4902219, + "Finland", + 1985 + ], + [ + 25917, + 75.7, + 55379923, + "France", + 1985 + ], + [ + 29851, + 74.6, + 77570009, + "Germany", + 1985 + ], + [ + 25997, + 77.6, + 241411, + "Iceland", + 1985 + ], + [ + 1462, + 55.9, + 782085127, + "India", + 1985 + ], + [ + 23554, + 77.8, + 119988663, + "Japan", + 1985 + ], + [ + 2121, + 65.9, + 18778101, + "North Korea", + 1985 + ], + [ + 6970, + 69.5, + 40501917, + "South Korea", + 1985 + ], + [ + 23750, + 74.2, + 3268192, + "New Zealand", + 1985 + ], + [ + 40031, + 76.1, + 4148355, + "Norway", + 1985 + ], + [ + 11159, + 70.7, + 37486105, + "Poland", + 1985 + ], + [ + 18576, + 68.2, + 142975753, + "Russia", + 1985 + ], + [ + 9163, + 65.2, + 49178079, + "Turkey", + 1985 + ], + [ + 22648, + 74.7, + 56415196, + "United Kingdom", + 1985 + ], + [ + 33065, + 74.8, + 240691557, + "United States", + 1985 + ], + [ + 26057, + 76, + 16047026, + "Australia", + 1986 + ], + [ + 29482, + 76.6, + 26181342, + "Canada", + 1986 + ], + [ + 1604, + 66.8, + 1071834975, + "China", + 1986 + ], + [ + 14025, + 74.5, + 10167998, + "Cuba", + 1986 + ], + [ + 25133, + 74.7, + 4921293, + "Finland", + 1986 + ], + [ + 26453, + 76, + 55686610, + "France", + 1986 + ], + [ + 30514, + 74.8, + 77671877, + "Germany", + 1986 + ], + [ + 27379, + 77.6, + 244145, + "Iceland", + 1986 + ], + [ + 1493, + 56.3, + 799607235, + "India", + 1986 + ], + [ + 24116, + 78.1, + 120551455, + "Japan", + 1986 + ], + [ + 2106, + 66.4, + 19058988, + "North Korea", + 1986 + ], + [ + 7996, + 70, + 41059473, + "South Korea", + 1986 + ], + [ + 24180, + 74.2, + 3290132, + "New Zealand", + 1986 + ], + [ + 41450, + 76.1, + 4164166, + "Norway", + 1986 + ], + [ + 11429, + 70.9, + 37703942, + "Poland", + 1986 + ], + [ + 19221, + 69.8, + 144016095, + "Russia", + 1986 + ], + [ + 9556, + 65.7, + 50187091, + "Turkey", + 1986 + ], + [ + 23516, + 74.9, + 56519444, + "United Kingdom", + 1986 + ], + [ + 33899, + 74.9, + 243032017, + "United States", + 1986 + ], + [ + 26969, + 76.2, + 16314778, + "Australia", + 1987 + ], + [ + 30288, + 76.8, + 26541981, + "Canada", + 1987 + ], + [ + 1652, + 67.2, + 1092646739, + "China", + 1987 + ], + [ + 13805, + 74.6, + 10269276, + "Cuba", + 1987 + ], + [ + 26086, + 74.7, + 4937259, + "Finland", + 1987 + ], + [ + 26963, + 76.4, + 56005443, + "France", + 1987 + ], + [ + 30986, + 75.1, + 77864381, + "Germany", + 1987 + ], + [ + 29335, + 77.7, + 246867, + "Iceland", + 1987 + ], + [ + 1525, + 56.6, + 817232241, + "India", + 1987 + ], + [ + 25018, + 78.4, + 121021830, + "Japan", + 1987 + ], + [ + 2142, + 66.8, + 19334550, + "North Korea", + 1987 + ], + [ + 9096, + 70.4, + 41588374, + "South Korea", + 1987 + ], + [ + 24222, + 74.4, + 3310408, + "New Zealand", + 1987 + ], + [ + 42225, + 76.1, + 4181326, + "Norway", + 1987 + ], + [ + 11207, + 71.1, + 37867481, + "Poland", + 1987 + ], + [ + 19355, + 70.1, + 145056221, + "Russia", + 1987 + ], + [ + 10351, + 66.1, + 51168841, + "Turkey", + 1987 + ], + [ + 24551, + 75.1, + 56649375, + "United Kingdom", + 1987 + ], + [ + 34787, + 75, + 245425409, + "United States", + 1987 + ], + [ + 27757, + 76.4, + 16585905, + "Australia", + 1988 + ], + [ + 31356, + 77.1, + 26919036, + "Canada", + 1988 + ], + [ + 1597, + 67.5, + 1114162025, + "China", + 1988 + ], + [ + 13925, + 74.6, + 10379080, + "Cuba", + 1988 + ], + [ + 27282, + 74.8, + 4951886, + "Finland", + 1988 + ], + [ + 28101, + 76.6, + 56328053, + "France", + 1988 + ], + [ + 31906, + 75.3, + 78146938, + "Germany", + 1988 + ], + [ + 28780, + 77.8, + 249563, + "Iceland", + 1988 + ], + [ + 1649, + 57, + 834944397, + "India", + 1988 + ], + [ + 26724, + 78.6, + 121432942, + "Japan", + 1988 + ], + [ + 2198, + 67.2, + 19610512, + "North Korea", + 1988 + ], + [ + 10233, + 71, + 42085050, + "South Korea", + 1988 + ], + [ + 24060, + 74.6, + 3332297, + "New Zealand", + 1988 + ], + [ + 42101, + 76.3, + 4199817, + "Norway", + 1988 + ], + [ + 11418, + 71.2, + 37990683, + "Poland", + 1988 + ], + [ + 19660, + 70, + 146040116, + "Russia", + 1988 + ], + [ + 10421, + 66.5, + 52126497, + "Turkey", + 1988 + ], + [ + 25750, + 75.3, + 56797704, + "United Kingdom", + 1988 + ], + [ + 35929, + 75, + 247865202, + "United States", + 1988 + ], + [ + 28556, + 76.6, + 16849253, + "Australia", + 1989 + ], + [ + 31550, + 77.2, + 27296517, + "Canada", + 1989 + ], + [ + 1474, + 67.7, + 1135128009, + "China", + 1989 + ], + [ + 13829, + 74.7, + 10486110, + "Cuba", + 1989 + ], + [ + 28735, + 74.8, + 4967776, + "Finland", + 1989 + ], + [ + 28942, + 76.9, + 56643349, + "France", + 1989 + ], + [ + 32706, + 75.4, + 78514790, + "Germany", + 1989 + ], + [ + 28629, + 78, + 252219, + "Iceland", + 1989 + ], + [ + 1723, + 57.3, + 852736160, + "India", + 1989 + ], + [ + 28077, + 78.9, + 121831143, + "Japan", + 1989 + ], + [ + 2257, + 67.6, + 19895390, + "North Korea", + 1989 + ], + [ + 11002, + 71.5, + 42546704, + "South Korea", + 1989 + ], + [ + 24206, + 75, + 3360350, + "New Zealand", + 1989 + ], + [ + 42449, + 76.5, + 4219532, + "Norway", + 1989 + ], + [ + 11212, + 71.1, + 38094812, + "Poland", + 1989 + ], + [ + 19906, + 69.8, + 146895053, + "Russia", + 1989 + ], + [ + 10103, + 66.9, + 53066569, + "Turkey", + 1989 + ], + [ + 26279, + 75.5, + 56953861, + "United Kingdom", + 1989 + ], + [ + 36830, + 75.2, + 250340795, + "United States", + 1989 + ], + [ + 28604, + 77, + 17096869, + "Australia", + 1990 + ], + [ + 31163, + 77.4, + 27662440, + "Canada", + 1990 + ], + [ + 1516, + 68, + 1154605773, + "China", + 1990 + ], + [ + 13670, + 74.7, + 10582082, + "Cuba", + 1990 + ], + [ + 28599, + 75, + 4986705, + "Finland", + 1990 + ], + [ + 29476, + 77.1, + 56943299, + "France", + 1990 + ], + [ + 31476, + 75.4, + 78958237, + "Germany", + 1990 + ], + [ + 28666, + 78.1, + 254830, + "Iceland", + 1990 + ], + [ + 1777, + 57.7, + 870601776, + "India", + 1990 + ], + [ + 29550, + 79.1, + 122249285, + "Japan", + 1990 + ], + [ + 2076, + 67.9, + 20194354, + "North Korea", + 1990 + ], + [ + 12087, + 72, + 42972254, + "South Korea", + 1990 + ], + [ + 24021, + 75.4, + 3397534, + "New Zealand", + 1990 + ], + [ + 43296, + 76.8, + 4240375, + "Norway", + 1990 + ], + [ + 10088, + 70.8, + 38195258, + "Poland", + 1990 + ], + [ + 19349, + 69.6, + 147568552, + "Russia", + 1990 + ], + [ + 10670, + 67.3, + 53994605, + "Turkey", + 1990 + ], + [ + 26424, + 75.7, + 57110117, + "United Kingdom", + 1990 + ], + [ + 37062, + 75.4, + 252847810, + "United States", + 1990 + ], + [ + 28122, + 77.4, + 17325818, + "Australia", + 1991 + ], + [ + 30090, + 77.6, + 28014102, + "Canada", + 1991 + ], + [ + 1634, + 68.3, + 1172327831, + "China", + 1991 + ], + [ + 12113, + 74.7, + 10664577, + "Cuba", + 1991 + ], + [ + 26761, + 75.4, + 5009381, + "Finland", + 1991 + ], + [ + 29707, + 77.3, + 57226524, + "France", + 1991 + ], + [ + 32844, + 75.6, + 79483739, + "Germany", + 1991 + ], + [ + 28272, + 78.3, + 257387, + "Iceland", + 1991 + ], + [ + 1760, + 58, + 888513869, + "India", + 1991 + ], + [ + 30437, + 79.2, + 122702527, + "Japan", + 1991 + ], + [ + 1973, + 68.2, + 20510208, + "North Korea", + 1991 + ], + [ + 13130, + 72.5, + 43358716, + "South Korea", + 1991 + ], + [ + 22636, + 75.8, + 3445596, + "New Zealand", + 1991 + ], + [ + 44419, + 77.1, + 4262367, + "Norway", + 1991 + ], + [ + 9347, + 70.7, + 38297549, + "Poland", + 1991 + ], + [ + 18332, + 69.4, + 148040354, + "Russia", + 1991 + ], + [ + 10568, + 67.6, + 54909508, + "Turkey", + 1991 + ], + [ + 26017, + 76, + 57264600, + "United Kingdom", + 1991 + ], + [ + 36543, + 75.6, + 255367160, + "United States", + 1991 + ], + [ + 27895, + 77.7, + 17538387, + "Australia", + 1992 + ], + [ + 29977, + 77.7, + 28353843, + "Canada", + 1992 + ], + [ + 1845, + 68.6, + 1188450231, + "China", + 1992 + ], + [ + 10637, + 74.8, + 10735775, + "Cuba", + 1992 + ], + [ + 25726, + 75.8, + 5034898, + "Finland", + 1992 + ], + [ + 30033, + 77.5, + 57495252, + "France", + 1992 + ], + [ + 33221, + 75.9, + 80075940, + "Germany", + 1992 + ], + [ + 26977, + 78.5, + 259895, + "Iceland", + 1992 + ], + [ + 1821, + 58.3, + 906461358, + "India", + 1992 + ], + [ + 30610, + 79.4, + 123180357, + "Japan", + 1992 + ], + [ + 1745, + 68.4, + 20838082, + "North Korea", + 1992 + ], + [ + 13744, + 73, + 43708170, + "South Korea", + 1992 + ], + [ + 22651, + 76.1, + 3502765, + "New Zealand", + 1992 + ], + [ + 45742, + 77.3, + 4285504, + "Norway", + 1992 + ], + [ + 9553, + 71.1, + 38396826, + "Poland", + 1992 + ], + [ + 15661, + 68, + 148322473, + "Russia", + 1992 + ], + [ + 10920, + 67.9, + 55811134, + "Turkey", + 1992 + ], + [ + 26062, + 76.3, + 57419469, + "United Kingdom", + 1992 + ], + [ + 37321, + 75.8, + 257908206, + "United States", + 1992 + ], + [ + 28732, + 78, + 17738428, + "Australia", + 1993 + ], + [ + 30424, + 77.8, + 28680921, + "Canada", + 1993 + ], + [ + 2078, + 68.9, + 1202982955, + "China", + 1993 + ], + [ + 9001, + 74.8, + 10797556, + "Cuba", + 1993 + ], + [ + 25414, + 76.2, + 5061465, + "Finland", + 1993 + ], + [ + 29719, + 77.7, + 57749881, + "France", + 1993 + ], + [ + 32689, + 76.2, + 80675999, + "Germany", + 1993 + ], + [ + 27055, + 78.7, + 262383, + "Iceland", + 1993 + ], + [ + 1871, + 58.6, + 924475633, + "India", + 1993 + ], + [ + 30587, + 79.6, + 123658854, + "Japan", + 1993 + ], + [ + 1619, + 68.6, + 21166230, + "North Korea", + 1993 + ], + [ + 14466, + 73.5, + 44031222, + "South Korea", + 1993 + ], + [ + 23830, + 76.5, + 3564227, + "New Zealand", + 1993 + ], + [ + 46765, + 77.6, + 4309606, + "Norway", + 1993 + ], + [ + 9884, + 71.7, + 38485892, + "Poland", + 1993 + ], + [ + 14320, + 65.2, + 148435811, + "Russia", + 1993 + ], + [ + 11569, + 68.3, + 56707454, + "Turkey", + 1993 + ], + [ + 26688, + 76.5, + 57575969, + "United Kingdom", + 1993 + ], + [ + 37844, + 75.7, + 260527420, + "United States", + 1993 + ], + [ + 29580, + 78.2, + 17932214, + "Australia", + 1994 + ], + [ + 31505, + 77.9, + 28995822, + "Canada", + 1994 + ], + [ + 2323, + 69.3, + 1216067023, + "China", + 1994 + ], + [ + 9018, + 74.8, + 10853435, + "Cuba", + 1994 + ], + [ + 26301, + 76.5, + 5086499, + "Finland", + 1994 + ], + [ + 30303, + 77.9, + 57991973, + "France", + 1994 + ], + [ + 33375, + 76.4, + 81206786, + "Germany", + 1994 + ], + [ + 27789, + 78.8, + 264893, + "Iceland", + 1994 + ], + [ + 1959, + 59, + 942604211, + "India", + 1994 + ], + [ + 30746, + 79.8, + 124101546, + "Japan", + 1994 + ], + [ + 1605, + 68.8, + 21478544, + "North Korea", + 1994 + ], + [ + 15577, + 73.8, + 44342530, + "South Korea", + 1994 + ], + [ + 24716, + 76.7, + 3623181, + "New Zealand", + 1994 + ], + [ + 48850, + 77.8, + 4334434, + "Norway", + 1994 + ], + [ + 10386, + 71.8, + 38553355, + "Poland", + 1994 + ], + [ + 12535, + 63.6, + 148416292, + "Russia", + 1994 + ], + [ + 10857, + 68.6, + 57608769, + "Turkey", + 1994 + ], + [ + 27691, + 76.7, + 57736667, + "United Kingdom", + 1994 + ], + [ + 38892, + 75.8, + 263301323, + "United States", + 1994 + ], + [ + 30359, + 78.4, + 18124770, + "Australia", + 1995 + ], + [ + 32101, + 78, + 29299478, + "Canada", + 1995 + ], + [ + 2551, + 69.6, + 1227841281, + "China", + 1995 + ], + [ + 9195, + 74.9, + 10906048, + "Cuba", + 1995 + ], + [ + 27303, + 76.7, + 5108176, + "Finland", + 1995 + ], + [ + 30823, + 78.1, + 58224051, + "France", + 1995 + ], + [ + 33843, + 76.6, + 81612900, + "Germany", + 1995 + ], + [ + 27671, + 78.9, + 267454, + "Iceland", + 1995 + ], + [ + 2069, + 59.3, + 960874982, + "India", + 1995 + ], + [ + 31224, + 79.9, + 124483305, + "Japan", + 1995 + ], + [ + 1442, + 62.4, + 21763670, + "North Korea", + 1995 + ], + [ + 16798, + 74.2, + 44652994, + "South Korea", + 1995 + ], + [ + 25476, + 76.9, + 3674886, + "New Zealand", + 1995 + ], + [ + 50616, + 78, + 4359788, + "Norway", + 1995 + ], + [ + 11093, + 72, + 38591860, + "Poland", + 1995 + ], + [ + 12013, + 64.2, + 148293265, + "Russia", + 1995 + ], + [ + 11530, + 69, + 58522320, + "Turkey", + 1995 + ], + [ + 28317, + 76.8, + 57903790, + "United Kingdom", + 1995 + ], + [ + 39476, + 75.9, + 266275528, + "United States", + 1995 + ], + [ + 31145, + 78.6, + 18318340, + "Australia", + 1996 + ], + [ + 32290, + 78.3, + 29590952, + "Canada", + 1996 + ], + [ + 2775, + 69.9, + 1238234851, + "China", + 1996 + ], + [ + 9871, + 75.2, + 10955372, + "Cuba", + 1996 + ], + [ + 28210, + 76.9, + 5126021, + "Finland", + 1996 + ], + [ + 31141, + 78.4, + 58443318, + "France", + 1996 + ], + [ + 34008, + 76.9, + 81870772, + "Germany", + 1996 + ], + [ + 28839, + 79.1, + 270089, + "Iceland", + 1996 + ], + [ + 2186, + 59.6, + 979290432, + "India", + 1996 + ], + [ + 31958, + 80.3, + 124794817, + "Japan", + 1996 + ], + [ + 1393, + 62.6, + 22016510, + "North Korea", + 1996 + ], + [ + 17835, + 74.7, + 44967346, + "South Korea", + 1996 + ], + [ + 25984, + 77.1, + 3717239, + "New Zealand", + 1996 + ], + [ + 52892, + 78.1, + 4385951, + "Norway", + 1996 + ], + [ + 11776, + 72.4, + 38599825, + "Poland", + 1996 + ], + [ + 11597, + 65.9, + 148078355, + "Russia", + 1996 + ], + [ + 12190, + 69.4, + 59451488, + "Turkey", + 1996 + ], + [ + 28998, + 76.9, + 58079322, + "United Kingdom", + 1996 + ], + [ + 40501, + 76.3, + 269483224, + "United States", + 1996 + ], + [ + 32013, + 78.9, + 18512971, + "Australia", + 1997 + ], + [ + 33310, + 78.7, + 29871092, + "Canada", + 1997 + ], + [ + 3000, + 70.3, + 1247259143, + "China", + 1997 + ], + [ + 10106, + 75.3, + 11000431, + "Cuba", + 1997 + ], + [ + 29884, + 77.1, + 5140755, + "Finland", + 1997 + ], + [ + 31756, + 78.7, + 58652709, + "France", + 1997 + ], + [ + 34578, + 77.3, + 81993831, + "Germany", + 1997 + ], + [ + 30009, + 79.3, + 272798, + "Iceland", + 1997 + ], + [ + 2235, + 60, + 997817250, + "India", + 1997 + ], + [ + 32391, + 80.6, + 125048424, + "Japan", + 1997 + ], + [ + 1230, + 62.7, + 22240826, + "North Korea", + 1997 + ], + [ + 18687, + 75.1, + 45283939, + "South Korea", + 1997 + ], + [ + 26152, + 77.4, + 3752102, + "New Zealand", + 1997 + ], + [ + 55386, + 78.2, + 4412958, + "Norway", + 1997 + ], + [ + 12602, + 72.7, + 38583109, + "Poland", + 1997 + ], + [ + 11779, + 67.4, + 147772805, + "Russia", + 1997 + ], + [ + 12911, + 69.8, + 60394104, + "Turkey", + 1997 + ], + [ + 29662, + 77.2, + 58263858, + "United Kingdom", + 1997 + ], + [ + 41812, + 76.8, + 272882865, + "United States", + 1997 + ], + [ + 33085, + 79.1, + 18709175, + "Australia", + 1998 + ], + [ + 34389, + 78.9, + 30145148, + "Canada", + 1998 + ], + [ + 3205, + 70.7, + 1255262566, + "China", + 1998 + ], + [ + 10086, + 75.4, + 11041893, + "Cuba", + 1998 + ], + [ + 31423, + 77.3, + 5153229, + "Finland", + 1998 + ], + [ + 32764, + 78.8, + 58867465, + "France", + 1998 + ], + [ + 35254, + 77.7, + 82010184, + "Germany", + 1998 + ], + [ + 31601, + 79.5, + 275568, + "Iceland", + 1998 + ], + [ + 2332, + 60.3, + 1016402907, + "India", + 1998 + ], + [ + 31656, + 80.6, + 125266403, + "Japan", + 1998 + ], + [ + 1267, + 62.8, + 22444986, + "North Korea", + 1998 + ], + [ + 17493, + 75.4, + 45599569, + "South Korea", + 1998 + ], + [ + 26077, + 77.8, + 3783516, + "New Zealand", + 1998 + ], + [ + 56502, + 78.3, + 4440109, + "Norway", + 1998 + ], + [ + 13225, + 73, + 38550777, + "Poland", + 1998 + ], + [ + 11173, + 67.6, + 147385440, + "Russia", + 1998 + ], + [ + 13008, + 70.4, + 61344874, + "Turkey", + 1998 + ], + [ + 30614, + 77.4, + 58456989, + "United Kingdom", + 1998 + ], + [ + 43166, + 77, + 276354096, + "United States", + 1998 + ], + [ + 34346, + 79.3, + 18906936, + "Australia", + 1999 + ], + [ + 35810, + 79.1, + 30420216, + "Canada", + 1999 + ], + [ + 3419, + 71.1, + 1262713651, + "China", + 1999 + ], + [ + 10674, + 75.6, + 11080506, + "Cuba", + 1999 + ], + [ + 32743, + 77.5, + 5164780, + "Finland", + 1999 + ], + [ + 33707, + 78.9, + 59107738, + "France", + 1999 + ], + [ + 35931, + 77.9, + 81965830, + "Germany", + 1999 + ], + [ + 32521, + 79.7, + 278376, + "Iceland", + 1999 + ], + [ + 2496, + 60.7, + 1034976626, + "India", + 1999 + ], + [ + 31535, + 80.7, + 125481050, + "Japan", + 1999 + ], + [ + 1377, + 63, + 22641747, + "North Korea", + 1999 + ], + [ + 19233, + 75.8, + 45908307, + "South Korea", + 1999 + ], + [ + 27371, + 78.1, + 3817489, + "New Zealand", + 1999 + ], + [ + 57246, + 78.5, + 4466468, + "Norway", + 1999 + ], + [ + 13824, + 73.2, + 38515359, + "Poland", + 1999 + ], + [ + 11925, + 66.2, + 146924174, + "Russia", + 1999 + ], + [ + 12381, + 70.3, + 62295617, + "Turkey", + 1999 + ], + [ + 31474, + 77.6, + 58657794, + "United Kingdom", + 1999 + ], + [ + 44673, + 77.1, + 279730801, + "United States", + 1999 + ], + [ + 35253, + 79.7, + 19107251, + "Australia", + 2000 + ], + [ + 37314, + 79.3, + 30701903, + "Canada", + 2000 + ], + [ + 3678, + 71.5, + 1269974572, + "China", + 2000 + ], + [ + 11268, + 75.9, + 11116787, + "Cuba", + 2000 + ], + [ + 34517, + 77.8, + 5176482, + "Finland", + 2000 + ], + [ + 34774, + 79.1, + 59387183, + "France", + 2000 + ], + [ + 36953, + 78.1, + 81895925, + "Germany", + 2000 + ], + [ + 33599, + 79.9, + 281214, + "Iceland", + 2000 + ], + [ + 2548, + 61.1, + 1053481072, + "India", + 2000 + ], + [ + 32193, + 81.1, + 125714674, + "Japan", + 2000 + ], + [ + 1287, + 63.2, + 22840218, + "North Korea", + 2000 + ], + [ + 20757, + 76.3, + 46206271, + "South Korea", + 2000 + ], + [ + 27963, + 78.5, + 3858234, + "New Zealand", + 2000 + ], + [ + 58699, + 78.7, + 4491572, + "Norway", + 2000 + ], + [ + 14565, + 73.8, + 38486305, + "Poland", + 2000 + ], + [ + 13173, + 65.4, + 146400951, + "Russia", + 2000 + ], + [ + 13025, + 71.5, + 63240157, + "Turkey", + 2000 + ], + [ + 32543, + 77.8, + 58867004, + "United Kingdom", + 2000 + ], + [ + 45986, + 77.1, + 282895741, + "United States", + 2000 + ], + [ + 35452, + 80.1, + 19308681, + "Australia", + 2001 + ], + [ + 37563, + 79.5, + 30991344, + "Canada", + 2001 + ], + [ + 3955, + 71.9, + 1277188787, + "China", + 2001 + ], + [ + 11588, + 76.2, + 11151472, + "Cuba", + 2001 + ], + [ + 35327, + 78.2, + 5188446, + "Finland", + 2001 + ], + [ + 35197, + 79.2, + 59711914, + "France", + 2001 + ], + [ + 37517, + 78.3, + 81809438, + "Germany", + 2001 + ], + [ + 34403, + 80.2, + 284037, + "Iceland", + 2001 + ], + [ + 2628, + 61.5, + 1071888190, + "India", + 2001 + ], + [ + 32230, + 81.4, + 125974298, + "Japan", + 2001 + ], + [ + 1368, + 63.3, + 23043441, + "North Korea", + 2001 + ], + [ + 21536, + 76.8, + 46492324, + "South Korea", + 2001 + ], + [ + 28752, + 78.8, + 3906911, + "New Zealand", + 2001 + ], + [ + 59620, + 78.9, + 4514907, + "Norway", + 2001 + ], + [ + 14744, + 74.3, + 38466543, + "Poland", + 2001 + ], + [ + 13902, + 65.1, + 145818121, + "Russia", + 2001 + ], + [ + 12106, + 72, + 64182694, + "Turkey", + 2001 + ], + [ + 33282, + 78, + 59080221, + "United Kingdom", + 2001 + ], + [ + 45978, + 77.1, + 285796198, + "United States", + 2001 + ], + [ + 36375, + 80.4, + 19514385, + "Australia", + 2002 + ], + [ + 38270, + 79.7, + 31288572, + "Canada", + 2002 + ], + [ + 4285, + 72.4, + 1284349938, + "China", + 2002 + ], + [ + 11715, + 76.6, + 11184540, + "Cuba", + 2002 + ], + [ + 35834, + 78.5, + 5200632, + "Finland", + 2002 + ], + [ + 35333, + 79.4, + 60075783, + "France", + 2002 + ], + [ + 37458, + 78.5, + 81699829, + "Germany", + 2002 + ], + [ + 34252, + 80.5, + 286865, + "Iceland", + 2002 + ], + [ + 2684, + 61.9, + 1090189358, + "India", + 2002 + ], + [ + 32248, + 81.7, + 126249509, + "Japan", + 2002 + ], + [ + 1375, + 63.5, + 23248053, + "North Korea", + 2002 + ], + [ + 23008, + 77.3, + 46769579, + "South Korea", + 2002 + ], + [ + 29637, + 79, + 3961695, + "New Zealand", + 2002 + ], + [ + 60152, + 79.2, + 4537240, + "Norway", + 2002 + ], + [ + 14964, + 74.6, + 38454823, + "Poland", + 2002 + ], + [ + 14629, + 64.9, + 145195521, + "Russia", + 2002 + ], + [ + 12669, + 72.5, + 65125766, + "Turkey", + 2002 + ], + [ + 33954, + 78.2, + 59301235, + "United Kingdom", + 2002 + ], + [ + 46367, + 77.2, + 288470847, + "United States", + 2002 + ], + [ + 37035, + 80.7, + 19735255, + "Australia", + 2003 + ], + [ + 38621, + 79.9, + 31596593, + "Canada", + 2003 + ], + [ + 4685, + 72.9, + 1291485488, + "China", + 2003 + ], + [ + 12123, + 76.8, + 11214837, + "Cuba", + 2003 + ], + [ + 36461, + 78.6, + 5213800, + "Finland", + 2003 + ], + [ + 35371, + 79.7, + 60464857, + "France", + 2003 + ], + [ + 37167, + 78.8, + 81569481, + "Germany", + 2003 + ], + [ + 34938, + 80.8, + 289824, + "Iceland", + 2003 + ], + [ + 2850, + 62.4, + 1108369577, + "India", + 2003 + ], + [ + 32721, + 81.8, + 126523884, + "Japan", + 2003 + ], + [ + 1405, + 69.8, + 23449173, + "North Korea", + 2003 + ], + [ + 23566, + 77.8, + 47043251, + "South Korea", + 2003 + ], + [ + 30404, + 79.3, + 4020195, + "New Zealand", + 2003 + ], + [ + 60351, + 79.5, + 4560947, + "Norway", + 2003 + ], + [ + 15508, + 74.9, + 38451227, + "Poland", + 2003 + ], + [ + 15768, + 64.8, + 144583147, + "Russia", + 2003 + ], + [ + 13151, + 72.9, + 66060121, + "Turkey", + 2003 + ], + [ + 35250, + 78.5, + 59548421, + "United Kingdom", + 2003 + ], + [ + 47260, + 77.3, + 291005482, + "United States", + 2003 + ], + [ + 38130, + 81, + 19985475, + "Australia", + 2004 + ], + [ + 39436, + 80.1, + 31918582, + "Canada", + 2004 + ], + [ + 5127, + 73.4, + 1298573031, + "China", + 2004 + ], + [ + 12791, + 76.9, + 11240680, + "Cuba", + 2004 + ], + [ + 37783, + 78.6, + 5228842, + "Finland", + 2004 + ], + [ + 36090, + 80.1, + 60858654, + "France", + 2004 + ], + [ + 37614, + 79.1, + 81417791, + "Germany", + 2004 + ], + [ + 37482, + 81.1, + 293084, + "Iceland", + 2004 + ], + [ + 3029, + 62.8, + 1126419321, + "India", + 2004 + ], + [ + 33483, + 82, + 126773081, + "Japan", + 2004 + ], + [ + 1410, + 69.9, + 23639296, + "North Korea", + 2004 + ], + [ + 24628, + 78.3, + 47320454, + "South Korea", + 2004 + ], + [ + 31098, + 79.5, + 4078779, + "New Zealand", + 2004 + ], + [ + 62370, + 79.7, + 4589241, + "Norway", + 2004 + ], + [ + 16314, + 75, + 38454520, + "Poland", + 2004 + ], + [ + 16967, + 65, + 144043914, + "Russia", + 2004 + ], + [ + 14187, + 73.4, + 66973561, + "Turkey", + 2004 + ], + [ + 35910, + 78.8, + 59846226, + "United Kingdom", + 2004 + ], + [ + 48597, + 77.6, + 293530886, + "United States", + 2004 + ], + [ + 38840, + 81.2, + 20274282, + "Australia", + 2005 + ], + [ + 40284, + 80.3, + 32256333, + "Canada", + 2005 + ], + [ + 5675, + 73.9, + 1305600630, + "China", + 2005 + ], + [ + 14200, + 77.1, + 11261052, + "Cuba", + 2005 + ], + [ + 38700, + 78.8, + 5246368, + "Finland", + 2005 + ], + [ + 36395, + 80.4, + 61241700, + "France", + 2005 + ], + [ + 37901, + 79.4, + 81246801, + "Germany", + 2005 + ], + [ + 39108, + 81.3, + 296745, + "Iceland", + 2005 + ], + [ + 3262, + 63.2, + 1144326293, + "India", + 2005 + ], + [ + 33916, + 82.2, + 126978754, + "Japan", + 2005 + ], + [ + 1464, + 70.1, + 23813324, + "North Korea", + 2005 + ], + [ + 25541, + 78.8, + 47605863, + "South Korea", + 2005 + ], + [ + 31798, + 79.8, + 4134699, + "New Zealand", + 2005 + ], + [ + 63573, + 80.1, + 4624388, + "Norway", + 2005 + ], + [ + 16900, + 75, + 38463514, + "Poland", + 2005 + ], + [ + 18118, + 64.8, + 143622566, + "Russia", + 2005 + ], + [ + 15176, + 73.8, + 67860617, + "Turkey", + 2005 + ], + [ + 36665, + 79.1, + 60210012, + "United Kingdom", + 2005 + ], + [ + 49762, + 77.7, + 296139635, + "United States", + 2005 + ], + [ + 39416, + 81.4, + 20606228, + "Australia", + 2006 + ], + [ + 41012, + 80.5, + 32611436, + "Canada", + 2006 + ], + [ + 6360, + 74.4, + 1312600877, + "China", + 2006 + ], + [ + 15901, + 77.4, + 11275199, + "Cuba", + 2006 + ], + [ + 40115, + 79, + 5266600, + "Finland", + 2006 + ], + [ + 37001, + 80.7, + 61609991, + "France", + 2006 + ], + [ + 39352, + 79.7, + 81055904, + "Germany", + 2006 + ], + [ + 39818, + 81.5, + 300887, + "Iceland", + 2006 + ], + [ + 3514, + 63.6, + 1162088305, + "India", + 2006 + ], + [ + 34468, + 82.3, + 127136576, + "Japan", + 2006 + ], + [ + 1461, + 70.2, + 23969897, + "North Korea", + 2006 + ], + [ + 26734, + 79.2, + 47901643, + "South Korea", + 2006 + ], + [ + 32281, + 80, + 4187584, + "New Zealand", + 2006 + ], + [ + 64573, + 80.4, + 4667105, + "Norway", + 2006 + ], + [ + 17959, + 75, + 38478763, + "Poland", + 2006 + ], + [ + 19660, + 66.1, + 143338407, + "Russia", + 2006 + ], + [ + 16013, + 74.3, + 68704721, + "Turkey", + 2006 + ], + [ + 37504, + 79.3, + 60648850, + "United Kingdom", + 2006 + ], + [ + 50599, + 77.8, + 298860519, + "United States", + 2006 + ], + [ + 40643, + 81.5, + 20975949, + "Australia", + 2007 + ], + [ + 41432, + 80.6, + 32982275, + "Canada", + 2007 + ], + [ + 7225, + 74.9, + 1319625197, + "China", + 2007 + ], + [ + 17055, + 77.6, + 11284043, + "Cuba", + 2007 + ], + [ + 42016, + 79.2, + 5289333, + "Finland", + 2007 + ], + [ + 37641, + 80.9, + 61966193, + "France", + 2007 + ], + [ + 40693, + 79.8, + 80854515, + "Germany", + 2007 + ], + [ + 42598, + 81.8, + 305415, + "Iceland", + 2007 + ], + [ + 3806, + 64, + 1179685631, + "India", + 2007 + ], + [ + 35183, + 82.5, + 127250015, + "Japan", + 2007 + ], + [ + 1392, + 70.3, + 24111945, + "North Korea", + 2007 + ], + [ + 28063, + 79.5, + 48205062, + "South Korea", + 2007 + ], + [ + 32928, + 80.1, + 4238021, + "New Zealand", + 2007 + ], + [ + 65781, + 80.6, + 4716584, + "Norway", + 2007 + ], + [ + 19254, + 75.1, + 38500356, + "Poland", + 2007 + ], + [ + 21374, + 67.2, + 143180249, + "Russia", + 2007 + ], + [ + 16551, + 74.7, + 69515492, + "Turkey", + 2007 + ], + [ + 38164, + 79.4, + 61151820, + "United Kingdom", + 2007 + ], + [ + 51011, + 78.1, + 301655953, + "United States", + 2007 + ], + [ + 41312, + 81.5, + 21370348, + "Australia", + 2008 + ], + [ + 41468, + 80.7, + 33363256, + "Canada", + 2008 + ], + [ + 7880, + 75.1, + 1326690636, + "China", + 2008 + ], + [ + 17765, + 77.8, + 11290239, + "Cuba", + 2008 + ], + [ + 42122, + 79.4, + 5314170, + "Finland", + 2008 + ], + [ + 37505, + 81, + 62309529, + "France", + 2008 + ], + [ + 41199, + 80, + 80665906, + "Germany", + 2008 + ], + [ + 42294, + 82, + 310033, + "Iceland", + 2008 + ], + [ + 3901, + 64.4, + 1197070109, + "India", + 2008 + ], + [ + 34800, + 82.6, + 127317900, + "Japan", + 2008 + ], + [ + 1427, + 70.6, + 24243829, + "North Korea", + 2008 + ], + [ + 28650, + 79.7, + 48509842, + "South Korea", + 2008 + ], + [ + 32122, + 80.2, + 4285380, + "New Zealand", + 2008 + ], + [ + 65216, + 80.7, + 4771633, + "Norway", + 2008 + ], + [ + 19996, + 75.3, + 38525752, + "Poland", + 2008 + ], + [ + 22506, + 67.6, + 143123163, + "Russia", + 2008 + ], + [ + 16454, + 75.1, + 70344357, + "Turkey", + 2008 + ], + [ + 37739, + 79.5, + 61689620, + "United Kingdom", + 2008 + ], + [ + 50384, + 78.2, + 304473143, + "United States", + 2008 + ], + [ + 41170, + 81.6, + 21770690, + "Australia", + 2009 + ], + [ + 39884, + 80.9, + 33746559, + "Canada", + 2009 + ], + [ + 8565, + 75.6, + 1333807063, + "China", + 2009 + ], + [ + 18035, + 77.9, + 11297442, + "Cuba", + 2009 + ], + [ + 38455, + 79.7, + 5340485, + "Finland", + 2009 + ], + [ + 36215, + 81, + 62640901, + "France", + 2009 + ], + [ + 38975, + 80, + 80519685, + "Germany", + 2009 + ], + [ + 39979, + 82.2, + 314336, + "Iceland", + 2009 + ], + [ + 4177, + 64.7, + 1214182182, + "India", + 2009 + ], + [ + 32880, + 82.8, + 127340884, + "Japan", + 2009 + ], + [ + 1407, + 70.7, + 24371806, + "North Korea", + 2009 + ], + [ + 28716, + 79.8, + 48807036, + "South Korea", + 2009 + ], + [ + 31723, + 80.3, + 4329124, + "New Zealand", + 2009 + ], + [ + 63354, + 80.8, + 4830371, + "Norway", + 2009 + ], + [ + 20507, + 75.6, + 38551489, + "Poland", + 2009 + ], + [ + 20739, + 68.3, + 143126660, + "Russia", + 2009 + ], + [ + 15467, + 75.4, + 71261307, + "Turkey", + 2009 + ], + [ + 35840, + 79.7, + 62221164, + "United Kingdom", + 2009 + ], + [ + 48558, + 78.3, + 307231961, + "United States", + 2009 + ], + [ + 41330, + 81.7, + 22162863, + "Australia", + 2010 + ], + [ + 40773, + 81.1, + 34126173, + "Canada", + 2010 + ], + [ + 9430, + 75.9, + 1340968737, + "China", + 2010 + ], + [ + 18477, + 78, + 11308133, + "Cuba", + 2010 + ], + [ + 39425, + 80, + 5367693, + "Finland", + 2010 + ], + [ + 36745, + 81.2, + 62961136, + "France", + 2010 + ], + [ + 40632, + 80.2, + 80435307, + "Germany", + 2010 + ], + [ + 38809, + 82.5, + 318042, + "Iceland", + 2010 + ], + [ + 4547, + 65.1, + 1230984504, + "India", + 2010 + ], + [ + 34404, + 83, + 127319802, + "Japan", + 2010 + ], + [ + 1393, + 70.8, + 24500506, + "North Korea", + 2010 + ], + [ + 30440, + 80, + 49090041, + "South Korea", + 2010 + ], + [ + 31824, + 80.5, + 4369027, + "New Zealand", + 2010 + ], + [ + 62946, + 80.9, + 4891251, + "Norway", + 2010 + ], + [ + 21328, + 76.1, + 38574682, + "Poland", + 2010 + ], + [ + 21664, + 68.7, + 143158099, + "Russia", + 2010 + ], + [ + 16674, + 75.7, + 72310416, + "Turkey", + 2010 + ], + [ + 36240, + 80, + 62716684, + "United Kingdom", + 2010 + ], + [ + 49373, + 78.5, + 309876170, + "United States", + 2010 + ], + [ + 41706, + 81.8, + 22542371, + "Australia", + 2011 + ], + [ + 41567, + 81.3, + 34499905, + "Canada", + 2011 + ], + [ + 10274, + 76.1, + 1348174478, + "China", + 2011 + ], + [ + 19005, + 78.1, + 11323570, + "Cuba", + 2011 + ], + [ + 40251, + 80.3, + 5395816, + "Finland", + 2011 + ], + [ + 37328, + 81.4, + 63268405, + "France", + 2011 + ], + [ + 42080, + 80.3, + 80424665, + "Germany", + 2011 + ], + [ + 39619, + 82.7, + 321030, + "Iceland", + 2011 + ], + [ + 4787, + 65.5, + 1247446011, + "India", + 2011 + ], + [ + 34316, + 82.8, + 127252900, + "Japan", + 2011 + ], + [ + 1397, + 71, + 24631359, + "North Korea", + 2011 + ], + [ + 31327, + 80.3, + 49356692, + "South Korea", + 2011 + ], + [ + 32283, + 80.6, + 4404483, + "New Zealand", + 2011 + ], + [ + 62737, + 81.1, + 4953945, + "Norway", + 2011 + ], + [ + 22333, + 76.5, + 38594217, + "Poland", + 2011 + ], + [ + 22570, + 69.4, + 143211476, + "Russia", + 2011 + ], + [ + 17908, + 76, + 73517002, + "Turkey", + 2011 + ], + [ + 36549, + 80.4, + 63164949, + "United Kingdom", + 2011 + ], + [ + 49781, + 78.7, + 312390368, + "United States", + 2011 + ], + [ + 42522, + 81.8, + 22911375, + "Australia", + 2012 + ], + [ + 41865, + 81.4, + 34868151, + "Canada", + 2012 + ], + [ + 11017, + 76.3, + 1355386952, + "China", + 2012 + ], + [ + 19586, + 78.2, + 11342631, + "Cuba", + 2012 + ], + [ + 39489, + 80.5, + 5424644, + "Finland", + 2012 + ], + [ + 37227, + 81.6, + 63561798, + "France", + 2012 + ], + [ + 42959, + 80.5, + 80477952, + "Germany", + 2012 + ], + [ + 39925, + 82.8, + 323407, + "Iceland", + 2012 + ], + [ + 4967, + 65.9, + 1263589639, + "India", + 2012 + ], + [ + 34988, + 83.2, + 127139821, + "Japan", + 2012 + ], + [ + 1393, + 71.1, + 24763353, + "North Korea", + 2012 + ], + [ + 31901, + 80.4, + 49608451, + "South Korea", + 2012 + ], + [ + 32806, + 80.6, + 4435883, + "New Zealand", + 2012 + ], + [ + 63620, + 81.3, + 5018367, + "Norway", + 2012 + ], + [ + 22740, + 76.7, + 38609486, + "Poland", + 2012 + ], + [ + 23299, + 70.4, + 143287536, + "Russia", + 2012 + ], + [ + 18057, + 76.2, + 74849187, + "Turkey", + 2012 + ], + [ + 36535, + 80.8, + 63573766, + "United Kingdom", + 2012 + ], + [ + 50549, + 78.8, + 314799465, + "United States", + 2012 + ], + [ + 42840, + 81.8, + 23270465, + "Australia", + 2013 + ], + [ + 42213, + 81.5, + 35230612, + "Canada", + 2013 + ], + [ + 11805, + 76.5, + 1362514260, + "China", + 2013 + ], + [ + 20122, + 78.3, + 11362505, + "Cuba", + 2013 + ], + [ + 38788, + 80.6, + 5453061, + "Finland", + 2013 + ], + [ + 37309, + 81.7, + 63844529, + "France", + 2013 + ], + [ + 42887, + 80.7, + 80565861, + "Germany", + 2013 + ], + [ + 40958, + 82.8, + 325392, + "Iceland", + 2013 + ], + [ + 5244, + 66.2, + 1279498874, + "India", + 2013 + ], + [ + 35614, + 83.3, + 126984964, + "Japan", + 2013 + ], + [ + 1392, + 71.2, + 24895705, + "North Korea", + 2013 + ], + [ + 32684, + 80.5, + 49846756, + "South Korea", + 2013 + ], + [ + 33360, + 80.6, + 4465276, + "New Zealand", + 2013 + ], + [ + 63322, + 81.4, + 5083450, + "Norway", + 2013 + ], + [ + 23144, + 76.9, + 38618698, + "Poland", + 2013 + ], + [ + 23561, + 71.3, + 143367341, + "Russia", + 2013 + ], + [ + 18579, + 76.3, + 76223639, + "Turkey", + 2013 + ], + [ + 36908, + 81, + 63955654, + "United Kingdom", + 2013 + ], + [ + 51282, + 78.9, + 317135919, + "United States", + 2013 + ], + [ + 43219, + 81.8, + 23622353, + "Australia", + 2014 + ], + [ + 42817, + 81.6, + 35587793, + "Canada", + 2014 + ], + [ + 12609, + 76.7, + 1369435670, + "China", + 2014 + ], + [ + 20704, + 78.4, + 11379111, + "Cuba", + 2014 + ], + [ + 38569, + 80.7, + 5479660, + "Finland", + 2014 + ], + [ + 37218, + 81.8, + 64121249, + "France", + 2014 + ], + [ + 43444, + 80.9, + 80646262, + "Germany", + 2014 + ], + [ + 41237, + 82.8, + 327318, + "Iceland", + 2014 + ], + [ + 5565, + 66.5, + 1295291543, + "India", + 2014 + ], + [ + 35635, + 83.4, + 126794564, + "Japan", + 2014 + ], + [ + 1391, + 71.3, + 25026772, + "North Korea", + 2014 + ], + [ + 33629, + 80.6, + 50074401, + "South Korea", + 2014 + ], + [ + 33538, + 80.6, + 4495482, + "New Zealand", + 2014 + ], + [ + 64020, + 81.5, + 5147970, + "Norway", + 2014 + ], + [ + 23952, + 77.1, + 38619974, + "Poland", + 2014 + ], + [ + 23293, + 72.21, + 143429435, + "Russia", + 2014 + ], + [ + 18884, + 76.4, + 77523788, + "Turkey", + 2014 + ], + [ + 37614, + 81.2, + 64331348, + "United Kingdom", + 2014 + ], + [ + 52118, + 79, + 319448634, + "United States", + 2014 + ], + [ + 44056, + 81.8, + 23968973, + "Australia", + 2015 + ], + [ + 43294, + 81.7, + 35939927, + "Canada", + 2015 + ], + [ + 13334, + 76.9, + 1376048943, + "China", + 2015 + ], + [ + 21291, + 78.5, + 11389562, + "Cuba", + 2015 + ], + [ + 38923, + 80.8, + 5503457, + "Finland", + 2015 + ], + [ + 37599, + 81.9, + 64395345, + "France", + 2015 + ], + [ + 44053, + 81.1, + 80688545, + "Germany", + 2015 + ], + [ + 42182, + 82.8, + 329425, + "Iceland", + 2015 + ], + [ + 5903, + 66.8, + 1311050527, + "India", + 2015 + ], + [ + 36162, + 83.5, + 126573481, + "Japan", + 2015 + ], + [ + 1390, + 71.4, + 25155317, + "North Korea", + 2015 + ], + [ + 34644, + 80.7, + 50293439, + "South Korea", + 2015 + ], + [ + 34186, + 80.6, + 4528526, + "New Zealand", + 2015 + ], + [ + 64304, + 81.6, + 5210967, + "Norway", + 2015 + ], + [ + 24787, + 77.3, + 38611794, + "Poland", + 2015 + ], + [ + 23038, + 73.13, + 143456918, + "Russia", + 2015 + ], + [ + 19360, + 76.5, + 78665830, + "Turkey", + 2015 + ], + [ + 38225, + 81.4, + 64715810, + "United Kingdom", + 2015 + ], + [ + 53354, + 79.1, + 321773631, + "United States", + 2015 + ] +] \ No newline at end of file diff --git a/test/fixtures/resize_cfg.json b/test/fixtures/resize_cfg.json new file mode 100644 index 000000000..33a2031ef --- /dev/null +++ b/test/fixtures/resize_cfg.json @@ -0,0 +1 @@ +[{"cid": "xxx", "width": 100, "height": 100, "top": 100, "left": 100}] \ No newline at end of file diff --git a/test/test_bar.py b/test/test_bar.py index 1efda76b3..a4d6380f4 100644 --- a/test/test_bar.py +++ b/test/test_bar.py @@ -210,6 +210,40 @@ def test_bar_custom_remote_host(fake_writer): @patch("pyecharts.render.engine.write_utf8_html_file") def test_bar_graphic(fake_writer): + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .set_global_opts( + graphic_opts=[ + opts.GraphicImage( + graphic_item=opts.GraphicItem( + id_="logo", + right=20, + top=20, + z=-10, + bounding="raw", + origin=[75, 75], + ), + graphic_imagestyle_opts=opts.GraphicImageStyleOpts( + image="http://echarts.baidu.com/images/favicon.png", + width=150, + height=150, + opacity=0.4, + graphic_basicstyle_opts=opts.GraphicBasicStyleOpts(), + ), + ) + ] + ) + ) + c.render() + file_name, content = fake_writer.call_args[0] + assert_equal("render.html", file_name) + assert_in("graphic", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_bar_graphic_v1(fake_writer): c = ( Bar() .add_xaxis(["A", "B", "C"]) @@ -288,3 +322,29 @@ def test_bar_with_brush(fake_writer): c.render() _, content = fake_writer.call_args[0] assert_in("brush", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_bar_add_dataset(fake_writer): + c = ( + Bar() + .add_dataset( + source=[ + ["product", "2015", "2016", "2017"], + ["Matcha Latte", 43.3, 85.8, 93.7], + ["Milk Tea", 83.1, 73.4, 55.1], + ["Cheese Cocoa", 86.4, 65.2, 82.5], + ["Walnut Brownie", 72.4, 53.9, 39.1], + ] + ) + .add_yaxis(series_name="2015", y_axis=[]) + .add_yaxis(series_name="2016", y_axis=[]) + .add_yaxis(series_name="2017", y_axis=[]) + .set_global_opts( + title_opts=opts.TitleOpts(title="Dataset simple bar example"), + xaxis_opts=opts.AxisOpts(type_="category"), + ) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("dataset", content) diff --git a/test/test_base.py b/test/test_base.py index 1493bbb1a..dcb5f377b 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -1,9 +1,10 @@ +from datetime import datetime from unittest.mock import patch from nose.tools import assert_equal, assert_not_in from pyecharts.charts import Bar -from pyecharts.charts.base import Base +from pyecharts.charts.base import Base, default def test_base_add_functions(): @@ -30,3 +31,11 @@ def test_render(fake_writer): bar = Bar() bar.add_xaxis(["1"]).add_yaxis("", [1]).render(my_render_content=my_render_content) assert "test ok" == "test ok" + + +def test_base_iso_format(): + mock_time_str = "2022-04-14 14:42:00" + assert ( + default(datetime.strptime(mock_time_str, "%Y-%m-%d %H:%M:%S")) + == "2022-04-14T14:42:00" + ) diff --git a/test/test_boxplot.py b/test/test_boxplot.py index 31fe5e1ab..b44e6a24f 100644 --- a/test/test_boxplot.py +++ b/test/test_boxplot.py @@ -1,6 +1,6 @@ from unittest.mock import patch -from nose.tools import assert_equal +from nose.tools import assert_equal, assert_in from pyecharts import options as opts from pyecharts.charts import Boxplot @@ -17,13 +17,35 @@ def test_boxplot_base(fake_writer): [890, 840, 780, 810, 760, 810, 790, 810, 820, 850, 870, 870], ] c = Boxplot() - c.add_xaxis(["expr1", "expr2"]).add_yaxis("A", c.prepare_data(v1)).add_yaxis( - "B", c.prepare_data(v2) - ) + c.add_xaxis(["expr1", "expr2"]).add_yaxis( + "A", c.prepare_data(v1), box_width=40 + ).add_yaxis("B", c.prepare_data(v2)) c.render() _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + assert_in("boxWidth", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_boxplot_base_v1(fake_writer): + v1 = [ + [1000, 1000, 1000], + [200, 200, 200], + ] + v2 = [ + [1000, 1000, 1000], + [200, 200, 200], + ] + c = Boxplot() + c.add_xaxis(["expr1", "expr2"]).add_yaxis( + "A", c.prepare_data(v1), box_width=40 + ).add_yaxis("B", c.prepare_data(v2)) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + assert_in("boxWidth", content) @patch("pyecharts.render.engine.write_utf8_html_file") @@ -42,8 +64,7 @@ def test_boxplot_item_base(fake_writer): series_a = [opts.BoxplotItem(name=x_axis[0], value=d) for d in c.prepare_data(v1)] series_b = [opts.BoxplotItem(name=x_axis[1], value=d) for d in c.prepare_data(v2)] - c.add_xaxis(xaxis_data=x_axis).add_yaxis("A", series_a).add_yaxis( - "B", series_b) + c.add_xaxis(xaxis_data=x_axis).add_yaxis("A", series_a).add_yaxis("B", series_b) c.render() _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") diff --git a/test/test_calendar.py b/test/test_calendar.py index f30a9fbf8..6a9a2a992 100644 --- a/test/test_calendar.py +++ b/test/test_calendar.py @@ -35,6 +35,7 @@ def test_calendar_base(fake_writer): _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + assert_in("visualMap", content) @patch("pyecharts.render.engine.write_utf8_html_file") @@ -57,8 +58,6 @@ def test_calendar_setting(fake_writer): daylabel_opts=opts.CalendarDayLabelOpts(name_map="cn"), monthlabel_opts=opts.CalendarMonthLabelOpts(name_map="cn"), ), - ) - .set_global_opts( visualmap_opts=opts.VisualMapOpts( max_=20000, min_=500, @@ -74,3 +73,4 @@ def test_calendar_setting(fake_writer): assert_in("cellSize", content) assert_in("dayLabel", content) assert_in("monthLabel", content) + assert_in("visualMap", content) diff --git a/test/test_chart.py b/test/test_chart.py index 87b265a22..56c517c08 100644 --- a/test/test_chart.py +++ b/test/test_chart.py @@ -1,7 +1,179 @@ from unittest.mock import patch -from nose.tools import assert_equal -from pyecharts.charts import Line +from nose.tools import assert_equal, assert_in + +from pyecharts import options as opts +from pyecharts.charts import Line, Bar + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_dark_mode(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_dark_mode() + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("darkMode", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_line_style_opts(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(linestyle_opts=opts.LineStyleOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("lineStyle", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_split_line_style_opts(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(splitline_opts=opts.SplitLineOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("splitLine", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_area_style_opts(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(areastyle_opts=opts.AreaStyleOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("areaStyle", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_axis_line_opts(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(axisline_opts=opts.AxisLineOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("axisLine", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_mark_point_opts(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(markpoint_opts=opts.MarkPointOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("markPoint", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_mark_line_opts(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(markline_opts=opts.MarkLineOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("markLine", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_mark_area_opts(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(markarea_opts=opts.MarkAreaOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("markArea", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_tooltip_opts(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(tooltip_opts=opts.TooltipOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("tooltip", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_item_style_opts(fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(itemstyle_opts=opts.ItemStyleOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("itemStyle", content) @patch("pyecharts.render.engine.write_utf8_html_file") @@ -31,3 +203,68 @@ def test_chart_append_color(fake_writer): ).split() expected_result = ["#80FFA5", "#00DDFF", *default_colors] assert_equal(c.colors, expected_result) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_add_dataset(fake_writer): + c = ( + Bar() + .add_dataset( + source=[ + ["product", "2015", "2016", "2017"], + ["Matcha Latte", 43.3, 85.8, 93.7], + ["Milk Tea", 83.1, 73.4, 55.1], + ["Cheese Cocoa", 86.4, 65.2, 82.5], + ["Walnut Brownie", 72.4, 53.9, 39.1], + ] + ) + .add_dataset(from_dataset_index=1, from_transform_result=1) + .add_yaxis(series_name="2015", y_axis=[]) + .add_yaxis(series_name="2016", y_axis=[]) + .add_yaxis(series_name="2017", y_axis=[]) + .set_global_opts( + title_opts=opts.TitleOpts(title="Dataset simple bar example"), + xaxis_opts=opts.AxisOpts(type_="category"), + ) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("dataset", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_chart_extend_axis(fake_writer): + v1 = [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3] + v2 = [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3] + v3 = [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2] + + bar = ( + Bar() + .add_xaxis([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + .add_yaxis("蒸发量", v1) + .add_yaxis("降水量", v2) + .extend_axis( + xaxis=opts.AxisOpts(), + yaxis=opts.AxisOpts( + axislabel_opts=opts.LabelOpts(formatter="{value} °C"), interval=5 + ) + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts( + title_opts=opts.TitleOpts(title="Overlap-bar+line"), + yaxis_opts=opts.AxisOpts( + axislabel_opts=opts.LabelOpts(formatter="{value} ml") + ), + ) + ) + + line = ( + Line() + .add_xaxis([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + .add_yaxis("平均温度", v3, yaxis_index=0, z_level=999) + .set_global_opts(xaxis_opts=opts.AxisOpts(type_="value")) + ) + bar.overlap(line) + bar.render() + _, content = fake_writer.call_args[0] + assert_in("xAxis", content) diff --git a/test/test_chart_diff.py b/test/test_chart_diff.py deleted file mode 100644 index d2a610d38..000000000 --- a/test/test_chart_diff.py +++ /dev/null @@ -1,27 +0,0 @@ -# encoding: utf-8 -""" -@file: test_chart_diff.py -@desc: -@author: guozhen3 -@time: 2022/1/28 -""" -from pyecharts.charts import Line - -x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] -y_data1 = [140, 232, 101, 264, 90, 340, 250] -y_data2 = [120, 282, 111, 234, 220, 340, 310] - -c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis( - series_name="品类 1", - y_axis=y_data1, - color='#80FFA5') - .add_yaxis( - series_name="品类 2", - y_axis=y_data2, - color='#00DDFF') - ) - -c.render('rend2.html') \ No newline at end of file diff --git a/test/test_chart_options.py b/test/test_chart_options.py new file mode 100644 index 000000000..17e64bbbc --- /dev/null +++ b/test/test_chart_options.py @@ -0,0 +1,101 @@ +from nose.tools import assert_equal + +from pyecharts.commons.utils import remove_key_with_none_value +from pyecharts.options.charts_options import ( + BarBackgroundStyleOpts, + GlobeLayersOpts, + GraphCategory, + ThemeRiverItem, + TimelineCheckPointerStyle, + TimelineControlStyle, + TreeItem, + TreeMapItemStyleOpts, +) + + +def test_bar_background_style_options_remove_none(): + option = BarBackgroundStyleOpts() + expected = { + "color": "rgba(180, 180, 180, 0.2)", + "borderColor": "#000", + "borderWidth": 0, + "borderType": "solid", + "barBorderRadius": 0, + "shadowOffsetX": 0, + "shadowOffsetY": 0, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_globe_layers_options_remove_none(): + option = GlobeLayersOpts() + expected = { + "show": True, + "type": "overlay", + "blendTo": "albedo", + "intensity": 1, + "shading": "lambert", + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_theme_river_item_remove_none(): + item = ThemeRiverItem() + expected = {} + assert_equal(expected, remove_key_with_none_value(item.opts)) + + +def test_tree_river_item_remove_none(): + item = TreeItem() + expected = {} + assert_equal(expected, remove_key_with_none_value(item.opts)) + + +def test_timeline_check_pointer_style_options_remove_none(): + option = TimelineCheckPointerStyle(symbol_offset=None) + expected = { + "symbol": "circle", + "symbolSize": 13, + "symbolKeepAspect": False, + "symbolOffset": [0, 0], + "color": "#c23531", + "borderWidth": 5, + "borderColor": "rgba(194,53,49,0.5)", + "animation": True, + "animationDuration": 300, + "animationEasing": "quinticInOut", + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_timeline_control_style_options_remove_none(): + option = TimelineControlStyle() + expected = { + "show": True, + "showPlayBtn": True, + "showPrevBtn": True, + "showNextBtn": True, + "itemSize": 22, + "itemGap": 12, + "position": "left", + "color": "#304654", + "borderColor": "#304654", + "borderWidth": 1, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_graph_category_item_remove_none(): + item = GraphCategory() + expected = {} + assert_equal(expected, remove_key_with_none_value(item.opts)) + + +def test_tree_map_item_style_options_remove_none(): + option = TreeMapItemStyleOpts() + expected = { + "gapWidth": 0, + "borderWidth": 0, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + diff --git a/test/test_datasets.py b/test/test_datasets.py index fcbf0aae2..df5e33f86 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -1,9 +1,16 @@ import os +import urllib.error from unittest.mock import patch from nose.tools import assert_equal, raises -from pyecharts.datasets import EXTRA, FuzzyDict, register_url +from pyecharts.datasets import ( + EXTRA, + FuzzyDict, + register_url, + register_files, + register_coords, +) @patch("pyecharts.datasets.urllib.request.urlopen") @@ -25,6 +32,13 @@ def test_register_url(fake): ) +def test_register_url_error(): + try: + register_url("http://127.0.0.1") + except urllib.error.HTTPError as err: + assert_equal(type(err), urllib.error.HTTPError) + + def test_fuzzy_search_dict(): fd = FuzzyDict() fd.update({"我是北京市": [1, 2]}) @@ -36,3 +50,11 @@ def test_fuzzy_search_key_error(): fd = FuzzyDict() fd.cutoff = 0.9 _ = fd["我是北京"] + + +def test_register_files(): + register_files(asset_files={"x": 1}) + + +def test_register_coords(): + register_coords(coords={"深圳": [113, 23]}) diff --git a/test/test_display.py b/test/test_display.py index 8d0abea97..bcc5c7b8c 100644 --- a/test/test_display.py +++ b/test/test_display.py @@ -15,3 +15,9 @@ def test_display_javascript(): obj = Javascript(js_content) assert_equal(obj.data, js_content) assert_equal(obj._repr_javascript_(), js_content) + + +def test_display_javascript_v1(): + js_content = "console.log('hello world')" + obj = Javascript(js_content, lib="test lib", css="test css") + assert_equal(obj.data, js_content) diff --git a/test/test_exception.py b/test/test_exception.py index 6d6f1efd9..e52784834 100644 --- a/test/test_exception.py +++ b/test/test_exception.py @@ -19,6 +19,7 @@ def test_geo_catch_nonexistent_coord_exception(): ) except NonexistentCoordinatesException as err: assert_equal(type(err), NonexistentCoordinatesException) + assert err.__str__() != '' def test_geo_ignore_nonexistent_coord_exception(): diff --git a/test/test_faker.py b/test/test_faker.py new file mode 100644 index 000000000..301616fcc --- /dev/null +++ b/test/test_faker.py @@ -0,0 +1,14 @@ +from unittest.mock import patch + +from nose.tools import assert_equal + +from pyecharts.faker import Faker, Collector + + +def test_rand_color(): + rand_color = Faker.rand_color() + assert rand_color is not None + + +def test_img_path(): + assert_equal(Faker.img_path(path="/usr/local"), "/usr/local") diff --git a/test/test_geo.py b/test/test_geo.py index ca0d4623a..665cbd55b 100644 --- a/test/test_geo.py +++ b/test/test_geo.py @@ -5,6 +5,7 @@ from pyecharts import options as opts from pyecharts.charts import Geo from pyecharts.faker import Faker +from pyecharts.globals import ChartType, SymbolType @patch("pyecharts.render.engine.write_utf8_html_file") @@ -22,6 +23,107 @@ def test_geo_base(fake_writer): assert_in("canvas", content) +def test_geo_add_coord_json(): + c = ( + Geo() + .add_schema(maptype="china") + .add_coordinate_json(json_file="fixtures/city_coordinates.json") + .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) + ) + assert_equal(c.theme, "white") + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_geo_custom(fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + [list(z) for z in zip(Faker.provinces, Faker.values())], + type_=ChartType.CUSTOM, + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(title_opts=opts.TitleOpts(title="Geo-EffectScatter")) + ) + assert_equal(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + assert_in("canvas", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_geo_effectscatter(fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + [list(z) for z in zip(Faker.provinces, Faker.values())], + type_=ChartType.EFFECT_SCATTER, + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(title_opts=opts.TitleOpts(title="Geo-EffectScatter")) + ) + assert_equal(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + assert_in("canvas", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_geo_heatmap(fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + [list(z) for z in zip(Faker.provinces, Faker.values())], + type_=ChartType.HEATMAP, + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(), + title_opts=opts.TitleOpts(title="Geo-HeatMap"), + ) + ) + assert_equal(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + assert_in("canvas", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_geo_lines(fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "", + [("广州", 55), ("北京", 66), ("杭州", 77), ("重庆", 88)], + type_=ChartType.EFFECT_SCATTER, + color="white", + ) + .add( + "geo", + [("广州", "上海"), ("广州", "北京"), ("广州", "杭州"), ("广州", "重庆")], + type_=ChartType.LINES, + effect_opts=opts.EffectOpts( + symbol=SymbolType.ARROW, symbol_size=6, color="blue" + ), + linestyle_opts=opts.LineStyleOpts(curve=0.2), + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(title_opts=opts.TitleOpts(title="Geo-Lines")) + ) + assert_equal(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + assert_in("canvas", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") def test_extra_geo_parameters(fake_writer): c = ( diff --git a/test/test_global_options.py b/test/test_global_options.py index 3eceede7d..577f81b67 100644 --- a/test/test_global_options.py +++ b/test/test_global_options.py @@ -3,7 +3,17 @@ from pyecharts.commons.utils import remove_key_with_none_value from pyecharts.options.global_options import ( AnimationOpts, + AngleAxisItem, + AngleAxisOpts, + AriaLabelOpts, + AriaDecalOpts, + AxisTickOpts, + CalendarYearLabelOpts, + DatasetTransformOpts, InitOpts, + ParallelAxisOpts, + RadiusAxisItem, + RadiusAxisOpts, ToolBoxFeatureBrushOpts, ToolBoxFeatureDataViewOpts, ToolBoxFeatureDataZoomOpts, @@ -35,6 +45,63 @@ def test_animation_options_remove_none(): assert_equal(expected, remove_key_with_none_value(option.opts)) +def test_aria_label_options_remove_none(): + option = AriaLabelOpts() + expected = { + "enabled": True, + "general": { + "withTitle": "这是一个关于“{title}”的图表。", + "withoutTitle": "这是一个图表,", + }, + "series": { + "maxCount": 10, + "single": { + "withName": "图表类型是{seriesType},表示{seriesName}。", + "withoutName": "图表类型是{seriesType}。", + }, + "multiple": { + "prefix": "它由{seriesCount}个图表系列组成。", + "withName": "图表类型是{seriesType},表示{seriesName}。", + "withoutName": "图表类型是{seriesType}。", + "separator": { + "middle": ";", + "end": "。", + } + }, + }, + "data": { + "maxCount": 10, + "allData": "其数据是——", + "partialData": "其中,前{displayCnt}项是——", + "withName": "{name}的数据是{value}", + "withoutName": "{value}", + "separator": { + "middle": ",", + } + } + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_aria_decal_options_remove_none(): + option = AriaDecalOpts() + expected = { + "show": False, + "decals": { + "symbol": "rect", + "symbolSize": 1, + "symbolKeepAspect": True, + "color": "rgba(0, 0, 0, 0.2)", + "dashArrayX": 5, + "dashArrayY": 5, + "rotation": 0, + "maxTileWidth": 512, + "maxTileHeight": 512, + } + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + def test_init_options_remove_none(): option = InitOpts(animation_opts={}, aria_opts={}) expected = { @@ -138,10 +205,10 @@ def test_legend_options_remove_none(): def test_visual_map_options_remove_none(): - option = VisualMapOpts() + option = VisualMapOpts(range_opacity=0.1) expected = { "calculable": True, - "inRange": {"color": ["#50a3ba", "#eac763", "#d94e5d"]}, + "inRange": {"color": ["#50a3ba", "#eac763", "#d94e5d"], "opacity": 0.1}, "itemHeight": 140, "itemWidth": 20, "max": 100, @@ -179,3 +246,91 @@ def test_tool_tip_options_remove_none(): "triggerOn": "mousemove|click", } assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_axis_tick_options_remove_none(): + option = AxisTickOpts() + expected = { + "show": True, + "alignWithLabel": False, + "inside": False, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_parallel_axis_options_remove_none(): + option = ParallelAxisOpts(dim=1) + expected = { + "dim": 1, + "parallelIndex": 0, + "realtime": True, + "name_location": "end", + "name_gap": 15, + "inverse": False, + "scale": False, + "logBase": 10, + "silent": False, + "triggerEvent": False, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_calendar_year_label_options_remove_none(): + option = CalendarYearLabelOpts() + expected = { + "show": True, + "color": "#000", + "fontStyle": "normal", + "fontWeight": "normal", + "fontFamily": "sans-serif", + "fontSize": 12, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_angle_axis_item_remove_none(): + item = AngleAxisItem(textstyle_opts=None) + expected = {} + assert_equal(expected, remove_key_with_none_value(item.opts)) + + +def test_angle_axis_options_remove_none(): + mock_data = [AngleAxisItem(value="1"), AngleAxisItem(value="2")] + option = AngleAxisOpts(data=mock_data) + expected = { + "data": mock_data, + "startAngle": 90, + "clockwise": False, + "scale": False, + "splitNumber": 5, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_radius_axis_item_remove_none(): + item = RadiusAxisItem() + expected = {} + assert_equal(expected, remove_key_with_none_value(item.opts)) + + +def test_radius_axis_options_remove_none(): + mock_data = [RadiusAxisItem(value="1"), RadiusAxisItem(value="2")] + option = RadiusAxisOpts(data=mock_data) + expected = { + "data": mock_data, + "nameGap": 15, + "inverse": False, + "scale": False, + "splitNumber": 5, + "minInterval": 0, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_dataset_transform_options_remove_none(): + option = DatasetTransformOpts() + expected = { + "type": "filter", + "print": False + } + assert_equal(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_graph.py b/test/test_graph.py index bbd170f2d..1f84d00f4 100644 --- a/test/test_graph.py +++ b/test/test_graph.py @@ -1,3 +1,5 @@ +import os +import json from unittest.mock import patch from nose.tools import assert_equal, assert_in @@ -25,6 +27,70 @@ def test_graph_base(fake_writer): assert_equal(c.renderer, "canvas") +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_graph_base_v1(fake_writer): + nodes = [ + opts.GraphNode(name="结点1", symbol_size=10), + opts.GraphNode(name="结点2", symbol_size=20), + opts.GraphNode(name="结点3", symbol_size=30), + opts.GraphNode(name="结点4", symbol_size=40), + opts.GraphNode(name="结点5", symbol_size=50), + ] + links = [ + opts.GraphLink(source="结点1", target="结点2"), + opts.GraphLink(source="结点2", target="结点3"), + opts.GraphLink(source="结点3", target="结点4"), + opts.GraphLink(source="结点4", target="结点5"), + opts.GraphLink(source="结点5", target="结点1"), + ] + c = ( + Graph() + .add("", nodes, links, repulsion=4000) + .set_global_opts(title_opts=opts.TitleOpts(title="Graph-GraphNode-GraphLink")) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_graph_base_v2(fake_writer): + with open( + os.path.join("fixtures", "les-miserables.json"), "r", encoding="utf-8" + ) as f: + j = json.load(f) + nodes = j["nodes"] + links = j["links"] + categories = j["categories"] + + categories = [opts.GraphCategory(name=d.get("name")) for d in categories] + + c = ( + Graph(init_opts=opts.InitOpts(width="1000px", height="600px")) + .add( + "", + nodes=nodes, + links=links, + categories=categories, + layout="circular", + is_rotate_label=True, + linestyle_opts=opts.LineStyleOpts(color="source", curve=0.3), + label_opts=opts.LabelOpts(position="right"), + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="Graph-Les Miserables"), + legend_opts=opts.LegendOpts( + orient="vertical", pos_left="2%", pos_top="20%" + ), + ) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") def test_graph_draggable_and_symbol_size(fake_writer): nodes = [ diff --git a/test/test_graphic.py b/test/test_graphic.py new file mode 100644 index 000000000..06d51c486 --- /dev/null +++ b/test/test_graphic.py @@ -0,0 +1,68 @@ +import os +import json +from unittest.mock import patch + +from nose.tools import assert_equal, assert_in + +from pyecharts.commons.utils import remove_key_with_none_value +from pyecharts import options as opts + + +def test_graphic_group(): + group = opts.GraphicGroup( + graphic_item={"item": 1}, + is_diff_children_by_name=False, + ) + expected = { + "type": "group", + "diffChildrenByName": False, + "item": 1, + } + assert_equal(expected, remove_key_with_none_value(group.opts)) + + +def test_graphic_image(): + image = opts.GraphicImage( + graphic_item={"item": 1}, + graphic_imagestyle_opts={"opts": 1} + ) + expected = { + "type": "image", + "item": 1, + "style": { + "opts": 1, + } + } + assert_equal(expected, remove_key_with_none_value(image.opts)) + + +def test_graphic_text(): + text = opts.GraphicText( + graphic_item={"item": 1}, + graphic_textstyle_opts={"opts": 1} + ) + expected = { + "type": "text", + "item": 1, + "style": { + "opts": 1, + } + } + assert_equal(expected, remove_key_with_none_value(text.opts)) + + +def test_graphic_rect(): + rect = opts.GraphicRect( + graphic_item={"item": 1}, + graphic_shape_opts={"shape": 1}, + graphic_basicstyle_opts={"opts": 1}, + ) + expected = { + "type": "rect", + "item": 1, + "shape": 1, + "style": { + "opts": 1, + } + } + assert_equal(expected, remove_key_with_none_value(rect.opts)) diff --git a/test/test_grid.py b/test/test_grid.py index 00c46a25b..1d1c41ca1 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -3,7 +3,10 @@ from nose.tools import assert_equal, assert_in from pyecharts import options as opts -from pyecharts.charts import Bar, Grid, Line +from pyecharts.charts import Bar, Grid, Line, Geo +from pyecharts.globals import ThemeType +from pyecharts.faker import Faker +from pyecharts.commons.utils import JsCode def _chart_for_grid() -> Bar: @@ -65,15 +68,16 @@ def test_grid_do_not_control_axis_index(): @patch("pyecharts.render.engine.write_utf8_html_file") def test_grid_options(fake_writer): bar = _chart_for_grid() - gc = Grid().add( + gc = Grid(init_opts=opts.InitOpts(theme=ThemeType.ESSOS)).add( bar, opts.GridOpts(pos_left="5%", pos_right="20%", is_contain_label=True) ) gc.render() _, content = fake_writer.call_args[0] assert_in("containLabel", content) + assert_in("color", content) -def test_chart_for_grid(): +def _chart_for_grid_v2(): x_data = ["{}月".format(i) for i in range(1, 13)] bar = ( Bar() @@ -111,13 +115,12 @@ def test_chart_for_grid(): bar.overlap(line) assert_equal(bar.colors[:3], ["red", "green", "blue"]) - bar.render("overlap_test_after_colors_update.html") return bar @patch("pyecharts.render.engine.write_utf8_html_file") def test_grid_example_1(fake_writer): - bar = test_chart_for_grid() + bar = _chart_for_grid_v2() gc = Grid().add( bar, opts.GridOpts(pos_left="5%", pos_right="20%"), is_control_axis_index=True ) @@ -127,3 +130,209 @@ def test_grid_example_1(fake_writer): gc.render() _, content = fake_writer.call_args[0] assert_in("yAxisIndex", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_grid_geo_bar(fake_writer): + bar = ( + Bar() + .add_xaxis(Faker.choose()) + .add_yaxis("商家A", Faker.values()) + .add_yaxis("商家B", Faker.values()) + .set_global_opts(legend_opts=opts.LegendOpts(pos_left="20%")) + ) + geo = ( + Geo() + .add_schema(maptype="china") + .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(), + title_opts=opts.TitleOpts(title="Grid-Geo-Bar"), + ) + ) + + grid = ( + Grid() + .add(bar, grid_opts=opts.GridOpts(pos_top="50%", pos_right="75%")) + .add(geo, grid_opts=opts.GridOpts(pos_left="60%")) + ) + grid.render() + _, content = fake_writer.call_args[0] + assert_in("geo", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_grid_mutil_yaxis(fake_writer): + bar = ( + Bar() + .add_xaxis(["{}月".format(i) for i in range(1, 13)]) + .add_yaxis( + "蒸发量", + [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], + yaxis_index=0, + color="#d14a61", + ) + .add_yaxis( + "降水量", + [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], + yaxis_index=1, + color="#5793f3", + ) + .extend_axis( + yaxis=opts.AxisOpts( + name="蒸发量", + type_="value", + min_=0, + max_=250, + position="right", + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#d14a61") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} ml"), + ) + ) + .extend_axis( + yaxis=opts.AxisOpts( + type_="value", + name="温度", + min_=0, + max_=25, + position="left", + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#675bba") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} °C"), + splitline_opts=opts.SplitLineOpts( + is_show=True, linestyle_opts=opts.LineStyleOpts(opacity=1) + ), + ) + ) + .set_global_opts( + yaxis_opts=opts.AxisOpts( + name="降水量", + min_=0, + max_=250, + position="right", + offset=80, + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#5793f3") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} ml"), + ), + title_opts=opts.TitleOpts(title="Grid-Overlap-多 X/Y 轴示例"), + tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"), + legend_opts=opts.LegendOpts(pos_left="25%"), + ) + ) + + line = ( + Line() + .add_xaxis(["{}月".format(i) for i in range(1, 13)]) + .add_yaxis( + "平均温度", + [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], + yaxis_index=2, + color="#675bba", + label_opts=opts.LabelOpts(is_show=False), + ) + ) + + bar1 = ( + Bar() + .add_xaxis(["{}月".format(i) for i in range(1, 13)]) + .add_yaxis( + "蒸发量 1", + [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], + color="#d14a61", + xaxis_index=1, + yaxis_index=3, + ) + .add_yaxis( + "降水量 2", + [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], + color="#5793f3", + xaxis_index=1, + yaxis_index=3, + ) + .extend_axis( + yaxis=opts.AxisOpts( + name="蒸发量", + type_="value", + min_=0, + max_=250, + position="right", + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#d14a61") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} ml"), + ) + ) + .extend_axis( + yaxis=opts.AxisOpts( + type_="value", + name="温度", + min_=0, + max_=25, + position="left", + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#675bba") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} °C"), + splitline_opts=opts.SplitLineOpts( + is_show=True, linestyle_opts=opts.LineStyleOpts(opacity=1) + ), + ) + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts(grid_index=1), + yaxis_opts=opts.AxisOpts( + name="降水量", + min_=0, + max_=250, + position="right", + offset=80, + grid_index=1, + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#5793f3") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} ml"), + ), + tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"), + legend_opts=opts.LegendOpts(pos_left="65%"), + ) + ) + + line1 = ( + Line() + .add_xaxis(["{}月".format(i) for i in range(1, 13)]) + .add_yaxis( + "平均温度 1", + [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], + color="#675bba", + label_opts=opts.LabelOpts(is_show=False), + xaxis_index=1, + yaxis_index=5, + ) + ) + + overlap_1 = bar.overlap(line) + overlap_2 = bar1.overlap(line1) + + grid = ( + Grid(init_opts=opts.InitOpts(width="1200px", height="800px")) + .add( + overlap_1, + grid_opts=opts.GridOpts(pos_right="58%"), + is_control_axis_index=True, + ) + .add( + overlap_2, + grid_opts=opts.GridOpts(pos_left="58%"), + is_control_axis_index=True, + ) + ) + grid.render() + _, content = fake_writer.call_args[0] + assert_in("xAxis", content) + assert_in("yAxis", content) diff --git a/test/test_image.py b/test/test_image.py index 25cbf5ae9..e3c87b559 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -2,6 +2,7 @@ from nose.tools import assert_in +from pyecharts import options as opts from pyecharts.components import Image from pyecharts.globals import CurrentConfig, NotebookType @@ -15,15 +16,31 @@ def _gen_image() -> Image: @patch("pyecharts.render.engine.write_utf8_html_file") -def test_table_base(fake_writer): +def test_image_base(fake_writer): image = _gen_image() image.render() _, content = fake_writer.call_args[0] assert_in(TEST_SRC, content) -def test_table_render_notebook(): +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_image_base_v1(fake_writer): + image = Image() + image.add(src=TEST_SRC, style_opts={"align": "center"}) + image.set_global_opts(title_opts=opts.ComponentTitleOpts(title="Image Test")) + image.render() + _, content = fake_writer.call_args[0] + assert_in(TEST_SRC, content) + + +def test_image_render_notebook(): CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK image = _gen_image() content = image.render_notebook().__html__() assert_in(TEST_SRC, content) + + +def test_images_render_embed(): + image = _gen_image() + s = image.render_embed() + assert s is not None diff --git a/test/test_lines3d.py b/test/test_lines3d.py new file mode 100644 index 000000000..39bff29c8 --- /dev/null +++ b/test/test_lines3d.py @@ -0,0 +1,50 @@ +import requests +from unittest.mock import patch + +from nose.tools import assert_in + +from pyecharts import options as opts +from pyecharts.charts import Lines3D + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_lines3d_base(fake_writer): + test_main_url: str = "https://cdn.jsdelivr.net/gh/apache/echarts-website@asf-site/examples" + data_json_url = test_main_url + "/data-gl/asset/data/flights.json" + base_texture = test_main_url + "/data-gl/asset/world.topo.bathy.200401.jpg" + height_texture = test_main_url + "/data-gl/asset/bathymetry_bw_composite_4k.jpg" + + resp = requests.get(data_json_url).json() + json_routes = resp.get("routes") + json_airports = resp.get("airports") + + routes_data = [] + for d in json_routes: + + def _inner_func(idx): + return [json_airports[idx][3], json_airports[idx][4]] + + routes_data.append([_inner_func(d[1]), _inner_func(d[2])]) + + c = ( + Lines3D(init_opts=opts.InitOpts(bg_color="#000")) + .add_globe( + base_texture=base_texture, + height_texture=height_texture, + shading="lambert", + light_opts=opts.Map3DLightOpts(ambient_intensity=0.4, main_intensity=0.4), + ) + .add( + series_name="1", + coordinate_system="globe", + blend_mode="lighter", + linestyle_opts=opts.LineStyleOpts( + width=1, color="rgb(50, 50, 150)", opacity=0.1 + ), + data_pair=routes_data, + ) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("baseTexture", content) + assert_in("heightTexture", content) diff --git a/test/test_liquid.py b/test/test_liquid.py index 110cb485e..aa5353a38 100644 --- a/test/test_liquid.py +++ b/test/test_liquid.py @@ -9,11 +9,13 @@ @patch("pyecharts.render.engine.write_utf8_html_file") def test_liquid_base(fake_writer): - c = Liquid().add("lq", [0.6, 0.7]) + c = Liquid().add("lq", [0.6, 0.7], is_animation=False) c.render() _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + assert_in("animationDuration", content) + assert_in("animationDurationUpdate", content) @patch("pyecharts.render.engine.write_utf8_html_file") diff --git a/test/test_map3d.py b/test/test_map3d.py index 5fe1b6eaf..068c2ac51 100644 --- a/test/test_map3d.py +++ b/test/test_map3d.py @@ -6,6 +6,7 @@ from pyecharts.charts import Map3D from pyecharts.faker import Faker from pyecharts.globals import ChartType +from pyecharts.commons.utils import JsCode @patch("pyecharts.render.engine.write_utf8_html_file") @@ -56,3 +57,222 @@ def test_map3d_schema(fake_writer): assert_in("realisticMaterial", content) assert_in("lambertMaterial", content) assert_in("colorMaterial", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_map3d_with_bar3d(fake_writer): + example_data = [ + ("黑龙江", [127.9688, 45.368, 100]), + ("内蒙古", [110.3467, 41.4899, 300]), + ("吉林", [125.8154, 44.2584, 300]), + ("辽宁", [123.1238, 42.1216, 300]), + ("河北", [114.4995, 38.1006, 300]), + ("天津", [117.4219, 39.4189, 300]), + ("山西", [112.3352, 37.9413, 300]), + ("陕西", [109.1162, 34.2004, 300]), + ("甘肃", [103.5901, 36.3043, 300]), + ("宁夏", [106.3586, 38.1775, 300]), + ("青海", [101.4038, 36.8207, 300]), + ("新疆", [87.9236, 43.5883, 300]), + ("西藏", [91.11, 29.97, 300]), + ("四川", [103.9526, 30.7617, 300]), + ("重庆", [108.384366, 30.439702, 300]), + ("山东", [117.1582, 36.8701, 300]), + ("河南", [113.4668, 34.6234, 300]), + ("江苏", [118.8062, 31.9208, 300]), + ("安徽", [117.29, 32.0581, 300]), + ("湖北", [114.3896, 30.6628, 300]), + ("浙江", [119.5313, 29.8773, 300]), + ("福建", [119.4543, 25.9222, 300]), + ("江西", [116.0046, 28.6633, 300]), + ("湖南", [113.0823, 28.2568, 300]), + ("贵州", [106.6992, 26.7682, 300]), + ("广西", [108.479, 23.1152, 300]), + ("海南", [110.3893, 19.8516, 300]), + ("上海", [121.4648, 31.2891, 1300]), + ] + + c = ( + Map3D() + .add_schema( + itemstyle_opts=opts.ItemStyleOpts( + color="rgb(5,101,123)", + opacity=1, + border_width=0.8, + border_color="rgb(62,215,213)", + ), + map3d_label=opts.Map3DLabelOpts( + is_show=False, + formatter=JsCode( + "function(data){return data.name + " " + data.value[2];}" + ), + ), + emphasis_label_opts=opts.LabelOpts( + is_show=False, + color="#fff", + font_size=10, + background_color="rgba(0,23,11,0)", + ), + light_opts=opts.Map3DLightOpts( + main_color="#fff", + main_intensity=1.2, + main_shadow_quality="high", + is_main_shadow=False, + main_beta=10, + ambient_intensity=0.3, + ), + ) + .add( + series_name="bar3D", + data_pair=example_data, + type_=ChartType.BAR3D, + bar_size=1, + shading="lambert", + label_opts=opts.LabelOpts( + is_show=False, + formatter=JsCode( + "function(data){return data.name + ' ' + data.value[2];}" + ), + ), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Bar3D")) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("itemStyle", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_map3d_with_lines3d(fake_writer): + example_data = [ + [[119.107078, 36.70925, 1000], [116.587245, 35.415393, 1000]], + [[117.000923, 36.675807], [120.355173, 36.082982]], + [[118.047648, 36.814939], [118.66471, 37.434564]], + [[121.391382, 37.539297], [119.107078, 36.70925]], + [[116.587245, 35.415393], [122.116394, 37.509691]], + [[119.461208, 35.428588], [118.326443, 35.065282]], + [[116.307428, 37.453968], [115.469381, 35.246531]], + ] + c = ( + Map3D() + .add_schema( + maptype="山东", + itemstyle_opts=opts.ItemStyleOpts( + color="rgb(5,101,123)", + opacity=1, + border_width=0.8, + border_color="rgb(62,215,213)", + ), + light_opts=opts.Map3DLightOpts( + main_color="#fff", + main_intensity=1.2, + is_main_shadow=False, + main_alpha=55, + main_beta=10, + ambient_intensity=0.3, + ), + view_control_opts=opts.Map3DViewControlOpts(center=[-10, 0, 10]), + post_effect_opts=opts.Map3DPostEffectOpts(is_enable=False), + ) + .add( + series_name="", + data_pair=example_data, + type_=ChartType.LINES3D, + effect=opts.Lines3DEffectOpts( + is_show=True, + period=4, + trail_width=3, + trail_length=0.5, + trail_color="#f00", + trail_opacity=1, + ), + linestyle_opts=opts.LineStyleOpts(is_show=False, color="#fff", opacity=0), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Lines3D")) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("itemStyle", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_map3d_with_scatter3d(fake_writer): + example_data = [ + ("黑龙江", [127.9688, 45.368, 100]), + ("内蒙古", [110.3467, 41.4899, 100]), + ("吉林", [125.8154, 44.2584, 100]), + ("辽宁", [123.1238, 42.1216, 100]), + ("河北", [114.4995, 38.1006, 100]), + ("天津", [117.4219, 39.4189, 100]), + ("山西", [112.3352, 37.9413, 100]), + ("陕西", [109.1162, 34.2004, 100]), + ("甘肃", [103.5901, 36.3043, 100]), + ("宁夏", [106.3586, 38.1775, 100]), + ("青海", [101.4038, 36.8207, 100]), + ("新疆", [87.9236, 43.5883, 100]), + ("西藏", [91.11, 29.97, 100]), + ("四川", [103.9526, 30.7617, 100]), + ("重庆", [108.384366, 30.439702, 100]), + ("山东", [117.1582, 36.8701, 100]), + ("河南", [113.4668, 34.6234, 100]), + ("江苏", [118.8062, 31.9208, 100]), + ("安徽", [117.29, 32.0581, 100]), + ("湖北", [114.3896, 30.6628, 100]), + ("浙江", [119.5313, 29.8773, 100]), + ("福建", [119.4543, 25.9222, 100]), + ("江西", [116.0046, 28.6633, 100]), + ("湖南", [113.0823, 28.2568, 100]), + ("贵州", [106.6992, 26.7682, 100]), + ("广西", [108.479, 23.1152, 100]), + ("海南", [110.3893, 19.8516, 100]), + ("上海", [121.4648, 31.2891, 100]), + ] + + c = ( + Map3D() + .add_schema( + itemstyle_opts=opts.ItemStyleOpts( + color="rgb(5,101,123)", + opacity=1, + border_width=0.8, + border_color="rgb(62,215,213)", + ), + map3d_label=opts.Map3DLabelOpts( + is_show=False, + formatter=JsCode( + "function(data){return data.name + " " + data.value[2];}" + ), + ), + emphasis_label_opts=opts.LabelOpts( + is_show=False, + color="#fff", + font_size=10, + background_color="rgba(0,23,11,0)", + ), + light_opts=opts.Map3DLightOpts( + main_color="#fff", + main_intensity=1.2, + main_shadow_quality="high", + is_main_shadow=False, + main_beta=10, + ambient_intensity=0.3, + ), + ) + .add( + series_name="Scatter3D", + data_pair=example_data, + type_=ChartType.SCATTER3D, + bar_size=1, + shading="lambert", + label_opts=opts.LabelOpts( + is_show=False, + formatter=JsCode( + "function(data){return data.name + ' ' + data.value[2];}" + ), + ), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Scatter3D")) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("itemStyle", content) diff --git a/test/test_map_globe.py b/test/test_map_globe.py index 5035e920b..a8d2febab 100644 --- a/test/test_map_globe.py +++ b/test/test_map_globe.py @@ -20,6 +20,21 @@ def test_map_base(fake_writer): assert_in("baseTexture", content) +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_map_base_v2(fake_writer): + c = ( + MapGlobe() + .add("商家A", [list(z) for z in zip(Faker.provinces, Faker.values())], "china") + .add_schema(maptype="china") + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("document.createElement('canvas')", content) + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + assert_in("baseTexture", content) + + def test_map_globe_in_jupyter(): CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK diff --git a/test/test_mixins.py b/test/test_mixins.py new file mode 100644 index 000000000..bc5dd228c --- /dev/null +++ b/test/test_mixins.py @@ -0,0 +1,27 @@ +from unittest.mock import patch + +from nose.tools import assert_equal + +from pyecharts import options as opts +from pyecharts.charts import Bar, Line +from pyecharts.charts.mixins import CompositeMixin + + +class CustomCompositeChart(CompositeMixin): + def __init__(self): + self._charts: list = [] + + def add(self, chart, tab_name): + chart.tab_name = tab_name + self._charts.append(chart) + return self + + +def test_composite_mixin_len(): + b_1 = Bar() + b_2 = Line() + c = CustomCompositeChart() + c.add(b_1, "bar") + c.add(b_2, "line") + assert len(c) == 2 + diff --git a/test/test_page.py b/test/test_page.py index 6e0ed0ddf..445fd0a9f 100644 --- a/test/test_page.py +++ b/test/test_page.py @@ -11,16 +11,26 @@ from pyecharts.charts import Bar, Line, Page from pyecharts.commons.utils import OrderedSet +from pyecharts.globals import ThemeType from pyecharts.components import Table +from pyecharts import options as opts from pyecharts.faker import Faker def _create_bar() -> Bar: - return Bar().add_xaxis(Faker.week).add_yaxis("商家A", [1, 2, 3, 4, 5, 6, 7]) + return ( + Bar(init_opts=opts.InitOpts(theme=ThemeType.ROMA)) + .add_xaxis(Faker.week) + .add_yaxis("商家A", [1, 2, 3, 4, 5, 6, 7]) + ) def _create_line() -> Line: - return Line().add_xaxis(Faker.week).add_yaxis("商家A", [7, 6, 5, 4, 3, 2, 1]) + return ( + Line(init_opts=opts.InitOpts(theme=ThemeType.LIGHT)) + .add_xaxis(Faker.week) + .add_yaxis("商家A", [7, 6, 5, 4, 3, 2, 1]) + ) def _create_table() -> Table: @@ -163,3 +173,12 @@ def test_page_resize(): ) assert_not_in(".resizable()", content) assert_not_in(".draggable()", content) + + +def test_page_resize_cfg(): + page = Page() + content = page.save_resize_html( + cfg_file="fixtures/resize_cfg.json" + ) + assert_not_in(".resizable()", content) + assert_not_in(".draggable()", content) diff --git a/test/test_parallel.py b/test/test_parallel.py index 97194d91b..1bc9b1c36 100644 --- a/test/test_parallel.py +++ b/test/test_parallel.py @@ -2,6 +2,7 @@ from nose.tools import assert_equal +from pyecharts import options as opts from pyecharts.charts import Parallel @@ -33,3 +34,50 @@ def test_parallel_base(fake_writer): _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_parallel_base_v1(fake_writer): + data = [ + [1, 91, 45, 125, 0.82, 34, 23, "良"], + [2, 65, 27, 78, 0.86, 45, 29, "良"], + [3, 83, 60, 84, 1.09, 73, 27, "良"], + [4, 109, 81, 121, 1.28, 68, 51, "轻度污染"], + [5, 106, 77, 114, 1.07, 55, 51, "轻度污染"], + [6, 109, 81, 121, 1.28, 68, 51, "轻度污染"], + [7, 106, 77, 114, 1.07, 55, 51, "轻度污染"], + [8, 89, 65, 78, 0.86, 51, 26, "良"], + [9, 53, 33, 47, 0.64, 50, 17, "良"], + [10, 80, 55, 80, 1.01, 75, 24, "良"], + [11, 117, 81, 124, 1.03, 45, 24, "轻度污染"], + [12, 99, 71, 142, 1.1, 62, 42, "良"], + [13, 95, 69, 130, 1.28, 74, 50, "良"], + [14, 116, 87, 131, 1.47, 84, 40, "轻度污染"], + ] + c = ( + Parallel() + .add_schema( + schema=[ + opts.ParallelAxisOpts(dim=0, name="data"), + opts.ParallelAxisOpts(dim=1, name="AQI"), + opts.ParallelAxisOpts(dim=2, name="PM2.5"), + opts.ParallelAxisOpts(dim=3, name="PM10"), + opts.ParallelAxisOpts(dim=4, name="CO"), + opts.ParallelAxisOpts(dim=5, name="NO2"), + opts.ParallelAxisOpts(dim=6, name="CO2"), + opts.ParallelAxisOpts( + dim=7, + name="等级", + type_="category", + data=["优", "良", "轻度污染", "中度污染", "重度污染", "严重污染"], + ), + ], + parallel_opts=opts.ParallelOpts() + ) + .add("parallel", data) + .set_global_opts(title_opts=opts.TitleOpts(title="Parallel-Category")) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") diff --git a/test/test_pie.py b/test/test_pie.py index 0120ed808..2422cbce3 100644 --- a/test/test_pie.py +++ b/test/test_pie.py @@ -1,6 +1,6 @@ from unittest.mock import patch -from nose.tools import assert_equal +from nose.tools import assert_equal, assert_in from pyecharts import options as opts from pyecharts.charts import Pie @@ -37,3 +37,56 @@ def test_pie_item_base(fake_writer): _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_pie_dataset(fake_writer): + c = ( + Pie() + .add_dataset( + source=[ + ["product", "2012", "2013", "2014", "2015", "2016", "2017"], + ["Matcha Latte", 41.1, 30.4, 65.1, 53.3, 83.8, 98.7], + ["Milk Tea", 86.5, 92.1, 85.7, 83.1, 73.4, 55.1], + ["Cheese Cocoa", 24.1, 67.2, 79.5, 86.4, 65.2, 82.5], + ["Walnut Brownie", 55.2, 67.1, 69.2, 72.4, 53.9, 39.1], + ] + ) + .add( + series_name="Matcha Latte", + data_pair=[], + radius=60, + center=["25%", "30%"], + encode={"itemName": "product", "value": "2012"}, + ) + .add( + series_name="Milk Tea", + data_pair=[], + radius=60, + center=["75%", "30%"], + encode={"itemName": "product", "value": "2013"}, + ) + .add( + series_name="Cheese Cocoa", + data_pair=[], + radius=60, + center=["25%", "75%"], + encode={"itemName": "product", "value": "2014"}, + ) + .add( + series_name="Walnut Brownie", + data_pair=[], + radius=60, + center=["75%", "75%"], + encode={"itemName": "product", "value": "2015"}, + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="Dataset simple pie example"), + legend_opts=opts.LegendOpts(pos_left="30%", pos_top="2%"), + ) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + assert_in("dataset", content) diff --git a/test/test_polar.py b/test/test_polar.py index 525e8fee6..050472d7a 100644 --- a/test/test_polar.py +++ b/test/test_polar.py @@ -15,3 +15,43 @@ def test_polar_scatter(fake_writer): _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_polar_effect_scatter(fake_writer): + data = [(i, random.randint(1, 100)) for i in range(101)] + c = ( + Polar() + .add( + "", + data, + type_="effectScatter", + effect_opts=opts.EffectOpts(scale=10, period=5), + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Polar-EffectScatter")) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_polar_base_1(fake_writer): + data = [(i, random.randint(1, 100)) for i in range(101)] + c = ( + Polar() + .add( + "", + data, + type_="effectScatter", + effect_opts=opts.EffectOpts(scale=10, period=5), + label_opts=opts.LabelOpts(is_show=True), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Polar-Base-1")) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") diff --git a/test/test_sankey.py b/test/test_sankey.py index 90a1c92fb..a7b86dd27 100644 --- a/test/test_sankey.py +++ b/test/test_sankey.py @@ -18,6 +18,7 @@ def test_sankey_base(fake_writer): "sankey", nodes, links, + layout_iterations=16, linestyle_opt=opts.LineStyleOpts(opacity=0.2, curve=0.5, color="source"), label_opts=opts.LabelOpts(position="right"), ) @@ -25,6 +26,7 @@ def test_sankey_base(fake_writer): _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + assert_in("layoutIteration", content) @patch("pyecharts.render.engine.write_utf8_html_file") @@ -66,3 +68,4 @@ def test_sankey_new_opts(fake_writer): assert_in("bottom", content) assert_in("orient", content) assert_in("levels", content) + assert_in("layoutIteration", content) diff --git a/test/test_scatter.py b/test/test_scatter.py index 166d1fc45..8c370cb29 100644 --- a/test/test_scatter.py +++ b/test/test_scatter.py @@ -1,9 +1,12 @@ +import json from unittest.mock import patch -from nose.tools import assert_equal +from nose.tools import assert_equal, assert_in from pyecharts import options as opts -from pyecharts.charts import Scatter +from pyecharts.charts import Scatter, Grid +from pyecharts.commons.utils import JsCode +from pyecharts.faker import Faker @patch("pyecharts.render.engine.write_utf8_html_file") @@ -38,3 +41,177 @@ def test_scatter_item_base(fake_writer): _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_scatter_dataset(fake_writer): + with open("fixtures/life-expectancy-table.json", "r", encoding="utf-8") as f: + j = json.load(f) + + l1_1 = ( + Scatter() + .add_dataset( + dimensions=[ + "Income", + "Life Expectancy", + "Population", + "Country", + {"name": "Year", "type": "ordinal"}, + ], + source=j, + ) + .add_yaxis( + series_name="", + y_axis=[], + symbol_size=2.5, + xaxis_index=0, + yaxis_index=0, + encode={"x": "Income", "y": "Life Expectancy", "tooltip": [0, 1, 2, 3, 4]}, + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts( + type_="value", + grid_index=0, + name="Income", + axislabel_opts=opts.LabelOpts(rotate=50, interval=0), + ), + yaxis_opts=opts.AxisOpts( + type_="value", grid_index=0, name="Life Expectancy" + ), + title_opts=opts.TitleOpts(title="Encode and Matrix"), + ) + ) + + l1_2 = ( + Scatter() + .add_dataset() + .add_yaxis( + series_name="", + y_axis=[], + symbol_size=2.5, + xaxis_index=1, + yaxis_index=1, + encode={"x": "Country", "y": "Income", "tooltip": [0, 1, 2, 3, 4]}, + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts( + type_="category", + grid_index=1, + name="Country", + boundary_gap=False, + axislabel_opts=opts.LabelOpts(rotate=50, interval=0), + ), + yaxis_opts=opts.AxisOpts(type_="value", grid_index=1, name="Income"), + ) + ) + + l2_1 = ( + Scatter() + .add_dataset() + .add_yaxis( + series_name="", + y_axis=[], + symbol_size=2.5, + xaxis_index=2, + yaxis_index=2, + encode={"x": "Income", "y": "Population", "tooltip": [0, 1, 2, 3, 4]}, + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts( + type_="value", + grid_index=2, + name="Income", + axislabel_opts=opts.LabelOpts(rotate=50, interval=0), + ), + yaxis_opts=opts.AxisOpts(type_="value", grid_index=2, name="Population"), + ) + ) + + l2_2 = ( + Scatter() + .add_dataset() + .add_yaxis( + series_name="", + y_axis=[], + symbol_size=2.5, + xaxis_index=3, + yaxis_index=3, + encode={ + "x": "Life Expectancy", + "y": "Population", + "tooltip": [0, 1, 2, 3, 4], + }, + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts( + type_="value", + grid_index=3, + name="Life Expectancy", + axislabel_opts=opts.LabelOpts(rotate=50, interval=0), + ), + yaxis_opts=opts.AxisOpts(type_="value", grid_index=3, name="Population"), + ) + ) + + grid = ( + Grid(init_opts=opts.InitOpts(width="1280px", height="960px")) + .add( + chart=l1_1, + grid_opts=opts.GridOpts(pos_right="57%", pos_bottom="57%"), + grid_index=0, + ) + .add( + chart=l1_2, + grid_opts=opts.GridOpts(pos_left="57%", pos_bottom="57%"), + grid_index=1, + ) + .add( + chart=l2_1, + grid_opts=opts.GridOpts(pos_right="57%", pos_top="57%"), + grid_index=2, + ) + .add( + chart=l2_2, + grid_opts=opts.GridOpts(pos_left="57%", pos_top="57%"), + grid_index=3, + ) + ) + grid.render() + _, content = fake_writer.call_args[0] + assert_in("grid", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_scatter_multi_dimension_data(fake_writer): + c = ( + Scatter() + .add_xaxis(Faker.choose()) + .add_yaxis( + "商家A", + [list(z) for z in zip(Faker.values(), Faker.choose())], + label_opts=opts.LabelOpts( + formatter=JsCode( + "function(params){return params.value[1] +' : '+ params.value[2];}" + ) + ), + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="Scatter-多维度数据"), + tooltip_opts=opts.TooltipOpts( + formatter=JsCode( + "function (params) {return params.name + ' : ' + params.value[2];}" + ) + ), + visualmap_opts=opts.VisualMapOpts( + type_="color", max_=150, min_=20, dimension=1 + ), + ) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") diff --git a/test/test_series_options.py b/test/test_series_options.py index dfa0e34c6..516b34735 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -1,6 +1,17 @@ from nose.tools import assert_equal -from pyecharts.options.series_options import LabelOpts +from pyecharts.commons.utils import remove_key_with_none_value +from pyecharts.options.series_options import ( + LabelOpts, + ItemStyleOpts, + MarkPointItem, + MarkLineItem, + MarkAreaItem, + MarkAreaOpts, + MinorTickOpts, + MinorSplitLineOpts, + TreeMapBreadcrumbOpts, +) def test_label_options_defaults(): @@ -55,3 +66,82 @@ def test_label_options_custom(): "rich": None, } assert_equal(expected, option.opts) + + +def test_mark_point_item_remove_none(): + item = MarkPointItem() + expected = {} + assert_equal(expected, remove_key_with_none_value(item.opts)) + + +def test_mark_line_item_remove_none(): + item = MarkLineItem() + expected = {} + assert_equal(expected, remove_key_with_none_value(item.opts)) + + +def test_mark_area_item_remove_none(): + item = MarkAreaItem() + expected = [{ + "itemStyle": None, + "label": None, + "name": None, + "type": None, + "valueDim": None, + "valueIndex": None, + "xAxis": None, + "yAxis": None, + }, { + "type": None, + "valueDim": None, + "valueIndex": None, + "xAxis": None, + "yAxis": None, + }] + assert_equal(expected, remove_key_with_none_value(item.opts)) + + +def test_mark_area_options_remove_none(): + label_opts = LabelOpts() + option = MarkAreaOpts(label_opts=label_opts) + expected = { + "silent": False, + "label": label_opts, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_tree_map_breadcrumb_options_remove_none(): + item_opts = ItemStyleOpts() + option = TreeMapBreadcrumbOpts(item_opts=item_opts) + expected = { + "show": True, + "left": "center", + "right": "auto", + "top": "auto", + "bottom": 0, + "height": 22, + "emptyItemWidth": 25, + "itemStyle": item_opts, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_minor_tick_options_remove_none(): + option = MinorTickOpts() + expected = { + "show": False, + "splitNumber": 5, + "length": 3, + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_minor_split_line_options_remove_none(): + option = MinorSplitLineOpts() + expected = { + "show": False, + "width": 1, + "type": "solid", + } + assert_equal(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_snapshot.py b/test/test_snapshot.py index 989937613..358a010e2 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -1,9 +1,16 @@ +import os from unittest.mock import patch from nose.tools import assert_equal, raises from pyecharts.charts import Bar from pyecharts.render import make_snapshot +from pyecharts.render.snapshot import ( + save_as, + save_as_png, + save_as_text, + decode_base64, +) def _gen_faker_engine(content: str): @@ -17,6 +24,31 @@ def make_snapshot(self, *args, **kwargs): return Engine(content) +def test_decode_base64(): + assert decode_base64(data="abcde12") == b'i\xb7\x1d{]' + + +def test_save_as_png(): + save_as_png(image_data=b'i\xb7\x1d{]', output_name="text_png.png") + os.unlink("text_png.png") + + +def test_save_as_text(): + save_as_text(image_data="test data", output_name="test_txt.txt") + os.unlink("test_txt.txt") + + +def test_save_as(): + with open("fixtures/img1.jpg", "rb") as f: + image_bytes = f.read() + save_as(image_data=image_bytes, output_name="test_pdf.pdf", file_type="pdf") + os.unlink("test_pdf.pdf") + save_as(image_data=image_bytes, output_name="test_gif.gif", file_type="gif") + os.unlink("test_gif.gif") + save_as(image_data=image_bytes, output_name="test_eps.eps", file_type="eps") + os.unlink("test_eps.eps") + + @patch("pyecharts.render.engine.write_utf8_html_file") def _gen_bar_chart(fake_writer) -> str: c = ( diff --git a/test/test_tab.py b/test/test_tab.py index 926654f98..6b779a78c 100644 --- a/test/test_tab.py +++ b/test/test_tab.py @@ -3,14 +3,20 @@ from nose.tools import assert_equal, assert_in, assert_true +from pyecharts import options as opts from pyecharts.charts import Bar, Line, Tab from pyecharts.commons.utils import OrderedSet from pyecharts.components import Table from pyecharts.faker import Faker +from pyecharts.globals import ThemeType def _create_bar() -> Bar: - return Bar().add_xaxis(Faker.week).add_yaxis("商家A", [1, 2, 3, 4, 5, 6, 7]) + return ( + Bar(init_opts=opts.InitOpts(theme=ThemeType.ESSOS)) + .add_xaxis(Faker.week) + .add_yaxis("商家A", [1, 2, 3, 4, 5, 6, 7]) + ) def _create_line() -> Line: diff --git a/test/test_table.py b/test/test_table.py index 8239c8a43..ec46091c2 100644 --- a/test/test_table.py +++ b/test/test_table.py @@ -2,6 +2,7 @@ from nose.tools import assert_in +from pyecharts import options as opts from pyecharts.components import Table from pyecharts.globals import CurrentConfig, NotebookType @@ -23,9 +24,32 @@ def test_table_base(fake_writer): assert_in("fl-table", content) +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_table_base_v1(fake_writer): + table = _gen_table() + table.set_global_opts(title_opts=opts.ComponentTitleOpts(title="Table Test")) + table.render() + _, content = fake_writer.call_args[0] + assert_in("fl-table", content) + + def test_table_render_notebook(): CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK table = _gen_table() html = table.render_notebook().__html__() assert_in("City name", html) assert_in("Brisbane", html) + + +def test_table_render_jupyter_lab(): + CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_LAB + table = _gen_table() + html = table.render_notebook().__html__() + assert_in("City name", html) + assert_in("Brisbane", html) + + +def test_table_render_embed(): + table = _gen_table() + s = table.render_embed() + assert s is not None diff --git a/test/test_timeline.py b/test/test_timeline.py index 67c094b52..f43c82b96 100644 --- a/test/test_timeline.py +++ b/test/test_timeline.py @@ -106,6 +106,47 @@ def test_timeline_graphic(self): type(self.tl.options.get("baseOption").get("timeline").get("graphic")[0]), ) + def test_timeline_graphic_v1(self): + self.tl.add_schema( + graphic_opts=[ + opts.GraphicGroup( + graphic_item=opts.GraphicItem( + rotation=JsCode("Math.PI / 4"), + bounding="raw", + right=110, + bottom=110, + z=100, + ), + children=[ + opts.GraphicRect( + graphic_item=opts.GraphicItem( + left="center", top="center", z=100 + ), + graphic_shape_opts=opts.GraphicShapeOpts( + width=400, height=50 + ), + graphic_basicstyle_opts=opts.GraphicBasicStyleOpts( + fill="rgba(0,0,0,0.3)" + ), + ), + opts.GraphicText( + graphic_item=opts.GraphicItem( + left="center", top="center", z=100 + ), + graphic_textstyle_opts=opts.GraphicTextStyleOpts( + text="pyecharts bar chart", + font="bold 26px Microsoft YaHei", + ), + ), + ], + ) + ] + ) + assert_equal( + type(opts.GraphicGroup()), + type(self.tl.options.get("baseOption").get("timeline").get("graphic")[0]), + ) + def test_page_with_multi_axis(): tl = Timeline() diff --git a/test/test_tree.py b/test/test_tree.py index b58956c2f..85952e5b3 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -35,6 +35,15 @@ def test_tree_base(fake_writer): assert_equal(c.renderer, "canvas") +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_tree_collapse_interval(fake_writer): + c = Tree().add("", TEST_DATA, collapse_interval=1) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") def test_tree_options(fake_writer): c = Tree().add( diff --git a/test/test_utils.py b/test/test_utils.py index f8e86f565..112a30a15 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -8,6 +8,11 @@ def test_utils_produce_require_dict(): assert_equal(cfg["config_items"], ["'echarts':'https://example.comecharts.min'"]) assert_equal(cfg["libraries"], ["'echarts'"]) + cfg_1 = utils.produce_require_dict(utils.OrderedSet("https://api.map.baidu.com"), "https://example.com") + print(cfg_1) + assert_equal(cfg_1["config_items"], ["'baidu_map_api25':'https://api.map.baidu.com'"]) + assert_equal(cfg_1["libraries"], ["'baidu_map_api25'"]) + def test_js_code(): fn = "function() { console.log('test_js_code') }" @@ -19,3 +24,13 @@ def test_ordered_set(): s = utils.OrderedSet() s.add("a", "b", "c") assert_equal(s.items, ["a", "b", "c"]) + + +def test_utils_remove_key_with_none_value(): + mock_data = [1, 2, 3] + list_res = utils.remove_key_with_none_value(mock_data) + assert list_res == mock_data + + mock_data_none = None + none_res = utils.remove_key_with_none_value(mock_data_none) + assert none_res == mock_data_none diff --git a/test/test_wordcloud.py b/test/test_wordcloud.py index e419c09c3..c17ba6363 100644 --- a/test/test_wordcloud.py +++ b/test/test_wordcloud.py @@ -3,6 +3,8 @@ from nose.tools import assert_equal from pyecharts.charts import WordCloud +from pyecharts.commons.utils import JsCode +from pyecharts.exceptions import WordCloudMaskImageException words = [ ("Sam S Club", 10000), @@ -23,3 +25,38 @@ def test_wordcloud_base(fake_writer): _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_wordcloud_shapes(fake_writer): + c = WordCloud().add("", words, word_size_range=[20, 100], shape="cardioid") + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + + +def test_wordcloud_error_url(): + try: + c = WordCloud().add( + "", words, word_size_range=[20, 100], mask_image="error images_url" + ) + c.render() + except WordCloudMaskImageException as err: + assert_equal(type(err), WordCloudMaskImageException) + assert err.__str__() != "" + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_wordcloud_mask_image(fake_writer): + c = WordCloud().add( + "", + words, + word_size_range=[20, 100], + shape="cardioid", + mask_image="fixtures/img.png", + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") From cf961485c0d560865dab53ee9cc7dd7a58fb1036 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 15 Apr 2022 15:18:46 +0800 Subject: [PATCH 026/150] Add missing legend for Custom Charts --- pyecharts/charts/basic_charts/custom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyecharts/charts/basic_charts/custom.py b/pyecharts/charts/basic_charts/custom.py index 45b0df015..977bf3038 100644 --- a/pyecharts/charts/basic_charts/custom.py +++ b/pyecharts/charts/basic_charts/custom.py @@ -37,6 +37,7 @@ def add( itemstyle_opts: types.ItemStyle = None, tooltip_opts: types.Tooltip = None, ): + self._append_legend(series_name, selected_mode) self.options.get("series").append( { From d1b2ecd223b6c6d429e698ec690e15bf8c40ae09 Mon Sep 17 00:00:00 2001 From: dongdong Date: Mon, 25 Apr 2022 14:07:07 +0800 Subject: [PATCH 027/150] Update README.md --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index 39711075f..91364fb63 100644 --- a/README.md +++ b/README.md @@ -231,16 +231,6 @@ pyecharts 主要由以下几位开发者开发维护 更多贡献者信息可以访问 [pyecharts/graphs/contributors](https://github.com/pyecharts/pyecharts/graphs/contributors) -## 💌 捐赠 - -开发和维护 pyecharts 花费了我巨大的心力,如果你觉得项目帮助到您,请认真考虑请作者喝一杯咖啡 😄 - -| 微信二维码 | 支付宝二维码 | -| -------- | ---------- | -| wechat-code | alipay-code | - -如果其他开发者帮助到了您,也可以请他们喝咖啡 [捐赠通道](http://pyecharts.org/#/zh-cn/donate) - ## 💡 贡献 期待能有更多的开发者参与到 pyecharts 的开发中来,我们会保证尽快 Reivew PR 并且及时回复。但提交 PR 请确保 From 73a5b11689d9626b61122a58d48e85536800a135 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Mon, 16 May 2022 17:00:57 +0800 Subject: [PATCH 028/150] Add some configuration of Tab and Page Components; Fix the old bug from Tab --- pyecharts/charts/base.py | 4 +- pyecharts/charts/composite_charts/page.py | 4 + pyecharts/charts/composite_charts/tab.py | 104 ++++++++++++++++++- pyecharts/options/__init__.py | 1 + pyecharts/options/charts_options.py | 18 ++++ pyecharts/options/global_options.py | 2 + pyecharts/render/templates/macro | 9 +- pyecharts/render/templates/simple_chart.html | 2 +- pyecharts/render/templates/simple_page.html | 8 +- pyecharts/render/templates/simple_tab.html | 8 +- 10 files changed, 150 insertions(+), 10 deletions(-) diff --git a/pyecharts/charts/base.py b/pyecharts/charts/base.py index 1ca344a41..ab67bd328 100644 --- a/pyecharts/charts/base.py +++ b/pyecharts/charts/base.py @@ -32,12 +32,14 @@ def __init__(self, init_opts: Union[InitOpts, dict] = InitOpts()): 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.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.options.update(backgroundColor=_opts.get("bg_color")) + self.options.update(backgroundColor=self.bg_color) self.options.update(_opts.get("animationOpts", AnimationOpts()).opts) self.options.update(aria=_opts.get("ariaOpts")) self._is_geo_chart: bool = False diff --git a/pyecharts/charts/composite_charts/page.py b/pyecharts/charts/composite_charts/page.py index e870462d5..ad470af6b 100644 --- a/pyecharts/charts/composite_charts/page.py +++ b/pyecharts/charts/composite_charts/page.py @@ -54,11 +54,15 @@ def __init__( 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() diff --git a/pyecharts/charts/composite_charts/tab.py b/pyecharts/charts/composite_charts/tab.py index d6b5064a0..bec4b6ee5 100644 --- a/pyecharts/charts/composite_charts/tab.py +++ b/pyecharts/charts/composite_charts/tab.py @@ -5,15 +5,67 @@ 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 = ""): + 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 = [] @@ -25,8 +77,58 @@ def add(self, chart, tab_name): 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"): diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index 09fa3975e..f868cab76 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -45,6 +45,7 @@ SankeyLevelsOpts, ScatterItem, SunburstItem, + TabChartGlobalOpts, ThemeRiverItem, TimelineCheckPointerStyle, TimelineControlStyle, diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 48f218ba1..5db58a773 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -1305,3 +1305,21 @@ def __init__( "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, + } diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 02fdc8ab1..95c4e86a4 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -159,6 +159,7 @@ def __init__( 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() @@ -171,6 +172,7 @@ def __init__( "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, diff --git a/pyecharts/render/templates/macro b/pyecharts/render/templates/macro index 691d1ed64..cd9f5f05e 100644 --- a/pyecharts/render/templates/macro +++ b/pyecharts/render/templates/macro @@ -1,6 +1,9 @@ {%- macro render_chart_content(c) -%}

+ {% endif %} {%- endmacro %} -{%- macro render_chart_dependencies(c) -%} - {% for dep in c.dependencies %} - - {% endfor %} +{%- macro render_chart_dependencies(c, i, js) -%} + {% if i %} + {% for dep in c.dependencies %} + + {% endfor %} + {% else %} + {% for dep in c.dependencies %} + + {% endfor %} + {% endif %} {%- endmacro %} {%- macro render_chart_css(c) -%} diff --git a/pyecharts/render/templates/nb_nteract.html b/pyecharts/render/templates/nb_nteract.html index c975bcf8b..c9739b387 100644 --- a/pyecharts/render/templates/nb_nteract.html +++ b/pyecharts/render/templates/nb_nteract.html @@ -3,7 +3,7 @@ - {{ macro.render_chart_dependencies(chart) }} + {{ macro.render_chart_dependencies(chart, _inner, _javascript) }} {% for c in chart %} diff --git a/pyecharts/render/templates/simple_chart.html b/pyecharts/render/templates/simple_chart.html index 75585090c..a84a2f5f6 100644 --- a/pyecharts/render/templates/simple_chart.html +++ b/pyecharts/render/templates/simple_chart.html @@ -4,7 +4,7 @@ {{ chart.page_title }} - {{ macro.render_chart_dependencies(chart) }} + {{ macro.render_chart_dependencies(chart, _inner, _javascript) }} {{ macro.render_chart_content(chart) }} diff --git a/pyecharts/render/templates/simple_globe.html b/pyecharts/render/templates/simple_globe.html index ad37793bc..0920aa8b7 100644 --- a/pyecharts/render/templates/simple_globe.html +++ b/pyecharts/render/templates/simple_globe.html @@ -4,7 +4,7 @@ {{ chart.page_title }} - {{ macro.render_chart_dependencies(chart) }} + {{ macro.render_chart_dependencies(chart, _inner, _javascript) }}
diff --git a/pyecharts/render/templates/simple_page.html b/pyecharts/render/templates/simple_page.html index 84a3e03b1..0f48f30a1 100644 --- a/pyecharts/render/templates/simple_page.html +++ b/pyecharts/render/templates/simple_page.html @@ -4,7 +4,7 @@ {{ chart.page_title }} - {{ macro.render_chart_dependencies(chart) }} + {{ macro.render_chart_dependencies(chart, _inner, _javascript) }} {{ macro.render_chart_css(chart) }} diff --git a/pyecharts/render/templates/simple_tab.html b/pyecharts/render/templates/simple_tab.html index 2eb691120..f5808a7e8 100644 --- a/pyecharts/render/templates/simple_tab.html +++ b/pyecharts/render/templates/simple_tab.html @@ -4,7 +4,7 @@ {{ chart.page_title }} - {{ macro.render_chart_dependencies(chart) }} + {{ macro.render_chart_dependencies(chart, _inner, _javascript) }} {{ macro.render_chart_css(chart) }} diff --git a/test/test_base.py b/test/test_base.py index df0b0adb5..f5dde1046 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -50,6 +50,14 @@ def test_render_js_host_none(fake_writer): assert_equal(bar.js_host, CurrentConfig.ONLINE_HOST) +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_inner_render(fake_writer): + my_render_content = "my_render_content" + bar = Bar() + bar.add_xaxis(["1"]).add_yaxis("", [1]).render(my_render_content=my_render_content, inner=True) + assert "test ok" == "test ok" + + def test_base_iso_format(): mock_time_str = "2022-04-14 14:42:00" mock_time = datetime.strptime(mock_time_str, "%Y-%m-%d %H:%M:%S") From 0078c6ec3ba6516e091f10455e28d53541c5bdbd Mon Sep 17 00:00:00 2001 From: Singu Date: Fri, 31 Mar 2023 13:32:45 +0800 Subject: [PATCH 074/150] add `render_opts` parameter to all chart classes. --- pyecharts/charts/base.py | 15 ++++++++++-- pyecharts/charts/basic_charts/bmap.py | 3 ++- pyecharts/charts/basic_charts/calendar.py | 8 +++++-- pyecharts/charts/basic_charts/geo.py | 11 ++++++--- pyecharts/charts/basic_charts/heatmap.py | 8 +++++-- pyecharts/charts/basic_charts/kline.py | 8 +++++-- pyecharts/charts/basic_charts/liquid.py | 8 +++++-- pyecharts/charts/basic_charts/parallel.py | 8 +++++-- pyecharts/charts/basic_charts/polar.py | 8 +++++-- pyecharts/charts/basic_charts/wordcloud.py | 8 +++++-- pyecharts/charts/chart.py | 24 ++++++++++++++----- pyecharts/charts/composite_charts/grid.py | 8 +++++-- pyecharts/charts/composite_charts/timeline.py | 8 +++++-- pyecharts/charts/three_axis_charts/bar3D.py | 10 +++++--- .../charts/three_axis_charts/graph_gl.py | 10 +++++--- pyecharts/charts/three_axis_charts/line3D.py | 10 +++++--- pyecharts/charts/three_axis_charts/lines3D.py | 10 +++++--- pyecharts/charts/three_axis_charts/map3D.py | 10 +++++--- .../charts/three_axis_charts/map_globe.py | 10 +++++--- .../charts/three_axis_charts/scatter3D.py | 10 +++++--- .../charts/three_axis_charts/surface3D.py | 10 +++++--- pyecharts/options/__init__.py | 1 + pyecharts/options/global_options.py | 7 ++++++ pyecharts/types.py | 1 + test/test_base.py | 11 ++++----- 25 files changed, 164 insertions(+), 61 deletions(-) diff --git a/pyecharts/charts/base.py b/pyecharts/charts/base.py index e8866b203..a84fd4154 100644 --- a/pyecharts/charts/base.py +++ b/pyecharts/charts/base.py @@ -6,7 +6,7 @@ from ..commons import utils from ..globals import CurrentConfig, RenderType, ThemeType -from ..options import InitOpts +from ..options import InitOpts, RenderOpts from ..options.global_options import AnimationOpts from ..options.series_options import BasicOpts from ..render import engine @@ -20,11 +20,19 @@ class Base(ChartMixin): part of the initialization parameters and common methods """ - def __init__(self, init_opts: Union[InitOpts, dict] = InitOpts()): + 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 = ( @@ -40,6 +48,7 @@ def __init__(self, init_opts: Union[InitOpts, dict] = InitOpts()): 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") @@ -54,6 +63,8 @@ def __init__(self, init_opts: Union[InitOpts, dict] = InitOpts()): self._geo_json_name: Optional[str] = None self._geo_json: Optional[dict] = None + self._render_cache: dict = dict() + def get_chart_id(self) -> str: return self.chart_id diff --git a/pyecharts/charts/basic_charts/bmap.py b/pyecharts/charts/basic_charts/bmap.py index 1d8a3ee07..bf1e29f9c 100644 --- a/pyecharts/charts/basic_charts/bmap.py +++ b/pyecharts/charts/basic_charts/bmap.py @@ -20,8 +20,9 @@ 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) + 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" diff --git a/pyecharts/charts/basic_charts/calendar.py b/pyecharts/charts/basic_charts/calendar.py index 7b78d42a7..3b850e044 100644 --- a/pyecharts/charts/basic_charts/calendar.py +++ b/pyecharts/charts/basic_charts/calendar.py @@ -13,8 +13,12 @@ class Calendar(Chart): Two categories of axes must be used in rectangular coordinates. """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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( diff --git a/pyecharts/charts/basic_charts/geo.py b/pyecharts/charts/basic_charts/geo.py index bcd44ec97..25a713430 100644 --- a/pyecharts/charts/basic_charts/geo.py +++ b/pyecharts/charts/basic_charts/geo.py @@ -9,8 +9,12 @@ class GeoChartBase(Chart): - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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 @@ -196,8 +200,9 @@ 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) + 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 diff --git a/pyecharts/charts/basic_charts/heatmap.py b/pyecharts/charts/basic_charts/heatmap.py index 5f88f96fd..6c987ec52 100644 --- a/pyecharts/charts/basic_charts/heatmap.py +++ b/pyecharts/charts/basic_charts/heatmap.py @@ -13,8 +13,12 @@ class HeatMap(RectChart): Two categories of axes must be used in rectangular coordinates. """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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( diff --git a/pyecharts/charts/basic_charts/kline.py b/pyecharts/charts/basic_charts/kline.py index 89a1bf147..161531e37 100644 --- a/pyecharts/charts/basic_charts/kline.py +++ b/pyecharts/charts/basic_charts/kline.py @@ -14,8 +14,12 @@ class Kline(RectChart): the fluctuation of a certain period. """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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), diff --git a/pyecharts/charts/basic_charts/liquid.py b/pyecharts/charts/basic_charts/liquid.py index 2e2a48b1e..ab06440b6 100644 --- a/pyecharts/charts/basic_charts/liquid.py +++ b/pyecharts/charts/basic_charts/liquid.py @@ -11,8 +11,12 @@ class Liquid(Chart): The liquid chart is mainly used to highlight the percentage of data. """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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( diff --git a/pyecharts/charts/basic_charts/parallel.py b/pyecharts/charts/basic_charts/parallel.py index b3babfcf3..952a55e52 100644 --- a/pyecharts/charts/basic_charts/parallel.py +++ b/pyecharts/charts/basic_charts/parallel.py @@ -12,8 +12,12 @@ class Parallel(Chart): high dimensional data. """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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( diff --git a/pyecharts/charts/basic_charts/polar.py b/pyecharts/charts/basic_charts/polar.py index 8fa667b78..9bfd223c5 100644 --- a/pyecharts/charts/basic_charts/polar.py +++ b/pyecharts/charts/basic_charts/polar.py @@ -12,8 +12,12 @@ class Polar(Chart): Polar coordinates can be used for scatter and polyline graphs. """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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( diff --git a/pyecharts/charts/basic_charts/wordcloud.py b/pyecharts/charts/basic_charts/wordcloud.py index 790c060f0..16e3cd38c 100644 --- a/pyecharts/charts/basic_charts/wordcloud.py +++ b/pyecharts/charts/basic_charts/wordcloud.py @@ -31,8 +31,12 @@ class WordCloud(Chart): appear frequently in the text. """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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"] diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index dd92c62b1..7d39f77b2 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -6,12 +6,16 @@ class Chart(Base): - def __init__(self, init_opts: types.Init = opts.InitOpts()): + 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) + 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 " @@ -203,8 +207,12 @@ def add_dataset( class RectChart(Chart): - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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( @@ -254,9 +262,13 @@ class Chart3D(Chart): `Chart3D`类是所有 3D 类图表的基类,继承自 `Chart` 类 """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): + def __init__( + self, + init_opts: types.Init = opts.InitOpts(), + render_opts: types.RenderInit = opts.RenderOpts() + ): init_opts.renderer = RenderType.CANVAS - super().__init__(init_opts) + 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 diff --git a/pyecharts/charts/composite_charts/grid.py b/pyecharts/charts/composite_charts/grid.py index 164147e21..36c7f3307 100644 --- a/pyecharts/charts/composite_charts/grid.py +++ b/pyecharts/charts/composite_charts/grid.py @@ -14,8 +14,12 @@ class Grid(Base): and scatter chart (bubble chart) can be drawn in grid. """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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 diff --git a/pyecharts/charts/composite_charts/timeline.py b/pyecharts/charts/composite_charts/timeline.py index 81990f9b0..a642abca8 100644 --- a/pyecharts/charts/composite_charts/timeline.py +++ b/pyecharts/charts/composite_charts/timeline.py @@ -8,8 +8,12 @@ class Timeline(Base): `Timeline` provides functions like switching and playing between multiple charts. """ - def __init__(self, init_opts: types.Init = opts.InitOpts()): - super().__init__(init_opts=init_opts) + 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 = [] diff --git a/pyecharts/charts/three_axis_charts/bar3D.py b/pyecharts/charts/three_axis_charts/bar3D.py index 9a9e74388..a80c937d8 100644 --- a/pyecharts/charts/three_axis_charts/bar3D.py +++ b/pyecharts/charts/three_axis_charts/bar3D.py @@ -1,7 +1,7 @@ from ... import types from ...charts.chart import ThreeAxisChart from ...globals import ChartType -from ...options import InitOpts +from ...options import InitOpts, RenderOpts class Bar3D(ThreeAxisChart): @@ -9,6 +9,10 @@ class Bar3D(ThreeAxisChart): <<< 3D Bar-Chart >>> """ - def __init__(self, init_opts: types.Init = InitOpts()): - super().__init__(init_opts) + 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 diff --git a/pyecharts/charts/three_axis_charts/graph_gl.py b/pyecharts/charts/three_axis_charts/graph_gl.py index b6458defe..4ef655590 100644 --- a/pyecharts/charts/three_axis_charts/graph_gl.py +++ b/pyecharts/charts/three_axis_charts/graph_gl.py @@ -2,7 +2,7 @@ from ... import types from ...charts.chart import Chart3D from ...globals import ChartType -from ...options import InitOpts +from ...options import InitOpts, RenderOpts class GraphGL(Chart3D): @@ -11,8 +11,12 @@ class GraphGL(Chart3D): drawing of large-scale network/relational data. >>> """ - def __init__(self, init_opts: types.Init = InitOpts()): - super().__init__(init_opts) + 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( diff --git a/pyecharts/charts/three_axis_charts/line3D.py b/pyecharts/charts/three_axis_charts/line3D.py index de3fd2855..8bb16075d 100644 --- a/pyecharts/charts/three_axis_charts/line3D.py +++ b/pyecharts/charts/three_axis_charts/line3D.py @@ -1,7 +1,7 @@ from ... import types from ...charts.chart import ThreeAxisChart from ...globals import ChartType -from ...options import InitOpts +from ...options import InitOpts, RenderOpts class Line3D(ThreeAxisChart): @@ -9,6 +9,10 @@ class Line3D(ThreeAxisChart): <<< 3D Line-Chart >>> """ - def __init__(self, init_opts: types.Init = InitOpts()): - super().__init__(init_opts) + 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 diff --git a/pyecharts/charts/three_axis_charts/lines3D.py b/pyecharts/charts/three_axis_charts/lines3D.py index eb799d4a4..e659ecb63 100644 --- a/pyecharts/charts/three_axis_charts/lines3D.py +++ b/pyecharts/charts/three_axis_charts/lines3D.py @@ -2,7 +2,7 @@ from ... import types from ...charts.chart import Chart3D from ...globals import ChartType -from ...options import InitOpts +from ...options import InitOpts, RenderOpts class Lines3D(Chart3D): @@ -10,8 +10,12 @@ class Lines3D(Chart3D): Lines 3D """ - def __init__(self, init_opts: types.Init = InitOpts()): - super().__init__(init_opts) + 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( diff --git a/pyecharts/charts/three_axis_charts/map3D.py b/pyecharts/charts/three_axis_charts/map3D.py index 4a5b5b9a0..91bf0eea9 100644 --- a/pyecharts/charts/three_axis_charts/map3D.py +++ b/pyecharts/charts/three_axis_charts/map3D.py @@ -2,7 +2,7 @@ from ... import types from ...charts.chart import Chart3D from ...globals import ChartType -from ...options import InitOpts +from ...options import InitOpts, RenderOpts class Map3D(Chart3D): @@ -10,8 +10,12 @@ class Map3D(Chart3D): 3D map """ - def __init__(self, init_opts: types.Init = InitOpts()): - super().__init__(init_opts) + 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( diff --git a/pyecharts/charts/three_axis_charts/map_globe.py b/pyecharts/charts/three_axis_charts/map_globe.py index 6f72ad756..6ee0f89c7 100644 --- a/pyecharts/charts/three_axis_charts/map_globe.py +++ b/pyecharts/charts/three_axis_charts/map_globe.py @@ -7,7 +7,7 @@ from ...charts.chart import Chart3D from ...commons import utils from ...globals import CurrentConfig, NotebookType -from ...options import InitOpts +from ...options import InitOpts, RenderOpts from ...render.display import HTML from ...render.engine import RenderEngine @@ -17,8 +17,12 @@ class MapGlobe(Chart3D, MapMixin): Globe Map """ - def __init__(self, init_opts: types.Init = InitOpts()): - super().__init__(init_opts) + 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) diff --git a/pyecharts/charts/three_axis_charts/scatter3D.py b/pyecharts/charts/three_axis_charts/scatter3D.py index 198461aeb..e6e6560cd 100644 --- a/pyecharts/charts/three_axis_charts/scatter3D.py +++ b/pyecharts/charts/three_axis_charts/scatter3D.py @@ -1,7 +1,7 @@ from ... import types from ...charts.chart import ThreeAxisChart from ...globals import ChartType -from ...options import InitOpts +from ...options import InitOpts, RenderOpts class Scatter3D(ThreeAxisChart): @@ -9,6 +9,10 @@ class Scatter3D(ThreeAxisChart): <<< 3D Scatter-Chart >>> """ - def __init__(self, init_opts: types.Init = InitOpts()): - super().__init__(init_opts) + 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 diff --git a/pyecharts/charts/three_axis_charts/surface3D.py b/pyecharts/charts/three_axis_charts/surface3D.py index db625420f..62963be81 100644 --- a/pyecharts/charts/three_axis_charts/surface3D.py +++ b/pyecharts/charts/three_axis_charts/surface3D.py @@ -2,7 +2,7 @@ from ... import types from ...charts.chart import ThreeAxisChart from ...globals import ChartType -from ...options import InitOpts +from ...options import InitOpts, RenderOpts class Surface3D(ThreeAxisChart): @@ -10,6 +10,10 @@ class Surface3D(ThreeAxisChart): <<< 3D Surface-Chart >>> """ - def __init__(self, init_opts: types.Init = InitOpts()): - super().__init__(init_opts) + 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 diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index aec2005e7..7ccf49541 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -81,6 +81,7 @@ Grid3DOpts, GridOpts, InitOpts, + RenderOpts, LegendOpts, ParallelAxisOpts, ParallelOpts, diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index f017f1a25..e7f19f83e 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -181,6 +181,13 @@ def __init__( } +class RenderOpts(BasicOpts): + def __init__(self, embed_js: bool = True): + self.opts: dict = { + "embed_js": embed_js, + } + + class ToolBoxFeatureSaveAsImageOpts(BasicOpts): def __init__( self, diff --git a/pyecharts/types.py b/pyecharts/types.py index b0e09d55e..cd0d7beb2 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -15,6 +15,7 @@ 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] diff --git a/test/test_base.py b/test/test_base.py index f5dde1046..654a44119 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -6,7 +6,7 @@ from pyecharts.charts import Bar from pyecharts.commons import utils from pyecharts.datasets import EXTRA -from pyecharts.options import InitOpts +from pyecharts.options import InitOpts, RenderOpts from pyecharts.globals import CurrentConfig from pyecharts.charts.base import Base, default from pyecharts.options.global_options import AnimationOpts @@ -50,12 +50,9 @@ def test_render_js_host_none(fake_writer): assert_equal(bar.js_host, CurrentConfig.ONLINE_HOST) -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_inner_render(fake_writer): - my_render_content = "my_render_content" - bar = Bar() - bar.add_xaxis(["1"]).add_yaxis("", [1]).render(my_render_content=my_render_content, inner=True) - assert "test ok" == "test ok" +def test_base_render_options(): + c0 = Base(render_opts=RenderOpts(embed_js=True)) + assert_equal(c0.render_options.get('embed_js'), True) def test_base_iso_format(): From 9c79761091c7bf849957d822b414241b59cd2fe0 Mon Sep 17 00:00:00 2001 From: Singu Date: Fri, 31 Mar 2023 13:33:33 +0800 Subject: [PATCH 075/150] optimize `embedded javascript` rendering. --- pyecharts/charts/base.py | 13 +++++++------ pyecharts/options/global_options.py | 2 +- pyecharts/render/templates/macro | 11 ++++++----- pyecharts/render/templates/simple_chart.html | 2 +- pyecharts/render/templates/simple_globe.html | 2 +- pyecharts/render/templates/simple_page.html | 2 +- pyecharts/render/templates/simple_tab.html | 2 +- test/test_base.py | 14 +++++++++++++- 8 files changed, 31 insertions(+), 17 deletions(-) diff --git a/pyecharts/charts/base.py b/pyecharts/charts/base.py index a84fd4154..2a96c0656 100644 --- a/pyecharts/charts/base.py +++ b/pyecharts/charts/base.py @@ -63,6 +63,7 @@ def __init__( 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: @@ -86,24 +87,18 @@ def render( path: str = "render.html", template_name: str = "simple_chart.html", env: Optional[Environment] = None, - inner: bool = False, **kwargs, ) -> str: self._prepare_render() - if inner: - kwargs = {'_inner': inner, '_javascript': self.load_javascript().load_javascript_contents(), **kwargs} return engine.render(self, path, template_name, env, **kwargs) def render_embed( self, template_name: str = "simple_chart.html", env: Optional[Environment] = None, - inner: bool = False, **kwargs, ) -> str: self._prepare_render() - if inner: - kwargs = {'_inner': inner, '_javascript': self.load_javascript().load_javascript_contents(), **kwargs} return engine.render_embed(self, template_name, env, **kwargs) def render_notebook(self): @@ -121,6 +116,12 @@ 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)): diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index e7f19f83e..9a1653252 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -182,7 +182,7 @@ def __init__( class RenderOpts(BasicOpts): - def __init__(self, embed_js: bool = True): + def __init__(self, embed_js: bool = False): self.opts: dict = { "embed_js": embed_js, } diff --git a/pyecharts/render/templates/macro b/pyecharts/render/templates/macro index e66685c12..e2ae45c8c 100644 --- a/pyecharts/render/templates/macro +++ b/pyecharts/render/templates/macro @@ -80,18 +80,19 @@ {%- endmacro %} -{%- macro render_chart_dependencies(c, i, js) -%} - {% if i %} +{%- macro render_chart_dependencies(c) -%} + {% if 'embed_js' in c.render_options and 'javascript' in c._render_cache and c.render_options.embed_js -%} + {% set _javascript = c._render_cache.javascript %} {% for dep in c.dependencies %} {% endfor %} - {% else %} + {%- else -%} {% for dep in c.dependencies %} {% endfor %} - {% endif %} + {%- endif %} {%- endmacro %} {%- macro render_chart_css(c) -%} diff --git a/pyecharts/render/templates/simple_chart.html b/pyecharts/render/templates/simple_chart.html index a84a2f5f6..75585090c 100644 --- a/pyecharts/render/templates/simple_chart.html +++ b/pyecharts/render/templates/simple_chart.html @@ -4,7 +4,7 @@ {{ chart.page_title }} - {{ macro.render_chart_dependencies(chart, _inner, _javascript) }} + {{ macro.render_chart_dependencies(chart) }} {{ macro.render_chart_content(chart) }} diff --git a/pyecharts/render/templates/simple_globe.html b/pyecharts/render/templates/simple_globe.html index 0920aa8b7..ad37793bc 100644 --- a/pyecharts/render/templates/simple_globe.html +++ b/pyecharts/render/templates/simple_globe.html @@ -4,7 +4,7 @@ {{ chart.page_title }} - {{ macro.render_chart_dependencies(chart, _inner, _javascript) }} + {{ macro.render_chart_dependencies(chart) }}
diff --git a/pyecharts/render/templates/simple_page.html b/pyecharts/render/templates/simple_page.html index 0f48f30a1..84a3e03b1 100644 --- a/pyecharts/render/templates/simple_page.html +++ b/pyecharts/render/templates/simple_page.html @@ -4,7 +4,7 @@ {{ chart.page_title }} - {{ macro.render_chart_dependencies(chart, _inner, _javascript) }} + {{ macro.render_chart_dependencies(chart) }} {{ macro.render_chart_css(chart) }} diff --git a/pyecharts/render/templates/simple_tab.html b/pyecharts/render/templates/simple_tab.html index f5808a7e8..2eb691120 100644 --- a/pyecharts/render/templates/simple_tab.html +++ b/pyecharts/render/templates/simple_tab.html @@ -4,7 +4,7 @@ {{ chart.page_title }} - {{ macro.render_chart_dependencies(chart, _inner, _javascript) }} + {{ macro.render_chart_dependencies(chart) }} {{ macro.render_chart_css(chart) }} diff --git a/test/test_base.py b/test/test_base.py index 654a44119..73e5822c8 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -1,7 +1,7 @@ from datetime import datetime from unittest.mock import patch -from nose.tools import assert_equal, assert_not_in +from nose.tools import assert_equal, assert_in, assert_not_in from pyecharts.charts import Bar from pyecharts.commons import utils @@ -50,6 +50,18 @@ def test_render_js_host_none(fake_writer): assert_equal(bar.js_host, CurrentConfig.ONLINE_HOST) +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_render_embed_js(_): + c = Base(render_opts=RenderOpts(embed_js=True)) + # Embedded JavaScript + content = c.render_embed() + assert_not_in(CurrentConfig.ONLINE_HOST, content, 'Embedding JavaScript fails') + # No embedded JavaScript + c.render_options.update(embed_js=False) + content = c.render_embed() + assert_in(CurrentConfig.ONLINE_HOST, content, 'Embedded JavaScript cannot be closed') + + def test_base_render_options(): c0 = Base(render_opts=RenderOpts(embed_js=True)) assert_equal(c0.render_options.get('embed_js'), True) From 1e1ea55cbc1a7fa003c72c4ba9fd440af58c6900 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 4 Apr 2023 18:30:53 +0800 Subject: [PATCH 076/150] update line.py --- pyecharts/charts/basic_charts/line.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyecharts/charts/basic_charts/line.py b/pyecharts/charts/basic_charts/line.py index 9eb4881c0..709a44b87 100644 --- a/pyecharts/charts/basic_charts/line.py +++ b/pyecharts/charts/basic_charts/line.py @@ -33,6 +33,8 @@ def add_yaxis( 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, tooltip_opts: types.Tooltip = None, @@ -40,6 +42,7 @@ def add_yaxis( label_opts: types.Label = opts.LabelOpts(), linestyle_opts: types.LineStyle = opts.LineStyleOpts(), areastyle_opts: types.AreaStyle = opts.AreaStyleOpts(), + encode: types.Union[types.JSFunc, dict, None] = None, ): self._append_color(color) self._append_legend(series_name) @@ -58,6 +61,9 @@ def add_yaxis( 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, @@ -77,6 +83,9 @@ def add_yaxis( "label": label_opts, "logBase": log_base, "sampling": sampling, + "dimensions": dimensions, + "encode": encode, + "seriesLayoutBy": series_layout_by, "lineStyle": linestyle_opts, "areaStyle": areastyle_opts, "markPoint": markpoint_opts, From 024c41824cc21c530d2615ba8df4e2fb8fe7a831 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 6 Apr 2023 17:04:24 +0800 Subject: [PATCH 077/150] code format --- pyecharts/charts/base.py | 14 +- pyecharts/charts/basic_charts/calendar.py | 6 +- pyecharts/charts/basic_charts/geo.py | 6 +- pyecharts/charts/basic_charts/heatmap.py | 6 +- pyecharts/charts/basic_charts/kline.py | 6 +- pyecharts/charts/basic_charts/liquid.py | 6 +- pyecharts/charts/basic_charts/parallel.py | 6 +- pyecharts/charts/basic_charts/polar.py | 10 +- pyecharts/charts/basic_charts/radar.py | 1 - pyecharts/charts/basic_charts/sankey.py | 2 +- pyecharts/charts/basic_charts/wordcloud.py | 10 +- pyecharts/charts/chart.py | 47 +- pyecharts/charts/composite_charts/grid.py | 6 +- pyecharts/charts/composite_charts/timeline.py | 8 +- pyecharts/charts/three_axis_charts/bar3D.py | 6 +- .../charts/three_axis_charts/graph_gl.py | 6 +- pyecharts/charts/three_axis_charts/line3D.py | 6 +- pyecharts/charts/three_axis_charts/lines3D.py | 6 +- pyecharts/charts/three_axis_charts/map3D.py | 6 +- .../charts/three_axis_charts/map_globe.py | 6 +- .../charts/three_axis_charts/scatter3D.py | 6 +- .../charts/three_axis_charts/surface3D.py | 6 +- pyecharts/options/global_options.py | 10 +- pyecharts/render/display.py | 6 +- pyecharts/render/engine.py | 1 + test.py | 6 +- test/__init__.py | 2 +- test/test_base.py | 10 +- test/test_calendar.py | 37 +- test/test_chart.py | 2 +- test/test_custom.py | 15 +- test/test_datasets.py | 4 +- test/test_exception.py | 2 +- test/test_faker.py | 1 - test/test_geo.py | 761 ++++-------------- test/test_global_options.py | 13 +- test/test_graph_gl.py | 38 +- test/test_graphic.py | 12 +- test/test_grid.py | 18 +- test/test_kline.py | 3 +- test/test_lines3d.py | 3 +- test/test_map.py | 761 ++++-------------- test/test_page.py | 4 +- test/test_parallel.py | 2 +- test/test_series_options.py | 35 +- test/test_snapshot.py | 4 +- test/test_tab.py | 10 +- test/test_utils.py | 9 +- 48 files changed, 520 insertions(+), 1431 deletions(-) diff --git a/pyecharts/charts/base.py b/pyecharts/charts/base.py index 2a96c0656..a1cc49a2c 100644 --- a/pyecharts/charts/base.py +++ b/pyecharts/charts/base.py @@ -21,9 +21,9 @@ class Base(ChartMixin): """ def __init__( - self, - init_opts: Union[InitOpts, dict] = InitOpts(), - render_opts: Union[RenderOpts, dict] = RenderOpts() + self, + init_opts: Union[InitOpts, dict] = InitOpts(), + render_opts: Union[RenderOpts, dict] = RenderOpts(), ): _opts = init_opts if isinstance(init_opts, InitOpts): @@ -117,10 +117,10 @@ def _prepare_render(self): self._use_theme() self._render_cache.clear() - if self.render_options.get('embed_js'): - self._render_cache['javascript'] = ( - self.load_javascript().load_javascript_contents() - ) + if self.render_options.get("embed_js"): + self._render_cache[ + "javascript" + ] = self.load_javascript().load_javascript_contents() def default(o): diff --git a/pyecharts/charts/basic_charts/calendar.py b/pyecharts/charts/basic_charts/calendar.py index 3b850e044..63a069e07 100644 --- a/pyecharts/charts/basic_charts/calendar.py +++ b/pyecharts/charts/basic_charts/calendar.py @@ -14,9 +14,9 @@ class Calendar(Chart): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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) diff --git a/pyecharts/charts/basic_charts/geo.py b/pyecharts/charts/basic_charts/geo.py index 25a713430..f1bda889f 100644 --- a/pyecharts/charts/basic_charts/geo.py +++ b/pyecharts/charts/basic_charts/geo.py @@ -10,9 +10,9 @@ class GeoChartBase(Chart): def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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() diff --git a/pyecharts/charts/basic_charts/heatmap.py b/pyecharts/charts/basic_charts/heatmap.py index 6c987ec52..af0f7906e 100644 --- a/pyecharts/charts/basic_charts/heatmap.py +++ b/pyecharts/charts/basic_charts/heatmap.py @@ -14,9 +14,9 @@ class HeatMap(RectChart): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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")) diff --git a/pyecharts/charts/basic_charts/kline.py b/pyecharts/charts/basic_charts/kline.py index 161531e37..880d7cfbe 100644 --- a/pyecharts/charts/basic_charts/kline.py +++ b/pyecharts/charts/basic_charts/kline.py @@ -15,9 +15,9 @@ class Kline(RectChart): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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( diff --git a/pyecharts/charts/basic_charts/liquid.py b/pyecharts/charts/basic_charts/liquid.py index ab06440b6..8713631f2 100644 --- a/pyecharts/charts/basic_charts/liquid.py +++ b/pyecharts/charts/basic_charts/liquid.py @@ -12,9 +12,9 @@ class Liquid(Chart): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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") diff --git a/pyecharts/charts/basic_charts/parallel.py b/pyecharts/charts/basic_charts/parallel.py index 952a55e52..f72127d67 100644 --- a/pyecharts/charts/basic_charts/parallel.py +++ b/pyecharts/charts/basic_charts/parallel.py @@ -13,9 +13,9 @@ class Parallel(Chart): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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) diff --git a/pyecharts/charts/basic_charts/polar.py b/pyecharts/charts/basic_charts/polar.py index 9bfd223c5..5e4387957 100644 --- a/pyecharts/charts/basic_charts/polar.py +++ b/pyecharts/charts/basic_charts/polar.py @@ -13,9 +13,9 @@ class Polar(Chart): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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() @@ -49,9 +49,7 @@ def add( itemstyle_opts: types.ItemStyle = None, ): self._append_legend(series_name) - self.options.update(polar={ - "center": center if center else ["50%", "50%"] - }) + 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( diff --git a/pyecharts/charts/basic_charts/radar.py b/pyecharts/charts/basic_charts/radar.py index 49929b122..efb837283 100644 --- a/pyecharts/charts/basic_charts/radar.py +++ b/pyecharts/charts/basic_charts/radar.py @@ -26,7 +26,6 @@ def add_schema( angleaxis_opts: types.AngleAxis = None, polar_opts: types.Polar = None, ): - self.options.update( radiusAxis=radiusaxis_opts, angleAxis=angleaxis_opts, polar=polar_opts ) diff --git a/pyecharts/charts/basic_charts/sankey.py b/pyecharts/charts/basic_charts/sankey.py index fa77da89b..f46b6d194 100644 --- a/pyecharts/charts/basic_charts/sankey.py +++ b/pyecharts/charts/basic_charts/sankey.py @@ -29,7 +29,7 @@ def add( layout_iterations: types.Numeric = 32, orient: str = "horizontal", is_draggable: bool = True, - focus_node_mode: str = 'none', + focus_node_mode: str = "none", levels: types.SankeyLevel = None, label_opts: types.Label = opts.LabelOpts(), linestyle_opt: types.LineStyle = opts.LineStyleOpts(), diff --git a/pyecharts/charts/basic_charts/wordcloud.py b/pyecharts/charts/basic_charts/wordcloud.py index 16e3cd38c..60dcd94d4 100644 --- a/pyecharts/charts/basic_charts/wordcloud.py +++ b/pyecharts/charts/basic_charts/wordcloud.py @@ -32,9 +32,9 @@ class WordCloud(Chart): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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") @@ -93,9 +93,7 @@ def add( ): data = [] for n, v in data_pair: - data.append( - {"name": n, "value": v, "textStyle": {"color": gen_color()}} - ) + data.append({"name": n, "value": v, "textStyle": {"color": gen_color()}}) word_size_range = word_size_range or (12, 60) diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index 7d39f77b2..13cb2d33d 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -7,9 +7,9 @@ class Chart(Base): def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + self, + init_opts: types.Init = opts.InitOpts(), + render_opts: types.RenderInit = opts.RenderOpts(), ): if isinstance(init_opts, dict): temp_opts = opts.InitOpts() @@ -18,8 +18,7 @@ def __init__( 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" + "#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: @@ -34,7 +33,7 @@ def __init__( def set_dark_mode( self, dark_mode_colors: Optional[Sequence[str]] = None, - dark_mode_bg_color: str = "#100C2A" + dark_mode_bg_color: str = "#100C2A", ): # [Hard Code Here] The Echarts default Dark Mode Configurations if dark_mode_colors is None: @@ -193,24 +192,26 @@ def add_dataset( ) 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, - }] + 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() + 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]) @@ -252,7 +253,7 @@ def overlap(self, chart: Base): ) 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]: + 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 @@ -263,9 +264,9 @@ class Chart3D(Chart): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + self, + init_opts: types.Init = opts.InitOpts(), + render_opts: types.RenderInit = opts.RenderOpts(), ): init_opts.renderer = RenderType.CANVAS super().__init__(init_opts, render_opts) @@ -376,7 +377,7 @@ def add( "lineStyle": wire_frame_line_style_opts, }, "equation": equation, - "parametricEquation": parametric_equation + "parametricEquation": parametric_equation, } ) else: diff --git a/pyecharts/charts/composite_charts/grid.py b/pyecharts/charts/composite_charts/grid.py index 36c7f3307..a7e7d9c48 100644 --- a/pyecharts/charts/composite_charts/grid.py +++ b/pyecharts/charts/composite_charts/grid.py @@ -15,9 +15,9 @@ class Grid(Base): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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 diff --git a/pyecharts/charts/composite_charts/timeline.py b/pyecharts/charts/composite_charts/timeline.py index a642abca8..f9aa2e6d3 100644 --- a/pyecharts/charts/composite_charts/timeline.py +++ b/pyecharts/charts/composite_charts/timeline.py @@ -9,9 +9,9 @@ class Timeline(Base): """ def __init__( - self, - init_opts: types.Init = opts.InitOpts(), - render_opts: types.RenderInit = opts.RenderOpts() + 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": []} @@ -78,7 +78,7 @@ def add_schema( "lineStyle": progress_linestyle_opts, "itemStyle": progress_itemstyle_opts, "label": progress_label_opts, - } + }, } ) return self diff --git a/pyecharts/charts/three_axis_charts/bar3D.py b/pyecharts/charts/three_axis_charts/bar3D.py index a80c937d8..8958a8a98 100644 --- a/pyecharts/charts/three_axis_charts/bar3D.py +++ b/pyecharts/charts/three_axis_charts/bar3D.py @@ -10,9 +10,9 @@ class Bar3D(ThreeAxisChart): """ def __init__( - self, - init_opts: types.Init = InitOpts(), - render_opts: types.RenderInit = RenderOpts() + self, + init_opts: types.Init = InitOpts(), + render_opts: types.RenderInit = RenderOpts(), ): super().__init__(init_opts, render_opts) self._3d_chart_type = ChartType.BAR3D diff --git a/pyecharts/charts/three_axis_charts/graph_gl.py b/pyecharts/charts/three_axis_charts/graph_gl.py index 4ef655590..f2a88293a 100644 --- a/pyecharts/charts/three_axis_charts/graph_gl.py +++ b/pyecharts/charts/three_axis_charts/graph_gl.py @@ -12,9 +12,9 @@ class GraphGL(Chart3D): """ def __init__( - self, - init_opts: types.Init = InitOpts(), - render_opts: types.RenderInit = RenderOpts() + self, + init_opts: types.Init = InitOpts(), + render_opts: types.RenderInit = RenderOpts(), ): super().__init__(init_opts, render_opts) self._3d_chart_type = ChartType.GRAPHGL diff --git a/pyecharts/charts/three_axis_charts/line3D.py b/pyecharts/charts/three_axis_charts/line3D.py index 8bb16075d..67d2230b9 100644 --- a/pyecharts/charts/three_axis_charts/line3D.py +++ b/pyecharts/charts/three_axis_charts/line3D.py @@ -10,9 +10,9 @@ class Line3D(ThreeAxisChart): """ def __init__( - self, - init_opts: types.Init = InitOpts(), - render_opts: types.RenderInit = RenderOpts() + self, + init_opts: types.Init = InitOpts(), + render_opts: types.RenderInit = RenderOpts(), ): super().__init__(init_opts, render_opts) self._3d_chart_type = ChartType.LINE3D diff --git a/pyecharts/charts/three_axis_charts/lines3D.py b/pyecharts/charts/three_axis_charts/lines3D.py index e659ecb63..e0948b456 100644 --- a/pyecharts/charts/three_axis_charts/lines3D.py +++ b/pyecharts/charts/three_axis_charts/lines3D.py @@ -11,9 +11,9 @@ class Lines3D(Chart3D): """ def __init__( - self, - init_opts: types.Init = InitOpts(), - render_opts: types.RenderInit = RenderOpts() + self, + init_opts: types.Init = InitOpts(), + render_opts: types.RenderInit = RenderOpts(), ): super().__init__(init_opts, render_opts) self._3d_chart_type = ChartType.LINES3D diff --git a/pyecharts/charts/three_axis_charts/map3D.py b/pyecharts/charts/three_axis_charts/map3D.py index 91bf0eea9..4a6086295 100644 --- a/pyecharts/charts/three_axis_charts/map3D.py +++ b/pyecharts/charts/three_axis_charts/map3D.py @@ -11,9 +11,9 @@ class Map3D(Chart3D): """ def __init__( - self, - init_opts: types.Init = InitOpts(), - render_opts: types.RenderInit = RenderOpts() + self, + init_opts: types.Init = InitOpts(), + render_opts: types.RenderInit = RenderOpts(), ): super().__init__(init_opts, render_opts) self._3d_chart_type = ChartType.MAP3D diff --git a/pyecharts/charts/three_axis_charts/map_globe.py b/pyecharts/charts/three_axis_charts/map_globe.py index 6ee0f89c7..868be02bf 100644 --- a/pyecharts/charts/three_axis_charts/map_globe.py +++ b/pyecharts/charts/three_axis_charts/map_globe.py @@ -18,9 +18,9 @@ class MapGlobe(Chart3D, MapMixin): """ def __init__( - self, - init_opts: types.Init = InitOpts(), - render_opts: types.RenderInit = RenderOpts() + self, + init_opts: types.Init = InitOpts(), + render_opts: types.RenderInit = RenderOpts(), ): super().__init__(init_opts, render_opts) diff --git a/pyecharts/charts/three_axis_charts/scatter3D.py b/pyecharts/charts/three_axis_charts/scatter3D.py index e6e6560cd..0b43dd380 100644 --- a/pyecharts/charts/three_axis_charts/scatter3D.py +++ b/pyecharts/charts/three_axis_charts/scatter3D.py @@ -10,9 +10,9 @@ class Scatter3D(ThreeAxisChart): """ def __init__( - self, - init_opts: types.Init = InitOpts(), - render_opts: types.RenderInit = RenderOpts() + self, + init_opts: types.Init = InitOpts(), + render_opts: types.RenderInit = RenderOpts(), ): super().__init__(init_opts, render_opts) self._3d_chart_type = ChartType.SCATTER3D diff --git a/pyecharts/charts/three_axis_charts/surface3D.py b/pyecharts/charts/three_axis_charts/surface3D.py index 62963be81..9727d89b3 100644 --- a/pyecharts/charts/three_axis_charts/surface3D.py +++ b/pyecharts/charts/three_axis_charts/surface3D.py @@ -11,9 +11,9 @@ class Surface3D(ThreeAxisChart): """ def __init__( - self, - init_opts: types.Init = InitOpts(), - render_opts: types.RenderInit = RenderOpts() + self, + init_opts: types.Init = InitOpts(), + render_opts: types.RenderInit = RenderOpts(), ): super().__init__(init_opts, render_opts) self._3d_chart_type = ChartType.SURFACE diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 9a1653252..634785ce3 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -86,7 +86,7 @@ def __init__( "separator": { "middle": series_multiple_separator_middle, "end": series_multiple_separator_end, - } + }, }, }, "data": { @@ -98,8 +98,8 @@ def __init__( "separator": { "middle": data_separator_middle, "end": data_separator_end, - } - } + }, + }, } @@ -131,7 +131,7 @@ def __init__( "rotation": decals_rotation, "maxTileWidth": decals_max_tile_width, "maxTileHeight": decals_max_tile_height, - } + }, } @@ -163,7 +163,7 @@ def __init__( is_fill_bg_color: bool = False, js_host: str = "", animation_opts: Union[AnimationOpts, dict] = AnimationOpts(), - aria_opts: Union[AriaOpts, dict] = AriaOpts() + aria_opts: Union[AriaOpts, dict] = AriaOpts(), ): self.opts: dict = { "width": width, diff --git a/pyecharts/render/display.py b/pyecharts/render/display.py index 1285acf25..78c93e2a0 100644 --- a/pyecharts/render/display.py +++ b/pyecharts/render/display.py @@ -75,11 +75,11 @@ def load_javascript_contents(self): resp: Optional[http.client.HTTPResponse] = None try: conn = http.client.HTTPSConnection(host, port) - conn.request('GET', path) + 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') + 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() diff --git a/pyecharts/render/engine.py b/pyecharts/render/engine.py index 481ba4018..5edcb79d5 100644 --- a/pyecharts/render/engine.py +++ b/pyecharts/render/engine.py @@ -1,4 +1,5 @@ import os + try: from collections.abc import Iterable except ImportError: diff --git a/test.py b/test.py index cd5e64cf3..967afe580 100644 --- a/test.py +++ b/test.py @@ -4,8 +4,10 @@ # os.system("nosetests --with-coverage --cover-package pyecharts --cover-package .") # current nose -os.system("nose2 --with-coverage --coverage pyecharts " - "--coverage-config .coveragerc -s test") +os.system( + "nose2 --with-coverage --coverage pyecharts " + "--coverage-config .coveragerc -s test" +) # pytest os.system("pytest -cov-config=.coveragerc --cov=./ test/") diff --git a/test/__init__.py b/test/__init__.py index 48a10c605..a342aa3c4 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -2,7 +2,7 @@ class ConsoleOutputRedirect: - """ Wrapper to redirect stdout or stderr """ + """Wrapper to redirect stdout or stderr""" def __init__(self, fp): self.fp = fp diff --git a/test/test_base.py b/test/test_base.py index 73e5822c8..a0488d764 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -55,22 +55,24 @@ def test_render_embed_js(_): c = Base(render_opts=RenderOpts(embed_js=True)) # Embedded JavaScript content = c.render_embed() - assert_not_in(CurrentConfig.ONLINE_HOST, content, 'Embedding JavaScript fails') + assert_not_in(CurrentConfig.ONLINE_HOST, content, "Embedding JavaScript fails") # No embedded JavaScript c.render_options.update(embed_js=False) content = c.render_embed() - assert_in(CurrentConfig.ONLINE_HOST, content, 'Embedded JavaScript cannot be closed') + assert_in( + CurrentConfig.ONLINE_HOST, content, "Embedded JavaScript cannot be closed" + ) def test_base_render_options(): c0 = Base(render_opts=RenderOpts(embed_js=True)) - assert_equal(c0.render_options.get('embed_js'), True) + assert_equal(c0.render_options.get("embed_js"), True) def test_base_iso_format(): mock_time_str = "2022-04-14 14:42:00" mock_time = datetime.strptime(mock_time_str, "%Y-%m-%d %H:%M:%S") - assert (default(mock_time) == "2022-04-14T14:42:00") + assert default(mock_time) == "2022-04-14T14:42:00" def test_base_animation_option(): diff --git a/test/test_calendar.py b/test/test_calendar.py index 6a9a2a992..d8b6b2415 100644 --- a/test/test_calendar.py +++ b/test/test_calendar.py @@ -47,26 +47,23 @@ def test_calendar_setting(fake_writer): for i in range((end - begin).days + 1) ] - c = ( - Calendar() - .add( - "", - data, - calendar_opts=opts.CalendarOpts( - range_="2017", - cell_size=15, - daylabel_opts=opts.CalendarDayLabelOpts(name_map="cn"), - monthlabel_opts=opts.CalendarMonthLabelOpts(name_map="cn"), - ), - visualmap_opts=opts.VisualMapOpts( - max_=20000, - min_=500, - orient="horizontal", - is_piecewise=True, - pos_top="230px", - pos_left="100px", - ) - ) + c = Calendar().add( + "", + data, + calendar_opts=opts.CalendarOpts( + range_="2017", + cell_size=15, + daylabel_opts=opts.CalendarDayLabelOpts(name_map="cn"), + monthlabel_opts=opts.CalendarMonthLabelOpts(name_map="cn"), + ), + visualmap_opts=opts.VisualMapOpts( + max_=20000, + min_=500, + orient="horizontal", + is_piecewise=True, + pos_top="230px", + pos_left="100px", + ), ) c.render() _, content = fake_writer.call_args[0] diff --git a/test/test_chart.py b/test/test_chart.py index 56c517c08..1c4764ba7 100644 --- a/test/test_chart.py +++ b/test/test_chart.py @@ -247,7 +247,7 @@ def test_chart_extend_axis(fake_writer): xaxis=opts.AxisOpts(), yaxis=opts.AxisOpts( axislabel_opts=opts.LabelOpts(formatter="{value} °C"), interval=5 - ) + ), ) .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) .set_global_opts( diff --git a/test/test_custom.py b/test/test_custom.py index fd8fb8c26..bc82bdec3 100644 --- a/test/test_custom.py +++ b/test/test_custom.py @@ -9,11 +9,10 @@ @patch("pyecharts.render.engine.write_utf8_html_file") def test_custom_base(fake_writer): - c = ( - Custom() - .add( - series_name="", - render_item=JsCode(""" + c = Custom().add( + series_name="", + render_item=JsCode( + """ function (params, api) { var categoryIndex = api.value(0); var start = api.coord([api.value(1), categoryIndex]); @@ -36,9 +35,9 @@ def test_custom_base(fake_writer): style: api.style() }; } - """), - data=None, - ) + """ + ), + data=None, ) c.render() _, content = fake_writer.call_args[0] diff --git a/test/test_datasets.py b/test/test_datasets.py index d232b2a9c..f23f12889 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -28,7 +28,7 @@ def test_register_url(fake): { "安庆": file_name, "English Name": file_name, - } + }, ) fake_registry_1 = os.path.join(current_path, "fixtures", "registry_1.json") @@ -40,7 +40,7 @@ def test_register_url(fake): { "安庆": file_name, "English Name": file_name, - } + }, ) diff --git a/test/test_exception.py b/test/test_exception.py index e52784834..695e9f96a 100644 --- a/test/test_exception.py +++ b/test/test_exception.py @@ -19,7 +19,7 @@ def test_geo_catch_nonexistent_coord_exception(): ) except NonexistentCoordinatesException as err: assert_equal(type(err), NonexistentCoordinatesException) - assert err.__str__() != '' + assert err.__str__() != "" def test_geo_ignore_nonexistent_coord_exception(): diff --git a/test/test_faker.py b/test/test_faker.py index e83d1099b..046648017 100644 --- a/test/test_faker.py +++ b/test/test_faker.py @@ -15,7 +15,6 @@ def test_img_path(): def test_collector(): - def _add(x, y): return x + y diff --git a/test/test_geo.py b/test/test_geo.py index b9564ca86..4debd8d17 100644 --- a/test/test_geo.py +++ b/test/test_geo.py @@ -178,616 +178,163 @@ def test_geo_add_geo_json(): "geometry": { "coordinates": [ [ - [ - 118.57812, - 26.557402 - ], - [ - 118.590394, - 26.553081 - ], - [ - 118.617087, - 26.553059 - ], - [ - 118.616111, - 26.527755 - ], - [ - 118.604148, - 26.504904 - ], - [ - 118.608376, - 26.497294 - ], - [ - 118.59623, - 26.478385 - ], - [ - 118.578807, - 26.473754 - ], - [ - 118.56458, - 26.465373 - ], - [ - 118.554474, - 26.445927 - ], - [ - 118.566533, - 26.432851 - ], - [ - 118.54632, - 26.417928 - ], - [ - 118.548079, - 26.396854 - ], - [ - 118.560868, - 26.378456 - ], - [ - 118.566104, - 26.358824 - ], - [ - 118.589836, - 26.373463 - ], - [ - 118.590222, - 26.359802 - ], - [ - 118.595801, - 26.347859 - ], - [ - 118.60369, - 26.350515 - ], - [ - 118.621191, - 26.361783 - ], - [ - 118.631475, - 26.354788 - ], - [ - 118.656162, - 26.340797 - ], - [ - 118.662964, - 26.322656 - ], - [ - 118.653222, - 26.313452 - ], - [ - 118.661204, - 26.296272 - ], - [ - 118.664809, - 26.270524 - ], - [ - 118.678076, - 26.279442 - ], - [ - 118.68585, - 26.28836 - ], - [ - 118.697277, - 26.287725 - ], - [ - 118.693599, - 26.305557 - ], - [ - 118.699533, - 26.311078 - ], - [ - 118.724644, - 26.337465 - ], - [ - 118.75152, - 26.330542 - ], - [ - 118.76682, - 26.327157 - ], - [ - 118.775794, - 26.325234 - ], - [ - 118.797129, - 26.340542 - ], - [ - 118.800059, - 26.357136 - ], - [ - 118.785137, - 26.365115 - ], - [ - 118.771085, - 26.385377 - ], - [ - 118.750534, - 26.390836 - ], - [ - 118.761617, - 26.420202 - ], - [ - 118.745161, - 26.427236 - ], - [ - 118.732826, - 26.437957 - ], - [ - 118.734248, - 26.45448 - ], - [ - 118.737767, - 26.472405 - ], - [ - 118.748152, - 26.481723 - ], - [ - 118.754353, - 26.487893 - ], - [ - 118.748195, - 26.494063 - ], - [ - 118.748238, - 26.506403 - ], - [ - 118.762057, - 26.508959 - ], - [ - 118.780881, - 26.504741 - ], - [ - 118.784598, - 26.491919 - ], - [ - 118.798615, - 26.484013 - ], - [ - 118.805766, - 26.474876 - ], - [ - 118.794544, - 26.459227 - ], - [ - 118.809178, - 26.445137 - ], - [ - 118.805487, - 26.430479 - ], - [ - 118.822825, - 26.420838 - ], - [ - 118.85278, - 26.438461 - ], - [ - 118.878615, - 26.469605 - ], - [ - 118.898227, - 26.473649 - ], - [ - 118.91784, - 26.472777 - ], - [ - 118.949511, - 26.464885 - ], - [ - 118.951619, - 26.487056 - ], - [ - 118.943747, - 26.493838 - ], - [ - 118.942741, - 26.507994 - ], - [ - 118.962064, - 26.530816 - ], - [ - 118.997866, - 26.528448 - ], - [ - 119.000968, - 26.556946 - ], - [ - 119.009563, - 26.560258 - ], - [ - 119.015767, - 26.581621 - ], - [ - 119.032957, - 26.594999 - ], - [ - 119.062507, - 26.602235 - ], - [ - 119.075945, - 26.587083 - ], - [ - 119.06329, - 26.569473 - ], - [ - 119.076433, - 26.569869 - ], - [ - 119.092152, - 26.562702 - ], - [ - 119.101005, - 26.572732 - ], - [ - 119.103604, - 26.555941 - ], - [ - 119.119788, - 26.539555 - ], - [ - 119.132, - 26.538732 - ], - [ - 119.144213, - 26.536681 - ], - [ - 119.171383, - 26.548551 - ], - [ - 119.194922, - 26.568383 - ], - [ - 119.212185, - 26.547589 - ], - [ - 119.218757, - 26.53412 - ], - [ - 119.229448, - 26.526792 - ], - [ - 119.251174, - 26.53241 - ], - [ - 119.253859, - 26.546561 - ], - [ - 119.272336, - 26.541055 - ], - [ - 119.281139, - 26.534343 - ], - [ - 119.287711, - 26.577995 - ], - [ - 119.301835, - 26.593391 - ], - [ - 119.329006, - 26.578699 - ], - [ - 119.354338, - 26.599587 - ], - [ - 119.363191, - 26.603281 - ], - [ - 119.376544, - 26.604515 - ], - [ - 119.381656, - 26.62171 - ], - [ - 119.397376, - 26.627859 - ], - [ - 119.387272, - 26.641423 - ], - [ - 119.375795, - 26.639028 - ], - [ - 119.358824, - 26.632951 - ], - [ - 119.351467, - 26.648969 - ], - [ - 119.326158, - 26.676212 - ], - [ - 119.261119, - 26.690807 - ], - [ - 119.241593, - 26.742684 - ], - [ - 119.226535, - 26.728854 - ], - [ - 119.204611, - 26.727288 - ], - [ - 119.182686, - 26.729403 - ], - [ - 119.167628, - 26.711891 - ], - [ - 119.146452, - 26.731884 - ], - [ - 119.115663, - 26.73961 - ], - [ - 119.047905, - 26.728693 - ], - [ - 119.045955, - 26.74669 - ], - [ - 119.054992, - 26.764685 - ], - [ - 119.051092, - 26.776147 - ], - [ - 119.042385, - 26.776575 - ], - [ - 119.033679, - 26.770872 - ], - [ - 119.012146, - 26.761305 - ], - [ - 118.982497, - 26.773075 - ], - [ - 118.945981, - 26.77013 - ], - [ - 118.926007, - 26.749652 - ], - [ - 118.882686, - 26.738982 - ], - [ - 118.887478, - 26.757469 - ], - [ - 118.903255, - 26.76737 - ], - [ - 118.923824, - 26.795751 - ], - [ - 118.890803, - 26.813278 - ], - [ - 118.843328, - 26.798569 - ], - [ - 118.827143, - 26.786924 - ], - [ - 118.795852, - 26.783859 - ], - [ - 118.771428, - 26.805618 - ], - [ - 118.741511, - 26.818793 - ], - [ - 118.728195, - 26.830895 - ], - [ - 118.722432, - 26.851573 - ], - [ - 118.739746, - 26.856167 - ], - [ - 118.696095, - 26.869031 - ], - [ - 118.664633, - 26.86214 - ], - [ - 118.63317, - 26.855248 - ], - [ - 118.620069, - 26.839818 - ], - [ - 118.635807, - 26.832963 - ], - [ - 118.638444, - 26.810674 - ], - [ - 118.625974, - 26.788073 - ], - [ - 118.623117, - 26.769147 - ], - [ - 118.600382, - 26.7093 - ], - [ - 118.582339, - 26.703842 - ], - [ - 118.569788, - 26.680592 - ], - [ - 118.57902, - 26.673354 - ], - [ - 118.594415, - 26.673546 - ], - [ - 118.594991, - 26.656748 - ], - [ - 118.587905, - 26.631739 - ], - [ - 118.57812, - 26.557402 - ] + [118.57812, 26.557402], + [118.590394, 26.553081], + [118.617087, 26.553059], + [118.616111, 26.527755], + [118.604148, 26.504904], + [118.608376, 26.497294], + [118.59623, 26.478385], + [118.578807, 26.473754], + [118.56458, 26.465373], + [118.554474, 26.445927], + [118.566533, 26.432851], + [118.54632, 26.417928], + [118.548079, 26.396854], + [118.560868, 26.378456], + [118.566104, 26.358824], + [118.589836, 26.373463], + [118.590222, 26.359802], + [118.595801, 26.347859], + [118.60369, 26.350515], + [118.621191, 26.361783], + [118.631475, 26.354788], + [118.656162, 26.340797], + [118.662964, 26.322656], + [118.653222, 26.313452], + [118.661204, 26.296272], + [118.664809, 26.270524], + [118.678076, 26.279442], + [118.68585, 26.28836], + [118.697277, 26.287725], + [118.693599, 26.305557], + [118.699533, 26.311078], + [118.724644, 26.337465], + [118.75152, 26.330542], + [118.76682, 26.327157], + [118.775794, 26.325234], + [118.797129, 26.340542], + [118.800059, 26.357136], + [118.785137, 26.365115], + [118.771085, 26.385377], + [118.750534, 26.390836], + [118.761617, 26.420202], + [118.745161, 26.427236], + [118.732826, 26.437957], + [118.734248, 26.45448], + [118.737767, 26.472405], + [118.748152, 26.481723], + [118.754353, 26.487893], + [118.748195, 26.494063], + [118.748238, 26.506403], + [118.762057, 26.508959], + [118.780881, 26.504741], + [118.784598, 26.491919], + [118.798615, 26.484013], + [118.805766, 26.474876], + [118.794544, 26.459227], + [118.809178, 26.445137], + [118.805487, 26.430479], + [118.822825, 26.420838], + [118.85278, 26.438461], + [118.878615, 26.469605], + [118.898227, 26.473649], + [118.91784, 26.472777], + [118.949511, 26.464885], + [118.951619, 26.487056], + [118.943747, 26.493838], + [118.942741, 26.507994], + [118.962064, 26.530816], + [118.997866, 26.528448], + [119.000968, 26.556946], + [119.009563, 26.560258], + [119.015767, 26.581621], + [119.032957, 26.594999], + [119.062507, 26.602235], + [119.075945, 26.587083], + [119.06329, 26.569473], + [119.076433, 26.569869], + [119.092152, 26.562702], + [119.101005, 26.572732], + [119.103604, 26.555941], + [119.119788, 26.539555], + [119.132, 26.538732], + [119.144213, 26.536681], + [119.171383, 26.548551], + [119.194922, 26.568383], + [119.212185, 26.547589], + [119.218757, 26.53412], + [119.229448, 26.526792], + [119.251174, 26.53241], + [119.253859, 26.546561], + [119.272336, 26.541055], + [119.281139, 26.534343], + [119.287711, 26.577995], + [119.301835, 26.593391], + [119.329006, 26.578699], + [119.354338, 26.599587], + [119.363191, 26.603281], + [119.376544, 26.604515], + [119.381656, 26.62171], + [119.397376, 26.627859], + [119.387272, 26.641423], + [119.375795, 26.639028], + [119.358824, 26.632951], + [119.351467, 26.648969], + [119.326158, 26.676212], + [119.261119, 26.690807], + [119.241593, 26.742684], + [119.226535, 26.728854], + [119.204611, 26.727288], + [119.182686, 26.729403], + [119.167628, 26.711891], + [119.146452, 26.731884], + [119.115663, 26.73961], + [119.047905, 26.728693], + [119.045955, 26.74669], + [119.054992, 26.764685], + [119.051092, 26.776147], + [119.042385, 26.776575], + [119.033679, 26.770872], + [119.012146, 26.761305], + [118.982497, 26.773075], + [118.945981, 26.77013], + [118.926007, 26.749652], + [118.882686, 26.738982], + [118.887478, 26.757469], + [118.903255, 26.76737], + [118.923824, 26.795751], + [118.890803, 26.813278], + [118.843328, 26.798569], + [118.827143, 26.786924], + [118.795852, 26.783859], + [118.771428, 26.805618], + [118.741511, 26.818793], + [118.728195, 26.830895], + [118.722432, 26.851573], + [118.739746, 26.856167], + [118.696095, 26.869031], + [118.664633, 26.86214], + [118.63317, 26.855248], + [118.620069, 26.839818], + [118.635807, 26.832963], + [118.638444, 26.810674], + [118.625974, 26.788073], + [118.623117, 26.769147], + [118.600382, 26.7093], + [118.582339, 26.703842], + [118.569788, 26.680592], + [118.57902, 26.673354], + [118.594415, 26.673546], + [118.594991, 26.656748], + [118.587905, 26.631739], + [118.57812, 26.557402], ] ], - "type": "Polygon" - } + "type": "Polygon", + }, } - ] + ], } c = Geo() diff --git a/test/test_global_options.py b/test/test_global_options.py index 7b8f86228..794996c6f 100644 --- a/test/test_global_options.py +++ b/test/test_global_options.py @@ -66,7 +66,7 @@ def test_aria_label_options_remove_none(): "separator": { "middle": ";", "end": "。", - } + }, }, }, "data": { @@ -77,8 +77,8 @@ def test_aria_label_options_remove_none(): "withoutName": "{value}", "separator": { "middle": ",", - } - } + }, + }, } assert_equal(expected, remove_key_with_none_value(option.opts)) @@ -97,7 +97,7 @@ def test_aria_decal_options_remove_none(): "rotation": 0, "maxTileWidth": 512, "maxTileHeight": 512, - } + }, } assert_equal(expected, remove_key_with_none_value(option.opts)) @@ -347,8 +347,5 @@ def test_radius_axis_options_remove_none(): def test_dataset_transform_options_remove_none(): option = DatasetTransformOpts() - expected = { - "type": "filter", - "print": False - } + expected = {"type": "filter", "print": False} assert_equal(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_graph_gl.py b/test/test_graph_gl.py index f9d08579a..1c303981e 100644 --- a/test/test_graph_gl.py +++ b/test/test_graph_gl.py @@ -14,27 +14,33 @@ def test_graph_gl_base(fake_writer): nodes = [] for i in range(50): for j in range(50): - nodes.append(opts.GraphGLNode( - x=random.random() * 958, - y=random.random() * 777, - value=1, - )) + nodes.append( + opts.GraphGLNode( + x=random.random() * 958, + y=random.random() * 777, + value=1, + ) + ) links = [] for i in range(50): for j in range(50): if i < 50 - 1: - links.append(opts.GraphGLLink( - source=i + j * 50, - target=i + 1 + j * 50, - value=1, - )) + links.append( + opts.GraphGLLink( + source=i + j * 50, + target=i + 1 + j * 50, + value=1, + ) + ) if j < 50 - 1: - links.append(opts.GraphGLLink( - source=i + j * 50, - target=i + (j + 1) * 50, - value=1, - )) + links.append( + opts.GraphGLLink( + source=i + j * 50, + target=i + (j + 1) * 50, + value=1, + ) + ) c = ( GraphGL(init_opts=opts.InitOpts()) @@ -47,7 +53,7 @@ def test_graph_gl_base(fake_writer): force_atlas2_opts=opts.GraphGLForceAtlas2Opts( steps=5, edge_weight_influence=4, - ) + ), ) .set_dark_mode() ) diff --git a/test/test_graphic.py b/test/test_graphic.py index 06d51c486..39f9d2a9f 100644 --- a/test/test_graphic.py +++ b/test/test_graphic.py @@ -23,30 +23,28 @@ def test_graphic_group(): def test_graphic_image(): image = opts.GraphicImage( - graphic_item={"item": 1}, - graphic_imagestyle_opts={"opts": 1} + graphic_item={"item": 1}, graphic_imagestyle_opts={"opts": 1} ) expected = { "type": "image", "item": 1, "style": { "opts": 1, - } + }, } assert_equal(expected, remove_key_with_none_value(image.opts)) def test_graphic_text(): text = opts.GraphicText( - graphic_item={"item": 1}, - graphic_textstyle_opts={"opts": 1} + graphic_item={"item": 1}, graphic_textstyle_opts={"opts": 1} ) expected = { "type": "text", "item": 1, "style": { "opts": 1, - } + }, } assert_equal(expected, remove_key_with_none_value(text.opts)) @@ -63,6 +61,6 @@ def test_graphic_rect(): "shape": 1, "style": { "opts": 1, - } + }, } assert_equal(expected, remove_key_with_none_value(rect.opts)) diff --git a/test/test_grid.py b/test/test_grid.py index e8b4b1ac0..833e929c4 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -178,34 +178,26 @@ def test_grid_two_radar(fake_writer): ] c = ( Radar() - .add_schema(schema=schema, center=['25%', '50%']) + .add_schema(schema=schema, center=["25%", "50%"]) .add( series_name="预算分配", data=[[4300, 10000, 28000, 35000, 50000, 19000]], radar_index=0, ) - .set_global_opts( - legend_opts=opts.LegendOpts(pos_left="20%") - ) + .set_global_opts(legend_opts=opts.LegendOpts(pos_left="20%")) ) c2 = ( Radar() - .add_schema(schema=schema, center=['75%', '50%']) + .add_schema(schema=schema, center=["75%", "50%"]) .add( series_name="实际开销", data=[[5000, 14000, 28000, 31000, 42000, 21000]], radar_index=1, ) - .set_global_opts( - legend_opts=opts.LegendOpts(pos_right="20%") - ) + .set_global_opts(legend_opts=opts.LegendOpts(pos_right="20%")) ) - grid = ( - Grid() - .add(c, grid_opts=opts.GridOpts()) - .add(c2, grid_opts=opts.GridOpts()) - ) + grid = Grid().add(c, grid_opts=opts.GridOpts()).add(c2, grid_opts=opts.GridOpts()) grid.render() _, content = fake_writer.call_args[0] assert_in("radar", content) diff --git a/test/test_kline.py b/test/test_kline.py index 92bc86da2..cb8f632f5 100644 --- a/test/test_kline.py +++ b/test/test_kline.py @@ -41,8 +41,7 @@ def test_kline_item_base(fake_writer): x_axis = ["2017/7/{}".format(i + 1) for i in range(10)] y_axis = data kline_item = [ - opts.CandleStickItem(name=d[0], value=d[1]) - for d in list(zip(x_axis, y_axis)) + opts.CandleStickItem(name=d[0], value=d[1]) for d in list(zip(x_axis, y_axis)) ] c = ( diff --git a/test/test_lines3d.py b/test/test_lines3d.py index 93dfd9b21..7c1f804ff 100644 --- a/test/test_lines3d.py +++ b/test/test_lines3d.py @@ -9,8 +9,9 @@ @patch("pyecharts.render.engine.write_utf8_html_file") def test_lines3d_base(fake_writer): - test_main_url: str = \ + test_main_url: str = ( "https://cdn.jsdelivr.net/gh/apache/echarts-website@asf-site/examples" + ) data_json_url = test_main_url + "/data-gl/asset/data/flights.json" base_texture = test_main_url + "/data-gl/asset/world.topo.bathy.200401.jpg" height_texture = test_main_url + "/data-gl/asset/bathymetry_bw_composite_4k.jpg" diff --git a/test/test_map.py b/test/test_map.py index 0120ab254..9ded99247 100644 --- a/test/test_map.py +++ b/test/test_map.py @@ -63,616 +63,163 @@ def test_map_add_geo_json(): "geometry": { "coordinates": [ [ - [ - 118.57812, - 26.557402 - ], - [ - 118.590394, - 26.553081 - ], - [ - 118.617087, - 26.553059 - ], - [ - 118.616111, - 26.527755 - ], - [ - 118.604148, - 26.504904 - ], - [ - 118.608376, - 26.497294 - ], - [ - 118.59623, - 26.478385 - ], - [ - 118.578807, - 26.473754 - ], - [ - 118.56458, - 26.465373 - ], - [ - 118.554474, - 26.445927 - ], - [ - 118.566533, - 26.432851 - ], - [ - 118.54632, - 26.417928 - ], - [ - 118.548079, - 26.396854 - ], - [ - 118.560868, - 26.378456 - ], - [ - 118.566104, - 26.358824 - ], - [ - 118.589836, - 26.373463 - ], - [ - 118.590222, - 26.359802 - ], - [ - 118.595801, - 26.347859 - ], - [ - 118.60369, - 26.350515 - ], - [ - 118.621191, - 26.361783 - ], - [ - 118.631475, - 26.354788 - ], - [ - 118.656162, - 26.340797 - ], - [ - 118.662964, - 26.322656 - ], - [ - 118.653222, - 26.313452 - ], - [ - 118.661204, - 26.296272 - ], - [ - 118.664809, - 26.270524 - ], - [ - 118.678076, - 26.279442 - ], - [ - 118.68585, - 26.28836 - ], - [ - 118.697277, - 26.287725 - ], - [ - 118.693599, - 26.305557 - ], - [ - 118.699533, - 26.311078 - ], - [ - 118.724644, - 26.337465 - ], - [ - 118.75152, - 26.330542 - ], - [ - 118.76682, - 26.327157 - ], - [ - 118.775794, - 26.325234 - ], - [ - 118.797129, - 26.340542 - ], - [ - 118.800059, - 26.357136 - ], - [ - 118.785137, - 26.365115 - ], - [ - 118.771085, - 26.385377 - ], - [ - 118.750534, - 26.390836 - ], - [ - 118.761617, - 26.420202 - ], - [ - 118.745161, - 26.427236 - ], - [ - 118.732826, - 26.437957 - ], - [ - 118.734248, - 26.45448 - ], - [ - 118.737767, - 26.472405 - ], - [ - 118.748152, - 26.481723 - ], - [ - 118.754353, - 26.487893 - ], - [ - 118.748195, - 26.494063 - ], - [ - 118.748238, - 26.506403 - ], - [ - 118.762057, - 26.508959 - ], - [ - 118.780881, - 26.504741 - ], - [ - 118.784598, - 26.491919 - ], - [ - 118.798615, - 26.484013 - ], - [ - 118.805766, - 26.474876 - ], - [ - 118.794544, - 26.459227 - ], - [ - 118.809178, - 26.445137 - ], - [ - 118.805487, - 26.430479 - ], - [ - 118.822825, - 26.420838 - ], - [ - 118.85278, - 26.438461 - ], - [ - 118.878615, - 26.469605 - ], - [ - 118.898227, - 26.473649 - ], - [ - 118.91784, - 26.472777 - ], - [ - 118.949511, - 26.464885 - ], - [ - 118.951619, - 26.487056 - ], - [ - 118.943747, - 26.493838 - ], - [ - 118.942741, - 26.507994 - ], - [ - 118.962064, - 26.530816 - ], - [ - 118.997866, - 26.528448 - ], - [ - 119.000968, - 26.556946 - ], - [ - 119.009563, - 26.560258 - ], - [ - 119.015767, - 26.581621 - ], - [ - 119.032957, - 26.594999 - ], - [ - 119.062507, - 26.602235 - ], - [ - 119.075945, - 26.587083 - ], - [ - 119.06329, - 26.569473 - ], - [ - 119.076433, - 26.569869 - ], - [ - 119.092152, - 26.562702 - ], - [ - 119.101005, - 26.572732 - ], - [ - 119.103604, - 26.555941 - ], - [ - 119.119788, - 26.539555 - ], - [ - 119.132, - 26.538732 - ], - [ - 119.144213, - 26.536681 - ], - [ - 119.171383, - 26.548551 - ], - [ - 119.194922, - 26.568383 - ], - [ - 119.212185, - 26.547589 - ], - [ - 119.218757, - 26.53412 - ], - [ - 119.229448, - 26.526792 - ], - [ - 119.251174, - 26.53241 - ], - [ - 119.253859, - 26.546561 - ], - [ - 119.272336, - 26.541055 - ], - [ - 119.281139, - 26.534343 - ], - [ - 119.287711, - 26.577995 - ], - [ - 119.301835, - 26.593391 - ], - [ - 119.329006, - 26.578699 - ], - [ - 119.354338, - 26.599587 - ], - [ - 119.363191, - 26.603281 - ], - [ - 119.376544, - 26.604515 - ], - [ - 119.381656, - 26.62171 - ], - [ - 119.397376, - 26.627859 - ], - [ - 119.387272, - 26.641423 - ], - [ - 119.375795, - 26.639028 - ], - [ - 119.358824, - 26.632951 - ], - [ - 119.351467, - 26.648969 - ], - [ - 119.326158, - 26.676212 - ], - [ - 119.261119, - 26.690807 - ], - [ - 119.241593, - 26.742684 - ], - [ - 119.226535, - 26.728854 - ], - [ - 119.204611, - 26.727288 - ], - [ - 119.182686, - 26.729403 - ], - [ - 119.167628, - 26.711891 - ], - [ - 119.146452, - 26.731884 - ], - [ - 119.115663, - 26.73961 - ], - [ - 119.047905, - 26.728693 - ], - [ - 119.045955, - 26.74669 - ], - [ - 119.054992, - 26.764685 - ], - [ - 119.051092, - 26.776147 - ], - [ - 119.042385, - 26.776575 - ], - [ - 119.033679, - 26.770872 - ], - [ - 119.012146, - 26.761305 - ], - [ - 118.982497, - 26.773075 - ], - [ - 118.945981, - 26.77013 - ], - [ - 118.926007, - 26.749652 - ], - [ - 118.882686, - 26.738982 - ], - [ - 118.887478, - 26.757469 - ], - [ - 118.903255, - 26.76737 - ], - [ - 118.923824, - 26.795751 - ], - [ - 118.890803, - 26.813278 - ], - [ - 118.843328, - 26.798569 - ], - [ - 118.827143, - 26.786924 - ], - [ - 118.795852, - 26.783859 - ], - [ - 118.771428, - 26.805618 - ], - [ - 118.741511, - 26.818793 - ], - [ - 118.728195, - 26.830895 - ], - [ - 118.722432, - 26.851573 - ], - [ - 118.739746, - 26.856167 - ], - [ - 118.696095, - 26.869031 - ], - [ - 118.664633, - 26.86214 - ], - [ - 118.63317, - 26.855248 - ], - [ - 118.620069, - 26.839818 - ], - [ - 118.635807, - 26.832963 - ], - [ - 118.638444, - 26.810674 - ], - [ - 118.625974, - 26.788073 - ], - [ - 118.623117, - 26.769147 - ], - [ - 118.600382, - 26.7093 - ], - [ - 118.582339, - 26.703842 - ], - [ - 118.569788, - 26.680592 - ], - [ - 118.57902, - 26.673354 - ], - [ - 118.594415, - 26.673546 - ], - [ - 118.594991, - 26.656748 - ], - [ - 118.587905, - 26.631739 - ], - [ - 118.57812, - 26.557402 - ] + [118.57812, 26.557402], + [118.590394, 26.553081], + [118.617087, 26.553059], + [118.616111, 26.527755], + [118.604148, 26.504904], + [118.608376, 26.497294], + [118.59623, 26.478385], + [118.578807, 26.473754], + [118.56458, 26.465373], + [118.554474, 26.445927], + [118.566533, 26.432851], + [118.54632, 26.417928], + [118.548079, 26.396854], + [118.560868, 26.378456], + [118.566104, 26.358824], + [118.589836, 26.373463], + [118.590222, 26.359802], + [118.595801, 26.347859], + [118.60369, 26.350515], + [118.621191, 26.361783], + [118.631475, 26.354788], + [118.656162, 26.340797], + [118.662964, 26.322656], + [118.653222, 26.313452], + [118.661204, 26.296272], + [118.664809, 26.270524], + [118.678076, 26.279442], + [118.68585, 26.28836], + [118.697277, 26.287725], + [118.693599, 26.305557], + [118.699533, 26.311078], + [118.724644, 26.337465], + [118.75152, 26.330542], + [118.76682, 26.327157], + [118.775794, 26.325234], + [118.797129, 26.340542], + [118.800059, 26.357136], + [118.785137, 26.365115], + [118.771085, 26.385377], + [118.750534, 26.390836], + [118.761617, 26.420202], + [118.745161, 26.427236], + [118.732826, 26.437957], + [118.734248, 26.45448], + [118.737767, 26.472405], + [118.748152, 26.481723], + [118.754353, 26.487893], + [118.748195, 26.494063], + [118.748238, 26.506403], + [118.762057, 26.508959], + [118.780881, 26.504741], + [118.784598, 26.491919], + [118.798615, 26.484013], + [118.805766, 26.474876], + [118.794544, 26.459227], + [118.809178, 26.445137], + [118.805487, 26.430479], + [118.822825, 26.420838], + [118.85278, 26.438461], + [118.878615, 26.469605], + [118.898227, 26.473649], + [118.91784, 26.472777], + [118.949511, 26.464885], + [118.951619, 26.487056], + [118.943747, 26.493838], + [118.942741, 26.507994], + [118.962064, 26.530816], + [118.997866, 26.528448], + [119.000968, 26.556946], + [119.009563, 26.560258], + [119.015767, 26.581621], + [119.032957, 26.594999], + [119.062507, 26.602235], + [119.075945, 26.587083], + [119.06329, 26.569473], + [119.076433, 26.569869], + [119.092152, 26.562702], + [119.101005, 26.572732], + [119.103604, 26.555941], + [119.119788, 26.539555], + [119.132, 26.538732], + [119.144213, 26.536681], + [119.171383, 26.548551], + [119.194922, 26.568383], + [119.212185, 26.547589], + [119.218757, 26.53412], + [119.229448, 26.526792], + [119.251174, 26.53241], + [119.253859, 26.546561], + [119.272336, 26.541055], + [119.281139, 26.534343], + [119.287711, 26.577995], + [119.301835, 26.593391], + [119.329006, 26.578699], + [119.354338, 26.599587], + [119.363191, 26.603281], + [119.376544, 26.604515], + [119.381656, 26.62171], + [119.397376, 26.627859], + [119.387272, 26.641423], + [119.375795, 26.639028], + [119.358824, 26.632951], + [119.351467, 26.648969], + [119.326158, 26.676212], + [119.261119, 26.690807], + [119.241593, 26.742684], + [119.226535, 26.728854], + [119.204611, 26.727288], + [119.182686, 26.729403], + [119.167628, 26.711891], + [119.146452, 26.731884], + [119.115663, 26.73961], + [119.047905, 26.728693], + [119.045955, 26.74669], + [119.054992, 26.764685], + [119.051092, 26.776147], + [119.042385, 26.776575], + [119.033679, 26.770872], + [119.012146, 26.761305], + [118.982497, 26.773075], + [118.945981, 26.77013], + [118.926007, 26.749652], + [118.882686, 26.738982], + [118.887478, 26.757469], + [118.903255, 26.76737], + [118.923824, 26.795751], + [118.890803, 26.813278], + [118.843328, 26.798569], + [118.827143, 26.786924], + [118.795852, 26.783859], + [118.771428, 26.805618], + [118.741511, 26.818793], + [118.728195, 26.830895], + [118.722432, 26.851573], + [118.739746, 26.856167], + [118.696095, 26.869031], + [118.664633, 26.86214], + [118.63317, 26.855248], + [118.620069, 26.839818], + [118.635807, 26.832963], + [118.638444, 26.810674], + [118.625974, 26.788073], + [118.623117, 26.769147], + [118.600382, 26.7093], + [118.582339, 26.703842], + [118.569788, 26.680592], + [118.57902, 26.673354], + [118.594415, 26.673546], + [118.594991, 26.656748], + [118.587905, 26.631739], + [118.57812, 26.557402], ] ], - "type": "Polygon" - } + "type": "Polygon", + }, } - ] + ], } c = Map() diff --git a/test/test_page.py b/test/test_page.py index 3e72bc891..a2616735b 100644 --- a/test/test_page.py +++ b/test/test_page.py @@ -177,9 +177,7 @@ def test_page_resize(): def test_page_resize_cfg(): page = Page() - content = page.save_resize_html( - cfg_file="test/fixtures/resize_cfg.json" - ) + content = page.save_resize_html(cfg_file="test/fixtures/resize_cfg.json") assert_not_in(".resizable()", content) assert_not_in(".draggable()", content) diff --git a/test/test_parallel.py b/test/test_parallel.py index 1bc9b1c36..bd29d3839 100644 --- a/test/test_parallel.py +++ b/test/test_parallel.py @@ -72,7 +72,7 @@ def test_parallel_base_v1(fake_writer): data=["优", "良", "轻度污染", "中度污染", "重度污染", "严重污染"], ), ], - parallel_opts=opts.ParallelOpts() + parallel_opts=opts.ParallelOpts(), ) .add("parallel", data) .set_global_opts(title_opts=opts.TitleOpts(title="Parallel-Category")) diff --git a/test/test_series_options.py b/test/test_series_options.py index d53aec513..d390e5cf3 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -82,22 +82,25 @@ def test_mark_line_item_remove_none(): def test_mark_area_item_remove_none(): item = MarkAreaItem() - expected = [{ - "itemStyle": None, - "label": None, - "name": None, - "type": None, - "valueDim": None, - "valueIndex": None, - "xAxis": None, - "yAxis": None, - }, { - "type": None, - "valueDim": None, - "valueIndex": None, - "xAxis": None, - "yAxis": None, - }] + expected = [ + { + "itemStyle": None, + "label": None, + "name": None, + "type": None, + "valueDim": None, + "valueIndex": None, + "xAxis": None, + "yAxis": None, + }, + { + "type": None, + "valueDim": None, + "valueIndex": None, + "xAxis": None, + "yAxis": None, + }, + ] assert_equal(expected, remove_key_with_none_value(item.opts)) diff --git a/test/test_snapshot.py b/test/test_snapshot.py index a22bd6319..a7d346750 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -25,11 +25,11 @@ def make_snapshot(self, *args, **kwargs): def test_decode_base64(): - assert decode_base64(data="abcde12") == b'i\xb7\x1d{]' + assert decode_base64(data="abcde12") == b"i\xb7\x1d{]" def test_save_as_png(): - save_as_png(image_data=b'i\xb7\x1d{]', output_name="text_png.png") + save_as_png(image_data=b"i\xb7\x1d{]", output_name="text_png.png") os.unlink("text_png.png") diff --git a/test/test_tab.py b/test/test_tab.py index 10ee35683..4c44ef86b 100644 --- a/test/test_tab.py +++ b/test/test_tab.py @@ -55,6 +55,7 @@ def test_tab_render_embed(): def test_tab_render_notebook(): from pyecharts.globals import CurrentConfig, NotebookType + CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK tab = Tab() @@ -96,8 +97,9 @@ def test_tab_attr(): def test_tab_with_chart_container(): - tab = Tab(tab_css_opts=opts.TabChartGlobalOpts( - is_enable=False, - tab_base_css={"overflow": "hidden"} - )) + tab = Tab( + tab_css_opts=opts.TabChartGlobalOpts( + is_enable=False, tab_base_css={"overflow": "hidden"} + ) + ) assert_true(isinstance(tab._charts, list)) diff --git a/test/test_utils.py b/test/test_utils.py index 2656fa680..5df998b62 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -10,10 +10,12 @@ def test_utils_produce_require_dict(): assert_equal(cfg["libraries"], ["'echarts'"]) cfg_1 = utils.produce_require_dict( - utils.OrderedSet("https://api.map.baidu.com"), "https://example.com", + utils.OrderedSet("https://api.map.baidu.com"), + "https://example.com", ) assert_equal( - cfg_1["config_items"], ["'baidu_map_api25':'https://api.map.baidu.com'"], + cfg_1["config_items"], + ["'baidu_map_api25':'https://api.map.baidu.com'"], ) assert_equal(cfg_1["libraries"], ["'baidu_map_api25'"]) @@ -24,7 +26,8 @@ def test_utils_produce_require_dict_with_extra(): "https://api.baidu.com/test.min": ["https://api.baidu.com/test.min", "css"] } cfg_0 = utils.produce_require_dict( - utils.OrderedSet("https://api.baidu.com/test.min"), "https://example.com", + utils.OrderedSet("https://api.baidu.com/test.min"), + "https://example.com", ) assert_equal(cfg_0["libraries"], ["'https://api.baidu.com/test.min'"]) From 05ee62f4f1ed550adf4f04cb7e864404e40a1f79 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 6 Apr 2023 17:37:04 +0800 Subject: [PATCH 078/150] update RenderOpts --- pyecharts/options/global_options.py | 4 ++-- test/test_base.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 634785ce3..fdb83002c 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -182,9 +182,9 @@ def __init__( class RenderOpts(BasicOpts): - def __init__(self, embed_js: bool = False): + def __init__(self, is_embed_js: bool = False): self.opts: dict = { - "embed_js": embed_js, + "embed_js": is_embed_js, } diff --git a/test/test_base.py b/test/test_base.py index a0488d764..dbb8a651e 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -52,7 +52,7 @@ def test_render_js_host_none(fake_writer): @patch("pyecharts.render.engine.write_utf8_html_file") def test_render_embed_js(_): - c = Base(render_opts=RenderOpts(embed_js=True)) + c = Base(render_opts=RenderOpts(is_embed_js=True)) # Embedded JavaScript content = c.render_embed() assert_not_in(CurrentConfig.ONLINE_HOST, content, "Embedding JavaScript fails") @@ -65,7 +65,7 @@ def test_render_embed_js(_): def test_base_render_options(): - c0 = Base(render_opts=RenderOpts(embed_js=True)) + c0 = Base(render_opts=RenderOpts(is_embed_js=True)) assert_equal(c0.render_options.get("embed_js"), True) From 6deda6a9abfe597d7af1c5fcb5e32d327ac73e68 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 6 Apr 2023 17:54:41 +0800 Subject: [PATCH 079/150] update map.py --- pyecharts/charts/basic_charts/map.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pyecharts/charts/basic_charts/map.py b/pyecharts/charts/basic_charts/map.py index 111c9f309..a3a598be3 100644 --- a/pyecharts/charts/basic_charts/map.py +++ b/pyecharts/charts/basic_charts/map.py @@ -30,6 +30,15 @@ def add( 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.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", + 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(), @@ -71,6 +80,15 @@ def add( "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, From 184da0efe40be40cb2875fe0efdf836c48db2b17 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 7 Apr 2023 18:10:04 +0800 Subject: [PATCH 080/150] update map.py and timeline.py --- pyecharts/charts/basic_charts/map.py | 8 ++++---- pyecharts/charts/composite_charts/timeline.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyecharts/charts/basic_charts/map.py b/pyecharts/charts/basic_charts/map.py index a3a598be3..f417dbd24 100644 --- a/pyecharts/charts/basic_charts/map.py +++ b/pyecharts/charts/basic_charts/map.py @@ -32,10 +32,10 @@ def add( is_map_symbol_show: bool = True, z_level: types.Numeric = 0, z: types.Numeric = 2, - 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", + 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, diff --git a/pyecharts/charts/composite_charts/timeline.py b/pyecharts/charts/composite_charts/timeline.py index f9aa2e6d3..2abbd80ad 100644 --- a/pyecharts/charts/composite_charts/timeline.py +++ b/pyecharts/charts/composite_charts/timeline.py @@ -92,7 +92,6 @@ def add(self, chart: Base, time_point: str): self.options.get("options").append( { "backgroundColor": chart.options.get("backgroundColor"), - "legend": chart.options.get("legend"), "series": chart.options.get("series"), "xAxis": chart.options.get("xAxis"), "yAxis": chart.options.get("yAxis"), @@ -125,6 +124,7 @@ def __check_components(self, chart: Base): "visualMap", "dataZoom", "parallelAxis", + "legend", ] for component in components: From 9acd81808b99d659bb65fc588cebb02cdc70091a Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Mon, 5 Jun 2023 11:56:28 +0800 Subject: [PATCH 081/150] update singleaxis options --- pyecharts/options/global_options.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index fdb83002c..d79ac6405 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -1268,6 +1268,15 @@ def __init__( 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, @@ -1281,6 +1290,15 @@ def __init__( "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, } From fd1fb59adabfc29f19c5a31c6a7fea49d470efd5 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Wed, 16 Aug 2023 15:44:27 +0800 Subject: [PATCH 082/150] update geo, kline, sankey charts --- .coveragerc | 2 + pyecharts/charts/basic_charts/geo.py | 31 + pyecharts/charts/basic_charts/kline.py | 2 + pyecharts/charts/basic_charts/sankey.py | 2 + pyecharts/options/charts_options.py | 1028 ++++++++++++----------- test/test_geo.py | 23 + test/test_line.py | 44 + test/test_lines3d.py | 4 +- 8 files changed, 622 insertions(+), 514 deletions(-) diff --git a/.coveragerc b/.coveragerc index e2039e3ef..17dc3c414 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,6 +3,8 @@ omit = pyecharts/render/templates/* setup.py install.py + test.py + test/* [report] show_missing = True diff --git a/pyecharts/charts/basic_charts/geo.py b/pyecharts/charts/basic_charts/geo.py index f1bda889f..c39a5db50 100644 --- a/pyecharts/charts/basic_charts/geo.py +++ b/pyecharts/charts/basic_charts/geo.py @@ -52,6 +52,7 @@ def add( 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, @@ -186,6 +187,33 @@ def add( "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 @@ -207,6 +235,9 @@ def __init__( 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 = [] for n, v in data_pair: try: diff --git a/pyecharts/charts/basic_charts/kline.py b/pyecharts/charts/basic_charts/kline.py index 880d7cfbe..94ae05532 100644 --- a/pyecharts/charts/basic_charts/kline.py +++ b/pyecharts/charts/basic_charts/kline.py @@ -31,6 +31,7 @@ def add_yaxis( y_axis: types.Sequence[types.Union[opts.CandleStickItem, dict]], *, color_by: types.Optional[str] = "series", + bar_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, @@ -46,6 +47,7 @@ def add_yaxis( "name": series_name, "colorBy": color_by, "layout": layout, + "barWidth": bar_width, "xAxisIndex": xaxis_index, "yAxisIndex": yaxis_index, "data": y_axis, diff --git a/pyecharts/charts/basic_charts/sankey.py b/pyecharts/charts/basic_charts/sankey.py index f46b6d194..b11d7f457 100644 --- a/pyecharts/charts/basic_charts/sankey.py +++ b/pyecharts/charts/basic_charts/sankey.py @@ -29,6 +29,7 @@ def add( layout_iterations: types.Numeric = 32, orient: str = "horizontal", is_draggable: bool = True, + edge_label_opt: types.Label = None, focus_node_mode: str = "none", levels: types.SankeyLevel = None, label_opts: types.Label = opts.LabelOpts(), @@ -56,6 +57,7 @@ def add( "layoutIteration": layout_iterations, "orient": orient, "draggable": is_draggable, + "edgeLabel": edge_label_opt, "emphasis": { "focus": focus_node_mode, }, diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 0fe0450da..3390bd739 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -17,465 +17,178 @@ ) -# Data Item -class BarItem(BasicOpts): +# Chart Options +class GraphNode(BasicOpts): def __init__( self, - name: Union[int, str], - value: Numeric, - *, + 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, 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, + "x": x, + "y": y, + "fixed": is_fixed, "value": value, + "category": category, + "symbol": symbol, + "symbolSize": symbol_size, "label": label_opts, - "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): +class GraphLink(BasicOpts): def __init__( self, - name: Union[str, int], - value: Sequence, - *, - itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, - tooltip_opts: Union[TooltipOpts, dict, None] = None, + 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, ): self.opts: dict = { - "name": name, + "source": source, + "target": target, "value": value, - "itemStyle": itemstyle_opts, - "tooltip": tooltip_opts, + "symbol": symbol, + "symbolSize": symbol_size, + "lineStyle": linestyle_opts, + "label": label_opts, } -class EffectScatterItem(BasicOpts): +class GraphCategory(BasicOpts): def __init__( self, - name: Union[str, Numeric], - value: Union[str, Numeric], - *, + name: Optional[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, + symbol_size: Union[Numeric, Sequence, 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, "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): +class BMapNavigationControlOpts(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, + 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 = { - "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, + "functions": [ + "bmap.addControl(new BMap.NavigationControl({}));".format( + bmap_nav_config + ) + ] } -class LineItem(BasicOpts): +class BMapOverviewMapControlOpts(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, + 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 = { - "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, + "functions": [ + "var overview = new BMap.OverviewMapControl({});".format( + bmap_overview_config + ), + "bmap.addControl(overview);", + ] } -class MapItem(BasicOpts): +class BMapScaleControlOpts(BasicOpts): def __init__( self, - name: Optional[str] = None, - value: Union[Sequence, Numeric, 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, + 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 = { - "name": name, - "value": value, - "selected": is_selected, - "label": label_opts, - "itemStyle": itemstyle_opts, - "tooltip": tooltip_opts, + "functions": [ + "bmap.addControl(new BMap.ScaleControl({}));".format(bmap_scale_config) + ] } -class PieItem(BasicOpts): +class BMapTypeControlOpts(BasicOpts): def __init__( self, - name: Optional[str] = None, - value: Optional[Numeric] = 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, + position: Numeric = BMapType.ANCHOR_TOP_RIGHT, + type_: Numeric = BMapType.MAPTYPE_CONTROL_HORIZONTAL, ): + bmap_type_config = json.dumps({"anchor": position, "type": type_}) + self.opts: dict = { - "name": name, - "value": value, - "selected": is_selected, - "label": label_opts, - "itemStyle": itemstyle_opts, - "tooltip": tooltip_opts, + "functions": [ + "bmap.addControl(new BMap.MapTypeControl({}));".format(bmap_type_config) + ] } -class RadarItem(BasicOpts): +class BMapCopyrightTypeOpts(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, + position: Numeric = BMapType.ANCHOR_BOTTOM_LEFT, + offset_width: Numeric = 2, + offset_height: Numeric = 2, + copyright_: str = "", ): - 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, - } + bmap_copyright_config = json.dumps( + { + "anchor": position, + "offset": {"width": offset_width, "height": offset_height}, + } + ) - -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, - } - - -# 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, - label_opts: Union[LabelOpts, dict, None] = None, - ): - self.opts: dict = { - "name": name, - "x": x, - "y": y, - "fixed": is_fixed, - "value": value, - "category": category, - "symbol": symbol, - "symbolSize": symbol_size, - "label": label_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, - ): - self.opts: dict = { - "source": source, - "target": target, - "value": value, - "symbol": symbol, - "symbolSize": symbol_size, - "lineStyle": linestyle_opts, - "label": label_opts, - } - - -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_}) + bmap_copyright_content_config = json.dumps({"id": 1, "content": copyright_}) self.opts: dict = { "functions": [ @@ -1296,186 +1009,475 @@ def __init__( } -class PieEmptyCircleStyle(BasicOpts): +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, + } + + +# Data Item +class BarItem(BasicOpts): + def __init__( + self, + name: Union[int, str], + value: Numeric, + *, + 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, + "label": label_opts, + "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, - 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, + 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 = { - "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, + "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 TimelineCheckPointerStyle(BasicOpts): +class LineItem(BasicOpts): def __init__( self, - symbol: str = "circle", - symbol_size: Union[Numeric, Sequence[Numeric]] = 13, + 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[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", + 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, ): - if symbol_offset is None: - symbol_offset = [0, 0] - self.opts: dict = { + "name": name, + "value": value, "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, + "label": label_opts, + "itemStyle": itemstyle_opts, + "tooltip": tooltip_opts, } -class TimelineControlStyle(BasicOpts): +class MapItem(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, + name: Optional[str] = None, + value: Union[Sequence, Numeric, 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 = { - "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, + "name": name, + "value": value, + "selected": is_selected, + "label": label_opts, + "itemStyle": itemstyle_opts, + "tooltip": tooltip_opts, } -class TabChartGlobalOpts(BasicOpts): +class PieItem(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, + name: Optional[str] = None, + value: Optional[Numeric] = 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 = { - "enable": is_enable, - "base": tab_base_css, - "button_base": tab_button_css, - "button_hover": tab_button_hover_css, - "button_active": tab_button_active_css, + "name": name, + "value": value, + "selected": is_selected, + "label": label_opts, + "labelLine": label_line_opts, + "itemStyle": itemstyle_opts, + "tooltip": tooltip_opts, } -class GraphGLNode(BasicOpts): +class RadarItem(BasicOpts): def __init__( self, name: Optional[str] = None, - x: Optional[Numeric] = None, - y: Optional[Numeric] = None, - value: Union[str, Numeric, Sequence, None] = 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, - "x": x, - "y": y, "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 GraphGLLink(BasicOpts): +class ScatterItem(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, + 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 = { - "source": source, - "target": target, + "name": name, "value": value, - "lineStyle": linestyle_opts, + "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 GeoRegionsOpts(BasicOpts): +class SunburstItem(BasicOpts): def __init__( self, + value: Optional[Numeric] = None, name: Optional[str] = None, - is_selected: bool = False, - itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, + link: Optional[str] = None, + target: Optional[str] = "blank", 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, + itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, + children: Optional[Sequence] = None, ): self.opts: dict = { + "value": value, "name": name, - "selected": is_selected, + "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, - "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, } diff --git a/test/test_geo.py b/test/test_geo.py index 4debd8d17..0866d9018 100644 --- a/test/test_geo.py +++ b/test/test_geo.py @@ -397,3 +397,26 @@ def test_geo_flow_gl(fake_writer): c.render() _, content = fake_writer.call_args[0] assert_in("canvas", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_geo_pie(fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + series_name="广东省", + data_pair=[ + ("A", 10), ("B", 20), ("C", 30), ("D", 50) + ], + type_=ChartType.PIE, + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="Geo-Pie"), + legend_opts={} + ) + ) + assert_equal(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + assert_in("canvas", content) diff --git a/test/test_line.py b/test/test_line.py index fe07b450a..f7e7d1086 100644 --- a/test/test_line.py +++ b/test/test_line.py @@ -90,3 +90,47 @@ def test_line_opts_with_zlevel_z(fake_writer): _, content = fake_writer.call_args[0] assert_in("zlevel", content) assert_in("z", content) + + +def test_line_dataset(): + c = ( + Line() + .add_dataset( + source=[ + ["product", "2012", "2013", "2014", "2015", "2016", "2017"], + ["Milk Tea", 56.5, 82.1, 88.7, 70.1, 53.4, 85.1], + ["Matcha Latte", 51.1, 51.4, 55.1, 53.3, 73.8, 68.7], + ["Cheese Cocoa", 40.1, 62.2, 69.5, 36.4, 45.2, 32.5], + ["Walnut Brownie", 25.2, 37.1, 41.2, 18, 33.9, 49.1], + ] + ) + .add_yaxis( + series_name="Milk Tea", + y_axis=[], + is_smooth=True, + series_layout_by="row", + ) + .add_yaxis( + series_name="Matcha Latte", + y_axis=[], + is_smooth=True, + series_layout_by="row", + ) + .add_yaxis( + series_name="Cheese Cocoa", + y_axis=[], + is_smooth=True, + series_layout_by="row", + ) + .add_yaxis( + series_name="Walnut Brownie", + y_axis=[], + is_smooth=True, + series_layout_by="row", + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="Dataset Line Example"), + xaxis_opts=opts.AxisOpts(type_="category"), + ) + ) + assert_equal(c.options.get("series")[0].get("data"), None) diff --git a/test/test_lines3d.py b/test/test_lines3d.py index 7c1f804ff..a84729b7c 100644 --- a/test/test_lines3d.py +++ b/test/test_lines3d.py @@ -1,7 +1,7 @@ import requests from unittest.mock import patch -from nose.tools import assert_in +from nose.tools import assert_in, assert_equal from pyecharts import options as opts from pyecharts.charts import Lines3D @@ -48,5 +48,7 @@ def _inner_func(idx): ) c.render() _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") assert_in("baseTexture", content) assert_in("heightTexture", content) From c7f22946654a2c9ae5a365f313033846b713226f Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Wed, 16 Aug 2023 16:55:38 +0800 Subject: [PATCH 083/150] update geo and version --- pyecharts/_version.py | 2 +- pyecharts/charts/basic_charts/geo.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyecharts/_version.py b/pyecharts/_version.py index 5422c0be7..8e2ccd70c 100644 --- a/pyecharts/_version.py +++ b/pyecharts/_version.py @@ -1,2 +1,2 @@ -__version__ = "2.0.3" +__version__ = "2.0.4" __author__ = "chenjiandongx" diff --git a/pyecharts/charts/basic_charts/geo.py b/pyecharts/charts/basic_charts/geo.py index c39a5db50..18e279874 100644 --- a/pyecharts/charts/basic_charts/geo.py +++ b/pyecharts/charts/basic_charts/geo.py @@ -59,7 +59,7 @@ def add( large_threshold: types.Numeric = 2000, progressive: types.Numeric = 400, progressive_threshold: types.Numeric = 3000, - label_opts: types.Label = opts.LabelOpts(), + label_opts: types.Label = None, effect_opts: types.Effect = opts.EffectOpts(), linestyle_opts: types.LineStyle = opts.LineStyleOpts(), tooltip_opts: types.Tooltip = None, From 6e780306aa864d0327a530ba31603b77e253702e Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 17 Aug 2023 17:16:02 +0800 Subject: [PATCH 084/150] update class GraphNode and class GraphLink --- pyecharts/options/charts_options.py | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 3390bd739..7f64bb0e5 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -29,7 +29,18 @@ def __init__( 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_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, + tooltip_opts: Union[TooltipOpts, dict, None] = None, ): self.opts: dict = { "name": name, @@ -40,7 +51,24 @@ def __init__( "category": category, "symbol": symbol, "symbolSize": symbol_size, + "symbolRotate": symbol_rotate, + "itemStyle": itemstyle_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, + }, + "tooltip": tooltip_opts, } @@ -54,6 +82,15 @@ def __init__( 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, @@ -63,6 +100,21 @@ def __init__( "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, } From f45e58abc71ab248e74869b661df4ff1ec958f75 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 17 Aug 2023 18:50:45 +0800 Subject: [PATCH 085/150] add conda-forge recipes folder --- recipes/pyecharts/LICENSE | 21 ++++++++++++++++ recipes/pyecharts/meta.yaml | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 recipes/pyecharts/LICENSE create mode 100644 recipes/pyecharts/meta.yaml diff --git a/recipes/pyecharts/LICENSE b/recipes/pyecharts/LICENSE new file mode 100644 index 000000000..b57eb0d4e --- /dev/null +++ b/recipes/pyecharts/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-2020 chenjiandongx + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/recipes/pyecharts/meta.yaml b/recipes/pyecharts/meta.yaml new file mode 100644 index 000000000..34e356c31 --- /dev/null +++ b/recipes/pyecharts/meta.yaml @@ -0,0 +1,48 @@ +{% set name = "pyecharts" %} +{% set version = "2.0.4" %} + + +package: + name: {{ name|lower }} + version: {{ version }} + +source: + url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/pyecharts-{{ version }}.tar.gz + sha256: 352a347c38c0bb122c1540bb2dd65cc534b3b8c9892c0e65c155d30102e824a1 + +build: + number: 0 + noarch: python + script: {{ PYTHON }} -m pip install . -vv + +requirements: + host: + - pip + - python >=3.6 + run: + - jinja2 + - prettytable + - python >=3.6 + - simplejson + +test: + imports: + - pyecharts + - pyecharts.charts + commands: + - pip check + requires: + - pip + +about: + home: https://github.com/pyecharts/pyecharts + summary: Python options, make charting easier + license: MIT + license_file: LICENSE + +extra: + recipe-maintainers: + - chenjiandongx + - chfw + - kinegratii + - sunhailin-Leo \ No newline at end of file From 23e7bfcf70d3f387cc50eca124bc30e4cba32d96 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 18 Aug 2023 15:13:41 +0800 Subject: [PATCH 086/150] update LabelOpts --- pyecharts/options/series_options.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyecharts/options/series_options.py b/pyecharts/options/series_options.py index 181ab4d7d..a0e6105a0 100644 --- a/pyecharts/options/series_options.py +++ b/pyecharts/options/series_options.py @@ -107,6 +107,10 @@ def __init__( 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, + overflow: Optional[str] = None, rich: Optional[dict] = None, ): self.opts: dict = { @@ -128,6 +132,10 @@ def __init__( "borderColor": border_color, "borderWidth": border_width, "borderRadius": border_radius, + "padding": padding, + "width": text_width, + "height": text_height, + "overflow": overflow, "rich": rich, } From 0a5a09dfb0206697a8fae8fd8b819ec5e21ad90e Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Mon, 21 Aug 2023 11:27:08 +0800 Subject: [PATCH 087/150] update GraphNode --- pyecharts/options/charts_options.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 7f64bb0e5..0e2ea90f9 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -33,12 +33,12 @@ def __init__( itemstyle_opts: Union[ItemStyleOpts, 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_itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, emphasis_label_opts: Union[LabelOpts, dict, None] = None, - blur_linestyle_opts: Union[LineStyleOpts, 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_linestyle_opts: Union[LineStyleOpts, dict, None] = None, + select_itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, select_label_opts: Union[LabelOpts, dict, None] = None, tooltip_opts: Union[TooltipOpts, dict, None] = None, ): @@ -56,16 +56,16 @@ def __init__( "label": label_opts, "emphasis": { "disabled": is_disabled_emphasis, - "lineStyle": emphasis_linestyle_opts, + "itemStyle": emphasis_itemstyle_opts, "label": emphasis_label_opts, }, "blur": { - "lineStyle": blur_linestyle_opts, + "itemStyle": blur_itemstyle_opts, "label": blur_label_opts, }, "select": { "disabled": is_disabled_select, - "lineStyle": select_linestyle_opts, + "itemStyle": select_itemstyle_opts, "label": select_label_opts, }, "tooltip": tooltip_opts, From f92c839a51d3878eeb24504ad191706c9db2c2ed Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Mon, 21 Aug 2023 11:59:26 +0800 Subject: [PATCH 088/150] update meta.yaml --- recipes/pyecharts/meta.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/pyecharts/meta.yaml b/recipes/pyecharts/meta.yaml index 34e356c31..091c4e3b6 100644 --- a/recipes/pyecharts/meta.yaml +++ b/recipes/pyecharts/meta.yaml @@ -8,7 +8,7 @@ package: source: url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/pyecharts-{{ version }}.tar.gz - sha256: 352a347c38c0bb122c1540bb2dd65cc534b3b8c9892c0e65c155d30102e824a1 + sha256: 14503e688d4de8560e3d41c19db1b472d9a0208e25dd860626e1e8c665d12d20 build: number: 0 From df5e4c6928691f42e8f0485cc3e2884aaa64c4a8 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 25 Aug 2023 11:06:13 +0800 Subject: [PATCH 089/150] update sunburst.py and add some sunburst sub config --- pyecharts/charts/basic_charts/sunburst.py | 14 ++++- pyecharts/options/__init__.py | 3 ++ pyecharts/options/charts_options.py | 62 +++++++++++++++++++++++ pyecharts/types.py | 3 ++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/pyecharts/charts/basic_charts/sunburst.py b/pyecharts/charts/basic_charts/sunburst.py index cb16b9f72..a9b5d71b2 100644 --- a/pyecharts/charts/basic_charts/sunburst.py +++ b/pyecharts/charts/basic_charts/sunburst.py @@ -24,9 +24,15 @@ def add( highlight_policy: str = "descendant", node_click: str = "rootToNode", sort_: types.Optional[types.JSFunc] = "desc", - levels: types.Optional[types.Sequence] = None, + is_render_label_for_zero_data: bool = False, + is_clockwise: bool = True, + start_angle: types.Numeric = 90, + levels: types.Optional[types.Sequence[types.SunburstLevelOpts, dict]] = 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, ): if not center: center = ["50%", "50%"] @@ -43,9 +49,15 @@ def add( "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, } ) return self diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index 7ccf49541..6a3f9835a 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -51,6 +51,9 @@ SankeyLevelsOpts, ScatterItem, SunburstItem, + SunburstLabelLayoutOpts, + SunburstLabelLineOpts, + SunburstLevelOpts, TabChartGlobalOpts, ThemeRiverItem, TimelineCheckPointerStyle, diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 0e2ea90f9..a61f0b959 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -1246,6 +1246,68 @@ def __init__( } +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__( diff --git a/pyecharts/types.py b/pyecharts/types.py index cd0d7beb2..92fd7c110 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -67,6 +67,9 @@ 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] From 8aae0a7d7659c98d6f8f0326aa8448e28016e300 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 25 Aug 2023 11:11:23 +0800 Subject: [PATCH 090/150] update sunburst.py --- pyecharts/charts/basic_charts/sunburst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyecharts/charts/basic_charts/sunburst.py b/pyecharts/charts/basic_charts/sunburst.py index a9b5d71b2..1ad174a9c 100644 --- a/pyecharts/charts/basic_charts/sunburst.py +++ b/pyecharts/charts/basic_charts/sunburst.py @@ -27,7 +27,7 @@ def add( is_render_label_for_zero_data: bool = False, is_clockwise: bool = True, start_angle: types.Numeric = 90, - levels: types.Optional[types.Sequence[types.SunburstLevelOpts, dict]] = None, + 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, From c52663f88ae606573c2bb0ea94f61143ecdf205a Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 25 Aug 2023 11:18:30 +0800 Subject: [PATCH 091/150] fix test --- test/test_series_options.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_series_options.py b/test/test_series_options.py index d390e5cf3..99d7e55de 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -35,6 +35,10 @@ def test_label_options_defaults(): "borderColor": None, "borderWidth": None, "borderRadius": None, + "padding": None, + "width": None, + "height": None, + "overflow": None, "rich": None, } assert_equal(expected, option.opts) @@ -63,6 +67,10 @@ def test_label_options_custom(): "borderColor": "green", "borderWidth": 1, "borderRadius": 2, + "padding": None, + "width": None, + "height": None, + "overflow": None, "rich": None, } assert_equal(expected, option.opts) From 34c1680be7af86de43ae1c096c314769c2d28c1a Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 25 Aug 2023 17:17:57 +0800 Subject: [PATCH 092/150] update test code --- test/test_chart_options.py | 23 +++++++++++++++++++++++ test/test_datasets.py | 6 ++++++ test/test_wordcloud.py | 18 +++++++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/test/test_chart_options.py b/test/test_chart_options.py index ff270eb16..6ee79f475 100644 --- a/test/test_chart_options.py +++ b/test/test_chart_options.py @@ -5,6 +5,9 @@ BarBackgroundStyleOpts, GlobeLayersOpts, GraphCategory, + SunburstLevelOpts, + SunburstLabelLineOpts, + SunburstLabelLayoutOpts, ThemeRiverItem, TimelineCheckPointerStyle, TimelineControlStyle, @@ -137,3 +140,23 @@ def test_geo_region_opts_remove_none(): "silent": False, } assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_sunburst_label_line_opts_remove_none(): + option = SunburstLabelLineOpts() + expected = { + "smooth": False + } + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_sunburst_label_layout_opts_remove_none(): + option = SunburstLabelLayoutOpts() + expected = {} + assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_sunburst_level_opts_remove_none(): + option = SunburstLevelOpts() + expected = {} + assert_equal(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_datasets.py b/test/test_datasets.py index f23f12889..b881f6d0b 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -51,6 +51,12 @@ def test_register_url(fake): # except (urllib.error.HTTPError, ConnectionRefusedError) as err: # assert_in(type(err), [urllib.error.HTTPError, ConnectionRefusedError]) +def test_register_url_error(): + try: + register_url("error_asset_url") + except ValueError as err: + assert_in(type(err), [ValueError]) + def test_fuzzy_search_dict(): fd = FuzzyDict() diff --git a/test/test_wordcloud.py b/test/test_wordcloud.py index 64296c249..837e89571 100644 --- a/test/test_wordcloud.py +++ b/test/test_wordcloud.py @@ -1,6 +1,6 @@ from unittest.mock import patch -from nose.tools import assert_equal, assert_in +from nose.tools import assert_equal, assert_in, assert_not_in from pyecharts.charts import WordCloud from pyecharts.commons.utils import JsCode @@ -60,3 +60,19 @@ def test_wordcloud_mask_image(fake_writer): _, content = fake_writer.call_args[0] assert_equal(c.theme, "white") assert_equal(c.renderer, "canvas") + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_wordcloud_encode_image_to_base64_os_error(fake_writer): + error_path = "A" * 1000 + c = WordCloud().add( + "", + words, + word_size_range=[20, 100], + shape="cardioid", + mask_image=f"{error_path}" + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in(error_path, content) + assert_not_in("data:image/", content) From a9013099e01f00d6dafbfc65567bde2bc830ebf7 Mon Sep 17 00:00:00 2001 From: nado Date: Sat, 28 Oct 2023 09:28:17 +0800 Subject: [PATCH 093/150] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E4=BB=AA?= =?UTF-8?q?=E8=A1=A8=E7=9B=98=E6=8C=87=E9=92=88=E9=85=8D=E7=BD=AE=E9=A1=B9?= =?UTF-8?q?=20GaugePointerOpts=20=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=20itemstyle=5Fopts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyecharts/options/charts_options.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 0e2ea90f9..ad00e23cc 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -1009,8 +1009,14 @@ def __init__( 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} + self.opts: dict = { + "show": is_show, + "length": length, + "width": width, + "itemStyle": itemstyle_opts, + } class GaugeAnchorOpts(BasicOpts): From bfaaa530fcb5d1a004b2dc833fa1dc6aa6c5c9d8 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 23 Nov 2023 11:33:14 +0800 Subject: [PATCH 094/150] update grid.py support more datazoom config and update test_grid.py --- pyecharts/charts/composite_charts/grid.py | 11 ++++++++ test/test_grid.py | 33 +++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/pyecharts/charts/composite_charts/grid.py b/pyecharts/charts/composite_charts/grid.py index a7e7d9c48..0c22f6a7d 100644 --- a/pyecharts/charts/composite_charts/grid.py +++ b/pyecharts/charts/composite_charts/grid.py @@ -58,6 +58,17 @@ def add( 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): diff --git a/test/test_grid.py b/test/test_grid.py index 833e929c4..0f2863adf 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -47,6 +47,27 @@ def _chart_for_grid() -> Bar: return bar +def _chart_for_grid_with_datazoom() -> Bar: + bar_1 = ( + Bar() + .add_xaxis(Faker.days_attrs) + .add_yaxis("商家", Faker.days_values) + .set_global_opts( + title_opts=opts.TitleOpts(title="Bar-DataZoom"), + datazoom_opts=opts.DataZoomOpts(), + toolbox_opts=opts.ToolboxOpts( + feature=opts.ToolBoxFeatureOpts( + save_as_image=opts.ToolBoxFeatureSaveAsImageOpts( + exclude_components=["dataZoom", "toolbox"], + ) + ), + ), + legend_opts=opts.LegendOpts(), + ) + ) + return bar_1 + + def test_grid_control_axis_index(): bar = _chart_for_grid() gc = Grid().add( @@ -65,6 +86,18 @@ def test_grid_do_not_control_axis_index(): assert_equal(series.get("yAxisIndex"), expected_idx[idx]) +def test_grid_with_more_datazoom_opts(): + bar_1 = _chart_for_grid_with_datazoom() + bar_2 = _chart_for_grid_with_datazoom() + grid = ( + Grid() + .add(chart=bar_1, grid_opts=opts.GridOpts()) + .add(chart=bar_2, grid_opts=opts.GridOpts()) + ) + expected_datazoom_opts_len = 2 + assert_equal(len(grid.options.get("dataZoom")), expected_datazoom_opts_len) + + @patch("pyecharts.render.engine.write_utf8_html_file") def test_grid_options(fake_writer): bar = _chart_for_grid() From b15e909f7358f0fbc016bb9ad3e9a8c483142c9a Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 23 Nov 2023 16:43:28 +0800 Subject: [PATCH 095/150] update all chart with emphasis_opts; fix legendOpts border_width; add GeoItem for Geo chart to support only geocoordinates data mode. --- pyecharts/charts/basic_charts/bar.py | 2 ++ pyecharts/charts/basic_charts/boxplot.py | 2 ++ pyecharts/charts/basic_charts/calendar.py | 2 ++ pyecharts/charts/basic_charts/custom.py | 2 ++ .../charts/basic_charts/effectscatter.py | 4 +++ pyecharts/charts/basic_charts/funnel.py | 2 ++ pyecharts/charts/basic_charts/gauge.py | 2 ++ pyecharts/charts/basic_charts/geo.py | 13 +++++++ pyecharts/charts/basic_charts/graph.py | 2 ++ pyecharts/charts/basic_charts/heatmap.py | 2 ++ pyecharts/charts/basic_charts/kline.py | 2 ++ pyecharts/charts/basic_charts/line.py | 2 ++ pyecharts/charts/basic_charts/parallel.py | 2 ++ pyecharts/charts/basic_charts/pictorialbar.py | 2 ++ pyecharts/charts/basic_charts/pie.py | 2 ++ pyecharts/charts/basic_charts/polar.py | 3 ++ pyecharts/charts/basic_charts/radar.py | 2 ++ pyecharts/charts/basic_charts/sankey.py | 6 ++-- pyecharts/charts/basic_charts/scatter.py | 2 ++ pyecharts/charts/basic_charts/sunburst.py | 2 ++ pyecharts/charts/basic_charts/themeriver.py | 2 ++ pyecharts/charts/basic_charts/tree.py | 2 ++ pyecharts/charts/basic_charts/treemap.py | 2 ++ pyecharts/options/__init__.py | 2 ++ pyecharts/options/charts_options.py | 16 +++++++++ pyecharts/options/global_options.py | 35 ++++++++++++++++++- pyecharts/types.py | 1 + test/test_geo.py | 20 +++++++++++ 28 files changed, 133 insertions(+), 5 deletions(-) diff --git a/pyecharts/charts/basic_charts/bar.py b/pyecharts/charts/basic_charts/bar.py index 801d140cd..321e06d36 100644 --- a/pyecharts/charts/basic_charts/bar.py +++ b/pyecharts/charts/basic_charts/bar.py @@ -47,6 +47,7 @@ def add_yaxis( markline_opts: types.MarkLine = 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) @@ -89,6 +90,7 @@ def add_yaxis( "markLine": markline_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, "encode": encode, } ) diff --git a/pyecharts/charts/basic_charts/boxplot.py b/pyecharts/charts/basic_charts/boxplot.py index 26a4ada53..ff09e95f7 100644 --- a/pyecharts/charts/basic_charts/boxplot.py +++ b/pyecharts/charts/basic_charts/boxplot.py @@ -31,6 +31,7 @@ def add_yaxis( markline_opts: types.MarkLine = opts.MarkLineOpts(), tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, + emphasis_opts: types.Emphasis = None, ): if box_width is None: box_width = [7, 50] @@ -51,6 +52,7 @@ def add_yaxis( "markLine": markline_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/calendar.py b/pyecharts/charts/basic_charts/calendar.py index 63a069e07..86f131194 100644 --- a/pyecharts/charts/basic_charts/calendar.py +++ b/pyecharts/charts/basic_charts/calendar.py @@ -33,6 +33,7 @@ def add( 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: @@ -51,6 +52,7 @@ def add( "label": label_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, **other_calendar_opts, } ) diff --git a/pyecharts/charts/basic_charts/custom.py b/pyecharts/charts/basic_charts/custom.py index 91928f50c..5a10f0345 100644 --- a/pyecharts/charts/basic_charts/custom.py +++ b/pyecharts/charts/basic_charts/custom.py @@ -36,6 +36,7 @@ def add( z: types.Numeric = 2, itemstyle_opts: types.ItemStyle = None, tooltip_opts: types.Tooltip = None, + emphasis_opts: types.Emphasis = None, ): self._append_legend(series_name) @@ -63,6 +64,7 @@ def add( "zlevel": z_level, "z": z, "tooltip": tooltip_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/effectscatter.py b/pyecharts/charts/basic_charts/effectscatter.py index 8253adbcf..90266242b 100644 --- a/pyecharts/charts/basic_charts/effectscatter.py +++ b/pyecharts/charts/basic_charts/effectscatter.py @@ -26,6 +26,8 @@ def add_yaxis( effect_opts: types.Effect = opts.EffectOpts(), 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) @@ -50,6 +52,8 @@ def add_yaxis( "label": label_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, + "encode": encode, } ) return self diff --git a/pyecharts/charts/basic_charts/funnel.py b/pyecharts/charts/basic_charts/funnel.py index b37b3905a..31659eab2 100644 --- a/pyecharts/charts/basic_charts/funnel.py +++ b/pyecharts/charts/basic_charts/funnel.py @@ -26,6 +26,7 @@ def add( label_opts: types.Label = opts.LabelOpts(), tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, + emphasis_opts: types.Emphasis = None, ): self._append_color(color) if all([isinstance(d, opts.FunnelItem) for d in data_pair]): @@ -50,6 +51,7 @@ def add( "label": label_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/gauge.py b/pyecharts/charts/basic_charts/gauge.py index 3ee5b69bd..40c3651a1 100644 --- a/pyecharts/charts/basic_charts/gauge.py +++ b/pyecharts/charts/basic_charts/gauge.py @@ -39,6 +39,7 @@ def add( 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%"] @@ -67,6 +68,7 @@ def add( "anchor": anchor, "pointer": pointer, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/geo.py b/pyecharts/charts/basic_charts/geo.py index 18e279874..9a430b8b8 100644 --- a/pyecharts/charts/basic_charts/geo.py +++ b/pyecharts/charts/basic_charts/geo.py @@ -239,6 +239,19 @@ def _feed_data(self, data_pair: types.Sequence, type_: str) -> types.Sequence: 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: diff --git a/pyecharts/charts/basic_charts/graph.py b/pyecharts/charts/basic_charts/graph.py index e3fae48b6..bb8b5397e 100644 --- a/pyecharts/charts/basic_charts/graph.py +++ b/pyecharts/charts/basic_charts/graph.py @@ -37,6 +37,7 @@ def add( 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: @@ -90,6 +91,7 @@ def add( "links": _links, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/heatmap.py b/pyecharts/charts/basic_charts/heatmap.py index af0f7906e..e91b1e86c 100644 --- a/pyecharts/charts/basic_charts/heatmap.py +++ b/pyecharts/charts/basic_charts/heatmap.py @@ -34,6 +34,7 @@ def add_yaxis( markline_opts: types.MarkLine = None, tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, + emphasis_opts: types.Emphasis = None, ): self._append_legend(series_name) self.options.get("yAxis")[0].update(data=yaxis_data) @@ -49,6 +50,7 @@ def add_yaxis( "markPoint": markpoint_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/kline.py b/pyecharts/charts/basic_charts/kline.py index 94ae05532..76dcbf1b9 100644 --- a/pyecharts/charts/basic_charts/kline.py +++ b/pyecharts/charts/basic_charts/kline.py @@ -39,6 +39,7 @@ def add_yaxis( markpoint_opts: types.MarkPoint = None, 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( @@ -55,6 +56,7 @@ def add_yaxis( "markLine": markline_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/line.py b/pyecharts/charts/basic_charts/line.py index 709a44b87..ffa04a8c8 100644 --- a/pyecharts/charts/basic_charts/line.py +++ b/pyecharts/charts/basic_charts/line.py @@ -42,6 +42,7 @@ def add_yaxis( 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) @@ -88,6 +89,7 @@ def add_yaxis( "seriesLayoutBy": series_layout_by, "lineStyle": linestyle_opts, "areaStyle": areastyle_opts, + "emphasis": emphasis_opts, "markPoint": markpoint_opts, "markLine": markline_opts, "tooltip": tooltip_opts, diff --git a/pyecharts/charts/basic_charts/parallel.py b/pyecharts/charts/basic_charts/parallel.py index f72127d67..d578fe16c 100644 --- a/pyecharts/charts/basic_charts/parallel.py +++ b/pyecharts/charts/basic_charts/parallel.py @@ -47,6 +47,7 @@ def add( 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( @@ -59,6 +60,7 @@ def add( "smooth": is_smooth, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/pictorialbar.py b/pyecharts/charts/basic_charts/pictorialbar.py index ab0ee222f..d7c493499 100644 --- a/pyecharts/charts/basic_charts/pictorialbar.py +++ b/pyecharts/charts/basic_charts/pictorialbar.py @@ -37,6 +37,7 @@ def add_yaxis( 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) @@ -64,6 +65,7 @@ def add_yaxis( "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, "encode": encode, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/pie.py b/pyecharts/charts/basic_charts/pie.py index 793586d70..5b60033a4 100644 --- a/pyecharts/charts/basic_charts/pie.py +++ b/pyecharts/charts/basic_charts/pie.py @@ -39,6 +39,7 @@ def add( 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, ): if self.options.get("dataset") is not None: @@ -90,6 +91,7 @@ def add( "labelLine": label_line_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, "encode": encode, } ) diff --git a/pyecharts/charts/basic_charts/polar.py b/pyecharts/charts/basic_charts/polar.py index 5e4387957..7d9c48d72 100644 --- a/pyecharts/charts/basic_charts/polar.py +++ b/pyecharts/charts/basic_charts/polar.py @@ -47,6 +47,7 @@ def add( 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%"]}) @@ -65,6 +66,7 @@ def add( "areaStyle": areastyle_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) @@ -82,6 +84,7 @@ def add( "label": label_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) diff --git a/pyecharts/charts/basic_charts/radar.py b/pyecharts/charts/basic_charts/radar.py index efb837283..a9bb5f62a 100644 --- a/pyecharts/charts/basic_charts/radar.py +++ b/pyecharts/charts/basic_charts/radar.py @@ -66,6 +66,7 @@ def add( 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: @@ -84,6 +85,7 @@ def add( "lineStyle": linestyle_opts, "areaStyle": areastyle_opts, "tooltip": tooltip_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/sankey.py b/pyecharts/charts/basic_charts/sankey.py index b11d7f457..0e5d641c5 100644 --- a/pyecharts/charts/basic_charts/sankey.py +++ b/pyecharts/charts/basic_charts/sankey.py @@ -30,12 +30,12 @@ def add( orient: str = "horizontal", is_draggable: bool = True, edge_label_opt: types.Label = None, - focus_node_mode: str = "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, ): if layout_iterations < 32: layout_iterations = 32 @@ -58,14 +58,12 @@ def add( "orient": orient, "draggable": is_draggable, "edgeLabel": edge_label_opt, - "emphasis": { - "focus": focus_node_mode, - }, "levels": levels, "label": label_opts, "lineStyle": linestyle_opt, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/scatter.py b/pyecharts/charts/basic_charts/scatter.py index 157d77d01..d4cbd3210 100644 --- a/pyecharts/charts/basic_charts/scatter.py +++ b/pyecharts/charts/basic_charts/scatter.py @@ -50,6 +50,7 @@ def add_yaxis( 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) @@ -73,6 +74,7 @@ def add_yaxis( "markArea": markarea_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, "encode": encode, } ) diff --git a/pyecharts/charts/basic_charts/sunburst.py b/pyecharts/charts/basic_charts/sunburst.py index 1ad174a9c..afbe14d6b 100644 --- a/pyecharts/charts/basic_charts/sunburst.py +++ b/pyecharts/charts/basic_charts/sunburst.py @@ -33,6 +33,7 @@ def add( 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%"] @@ -58,6 +59,7 @@ def add( "labelLayout": label_layout_opts, "itemStyle": itemstyle_opts, "tooltip": tooltip_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/themeriver.py b/pyecharts/charts/basic_charts/themeriver.py index be7e4ab21..fc34e4cf4 100644 --- a/pyecharts/charts/basic_charts/themeriver.py +++ b/pyecharts/charts/basic_charts/themeriver.py @@ -22,6 +22,7 @@ def add( 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) @@ -34,6 +35,7 @@ def add( "label": label_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) diff --git a/pyecharts/charts/basic_charts/tree.py b/pyecharts/charts/basic_charts/tree.py index 02a1d55d7..01c314bde 100644 --- a/pyecharts/charts/basic_charts/tree.py +++ b/pyecharts/charts/basic_charts/tree.py @@ -55,6 +55,7 @@ def add( leaves_label_opts: types.Label = opts.LabelOpts(), tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, + emphasis_opts: types.Emphasis = None, ): _data = self._set_collapse_interval(data, collapse_interval) self.options.get("series").append( @@ -79,6 +80,7 @@ def add( "leaves": {"label": leaves_label_opts}, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/treemap.py b/pyecharts/charts/basic_charts/treemap.py index 106876b53..1f1a79aa0 100644 --- a/pyecharts/charts/basic_charts/treemap.py +++ b/pyecharts/charts/basic_charts/treemap.py @@ -43,6 +43,7 @@ def add( 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( @@ -76,6 +77,7 @@ def add( "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, "breadcrumb": breadcrumb_opts, + "emphasis": emphasis_opts, } ) return self diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index 6a3f9835a..550aabaa3 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -19,6 +19,7 @@ GaugeAnchorOpts, GaugeProgressOpts, GaugeTitleOpts, + GeoItem, GeoRegionsOpts, GraphCategory, GraphicBasicStyleOpts, @@ -81,6 +82,7 @@ CalendarYearLabelOpts, DataZoomOpts, DatasetTransformOpts, + EmphasisOpts, Grid3DOpts, GridOpts, InitOpts, diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index a61f0b959..13d17ba21 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -1467,6 +1467,22 @@ def __init__( } +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 PieItem(BasicOpts): def __init__( self, diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index d79ac6405..67c32a97f 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -1,6 +1,7 @@ from ..globals import CurrentConfig, RenderType, ThemeType from ..options.series_options import ( BasicOpts, + AreaStyleOpts, ItemStyleOpts, JSFunc, LabelOpts, @@ -569,7 +570,7 @@ def __init__( legend_icon: Optional[str] = None, background_color: Optional[str] = "transparent", border_color: Optional[str] = "#ccc", - border_width: int = 1, + border_width: Optional[int] = None, border_radius: Union[int, Sequence] = 0, page_button_item_gap: int = 5, page_button_gap: Optional[int] = None, @@ -1456,3 +1457,35 @@ def __init__( 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, + } diff --git a/pyecharts/types.py b/pyecharts/types.py index 92fd7c110..881849a89 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -29,6 +29,7 @@ _DataZoomType = Union[opts.DataZoomOpts, dict] DataZoom = Union[_DataZoomType, Sequence[_DataZoomType], None] Effect = Union[opts.EffectOpts, dict, None] +Emphasis = Union[opts.EmphasisOpts, dict, None] GaugeAnchor = Union[opts.GaugeAnchorOpts, dict, None] GaugeTitle = Union[opts.GaugeTitleOpts, dict, None] GaugeDetail = Union[opts.GaugeDetailOpts, dict, None] diff --git a/test/test_geo.py b/test/test_geo.py index 0866d9018..a475835b5 100644 --- a/test/test_geo.py +++ b/test/test_geo.py @@ -420,3 +420,23 @@ def test_geo_pie(fake_writer): c.render() _, content = fake_writer.call_args[0] assert_in("canvas", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_geo_item_base(fake_writer): + data_pair = [ + opts.GeoItem(longitude=121.97, latitude=30.88, name="公园", value=12), + ] + c = ( + Geo() + .add_schema(maptype="china") + .add(series_name="测试数据", data_pair=data_pair) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(), + title_opts=opts.TitleOpts(title="Geo-基本示例"), + ) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") From 387f72eb040994325758cec70925b4eac717d7cf Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 23 Nov 2023 17:00:49 +0800 Subject: [PATCH 096/150] update test code for coverage --- test/test_grid.py | 14 ++++++++++++-- test/test_line.py | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/test/test_grid.py b/test/test_grid.py index 0f2863adf..48f22b45d 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -89,13 +89,23 @@ def test_grid_do_not_control_axis_index(): def test_grid_with_more_datazoom_opts(): bar_1 = _chart_for_grid_with_datazoom() bar_2 = _chart_for_grid_with_datazoom() - grid = ( + grid_1 = ( Grid() .add(chart=bar_1, grid_opts=opts.GridOpts()) .add(chart=bar_2, grid_opts=opts.GridOpts()) ) expected_datazoom_opts_len = 2 - assert_equal(len(grid.options.get("dataZoom")), expected_datazoom_opts_len) + assert_equal(len(grid_1.options.get("dataZoom")), expected_datazoom_opts_len) + + bar_3 = _chart_for_grid() + bar_4 = _chart_for_grid_with_datazoom() + grid_2 = ( + Grid() + .add(chart=bar_3, grid_opts=opts.GridOpts()) + .add(chart=bar_4, grid_opts=opts.GridOpts()) + ) + expected_datazoom_opts_len = 1 + assert_equal(len(grid_2.options.get("dataZoom")), expected_datazoom_opts_len) @patch("pyecharts.render.engine.write_utf8_html_file") diff --git a/test/test_line.py b/test/test_line.py index f7e7d1086..a0c07d422 100644 --- a/test/test_line.py +++ b/test/test_line.py @@ -7,7 +7,7 @@ @patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_base(fake_writer): +def test_line_base(fake_writer): c = ( Line() .add_xaxis(["A", "B", "C"]) @@ -21,7 +21,22 @@ def test_bar_base(fake_writer): @patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_item_base(fake_writer): +def test_line_with_emphasis(fake_writer): + c = ( + Line() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4], emphasis_opts=opts.EmphasisOpts()) + .add_yaxis("series1", [2, 3, 6], emphasis_opts=opts.EmphasisOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + assert_in("emphasis", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_line_item_base(fake_writer): x_axis = ["A", "B", "C"] y_axis_0 = [1, 2, 4] line_item_0 = [ From b04dce524a1716863bc772f2e75efe54c70be18c Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 23 Nov 2023 17:05:17 +0800 Subject: [PATCH 097/150] update workflow --- .github/workflows/python-app.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 5c75a9a03..5f92086f2 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -7,12 +7,12 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From 4d447d5a49bdbd2efcec6c696c7b4131e2657274 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Thu, 23 Nov 2023 17:08:17 +0800 Subject: [PATCH 098/150] update workflow --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 5f92086f2..dbca687f5 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -17,7 +17,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip setuptools pip install -r requirements.txt pip install -r test/requirements.txt - name: Run unit test From 7f537c4f4379d69182e161414740a84846c1f66e Mon Sep 17 00:00:00 2001 From: LeoSun <379978424@qq.com> Date: Thu, 23 Nov 2023 17:11:35 +0800 Subject: [PATCH 099/150] fix code indent --- pyecharts/options/charts_options.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 42a3af2b0..d2b76c665 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -1012,11 +1012,11 @@ def __init__( itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, ): self.opts: dict = { - "show": is_show, - "length": length, - "width": width, - "itemStyle": itemstyle_opts, - } + "show": is_show, + "length": length, + "width": width, + "itemStyle": itemstyle_opts, + } class GaugeAnchorOpts(BasicOpts): From 85108e21d1abc9938c229672b91b01a87ce38167 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 24 Nov 2023 15:30:28 +0800 Subject: [PATCH 100/150] add axis value types validation before render; update test code --- pyecharts/commons/utils.py | 7 ++ pyecharts/faker.py | 238 ------------------------------------ test/requirements.txt | 4 +- test/test_display.py | 18 +++ test/test_global_options.py | 1 - test/test_utils.py | 19 +++ 6 files changed, 47 insertions(+), 240 deletions(-) diff --git a/pyecharts/commons/utils.py b/pyecharts/commons/utils.py index 88562567f..2af574005 100644 --- a/pyecharts/commons/utils.py +++ b/pyecharts/commons/utils.py @@ -77,6 +77,13 @@ def _clean_dict(mydict): 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 diff --git a/pyecharts/faker.py b/pyecharts/faker.py index a6d13473b..cc0e8cbc9 100644 --- a/pyecharts/faker.py +++ b/pyecharts/faker.py @@ -100,241 +100,3 @@ class Collector: @staticmethod def funcs(fn): Collector.charts.append((fn, fn.__name__)) - - -POPULATION = [ - ["Country (or dependency)", "Population\n(2019)"], - ["China", 1420062022], - ["India", 1368737513], - ["United States", 329093110], - ["Indonesia", 269536482], - ["Brazil", 212392717], - ["Pakistan", 204596442], - ["Nigeria", 200962417], - ["Bangladesh", 168065920], - ["Russia", 143895551], - ["Mexico", 132328035], - ["Japan", 126854745], - ["Ethiopia", 110135635], - ["Philippines", 108106310], - ["Egypt", 101168745], - ["Vietnam", 97429061], - ["DR Congo", 86727573], - ["Turkey", 82961805], - ["Iran", 82820766], - ["Germany", 82438639], - ["Thailand", 69306160], - ["United Kingdom", 66959016], - ["France", 65480710], - ["Tanzania", 60913557], - ["Italy", 59216525], - ["South Africa", 58065097], - ["Myanmar", 54336138], - ["Kenya", 52214791], - ["South Korea", 51339238], - ["Colombia", 49849818], - ["Spain", 46441049], - ["Uganda", 45711874], - ["Argentina", 45101781], - ["Ukraine", 43795220], - ["Algeria", 42679018], - ["Sudan", 42514094], - ["Iraq", 40412299], - ["Poland", 38028278], - ["Canada", 37279811], - ["Afghanistan", 37209007], - ["Morocco", 36635156], - ["Saudi Arabia", 34140662], - ["Peru", 32933835], - ["Uzbekistan", 32807368], - ["Venezuela", 32779868], - ["Malaysia", 32454455], - ["Angola", 31787566], - ["Mozambique", 31408823], - ["Ghana", 30096970], - ["Nepal", 29942018], - ["Yemen", 29579986], - ["Madagascar", 26969642], - ["North Korea", 25727408], - ["Côte d'Ivoire", 25531083], - ["Cameroon", 25312993], - ["Australia", 25088636], - ["Taiwan", 23758247], - ["Niger", 23176691], - ["Sri Lanka", 21018859], - ["Burkina Faso", 20321560], - ["Malawi", 19718743], - ["Mali", 19689140], - ["Romania", 19483360], - ["Kazakhstan", 18592970], - ["Syria", 18499181], - ["Chile", 18336653], - ["Zambia", 18137369], - ["Guatemala", 17577842], - ["Zimbabwe", 17297495], - ["Netherlands", 17132908], - ["Ecuador", 17100444], - ["Senegal", 16743859], - ["Cambodia", 16482646], - ["Chad", 15814345], - ["Somalia", 15636171], - ["Guinea", 13398180], - ["South Sudan", 13263184], - ["Rwanda", 12794412], - ["Benin", 11801595], - ["Tunisia", 11783168], - ["Burundi", 11575964], - ["Belgium", 11562784], - ["Cuba", 11492046], - ["Bolivia", 11379861], - ["Haiti", 11242856], - ["Greece", 11124603], - ["Dominican Republic", 10996774], - ["Czechia", 10630589], - ["Portugal", 10254666], - ["Jordan", 10069794], - ["Sweden", 10053135], - ["Azerbaijan", 10014575], - ["United Arab Emirates", 9682088], - ["Hungary", 9655361], - ["Honduras", 9568688], - ["Belarus", 9433874], - ["Tajikistan", 9292000], - ["Austria", 8766201], - ["Serbia", 8733407], - ["Switzerland", 8608259], - ["Papua New Guinea", 8586525], - ["Israel", 8583916], - ["Togo", 8186384], - ["Sierra Leone", 7883123], - ["Hong Kong", 7490776], - ["Laos", 7064242], - ["Bulgaria", 6988739], - ["Paraguay", 6981981], - ["Libya", 6569864], - ["El Salvador", 6445405], - ["Nicaragua", 6351157], - ["Kyrgyzstan", 6218616], - ["Lebanon", 6065922], - ["Turkmenistan", 5942561], - ["Singapore", 5868104], - ["Denmark", 5775224], - ["Finland", 5561389], - ["Congo", 5542197], - ["Slovakia", 5450987], - ["Norway", 5400916], - ["Eritrea", 5309659], - ["State of Palestine", 5186790], - ["Oman", 5001875], - ["Costa Rica", 4999384], - ["Liberia", 4977720], - ["Ireland", 4847139], - ["Central African Republic", 4825711], - ["New Zealand", 4792409], - ["Mauritania", 4661149], - ["Kuwait", 4248974], - ["Panama", 4226197], - ["Croatia", 4140148], - ["Moldova", 4029750], - ["Georgia", 3904204], - ["Puerto Rico", 3654978], - ["Bosnia and Herzegovina", 3501774], - ["Uruguay", 3482156], - ["Mongolia", 3166244], - ["Albania", 2938428], - ["Armenia", 2936706], - ["Jamaica", 2906339], - ["Lithuania", 2864459], - ["Qatar", 2743901], - ["Namibia", 2641996], - ["Botswana", 2374636], - ["Lesotho", 2292682], - ["Gambia", 2228075], - ["Gabon", 2109099], - ["North Macedonia", 2086720], - ["Slovenia", 2081900], - ["Guinea-Bissau", 1953723], - ["Latvia", 1911108], - ["Bahrain", 1637896], - ["Swaziland", 1415414], - ["Trinidad and Tobago", 1375443], - ["Equatorial Guinea", 1360104], - ["Timor-Leste", 1352360], - ["Estonia", 1303798], - ["Mauritius", 1271368], - ["Cyprus", 1198427], - ["Djibouti", 985690], - ["Fiji", 918757], - ["Réunion", 889918], - ["Comoros", 850910], - ["Bhutan", 826229], - ["Guyana", 786508], - ["Macao", 642090], - ["Solomon Islands", 635254], - ["Montenegro", 629355], - ["Luxembourg", 596992], - ["Western Sahara", 582478], - ["Suriname", 573085], - ["Cabo Verde", 560349], - ["Micronesia", 536579], - ["Maldives", 451738], - ["Guadeloupe", 448798], - ["Brunei", 439336], - ["Malta", 433245], - ["Bahamas", 403095], - ["Belize", 390231], - ["Martinique", 385320], - ["Iceland", 340566], - ["French Guiana", 296847], - ["French Polynesia", 288506], - ["Vanuatu", 288017], - ["Barbados", 287010], - ["New Caledonia", 283376], - ["Mayotte", 266380], - ["Sao Tome & Principe", 213379], - ["Samoa", 198909], - ["Saint Lucia", 180454], - ["Guam", 167245], - ["Channel Islands", 166828], - ["Curaçao", 162547], - ["Kiribati", 120428], - ["St. Vincent & Grenadines", 110488], - ["Tonga", 110041], - ["Grenada", 108825], - ["Aruba", 106053], - ["U.S. Virgin Islands", 104909], - ["Antigua and Barbuda", 104084], - ["Seychelles", 95702], - ["Isle of Man", 85369], - ["Andorra", 77072], - ["Dominica", 74679], - ["Cayman Islands", 63129], - ["Bermuda", 60833], - ["Greenland", 56673], - ["Saint Kitts & Nevis", 56345], - ["American Samoa", 55727], - ["Northern Mariana Islands", 55246], - ["Marshall Islands", 53211], - ["Faeroe Islands", 49692], - ["Sint Maarten", 40939], - ["Monaco", 39102], - ["Liechtenstein", 38404], - ["Turks and Caicos", 36461], - ["Gibraltar", 34879], - ["San Marino", 33683], - ["British Virgin Islands", 32206], - ["Caribbean Netherlands", 25971], - ["Palau", 22206], - ["Cook Islands", 17462], - ["Anguilla", 15174], - ["Wallis & Futuna", 11617], - ["Tuvalu", 11393], - ["Nauru", 11260], - ["Saint Pierre & Miquelon", 6375], - ["Montserrat", 5220], - ["Saint Helena", 4096], - ["Falkland Islands", 2921], - ["Niue", 1628], - ["Tokelau", 1340], - ["Holy See", 799], -] diff --git a/test/requirements.txt b/test/requirements.txt index 146ba282d..eb85a8e61 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -6,4 +6,6 @@ flake8 mccabe Pillow pytest -pytest-cov \ No newline at end of file +pytest-cov +numpy +pandas \ No newline at end of file diff --git a/test/test_display.py b/test/test_display.py index ea673f354..a9fca5c0a 100644 --- a/test/test_display.py +++ b/test/test_display.py @@ -29,3 +29,21 @@ def test_display_javascript_v1(): ) assert_equal(obj_1.data, js_content) assert_in(js_content, obj_1._repr_javascript_()) + + +def test_display_javascript_v2(): + import ssl + ssl._create_default_https_context = ssl._create_unverified_context + + obj = Javascript(lib=['https://assets.pyecharts.org/assets/v5/echarts.min.js']) + obj.load_javascript_contents() + assert_in( + "echarts", + obj.javascript_contents["https://assets.pyecharts.org/assets/v5/echarts.min.js"], + ) + + obj_1 = Javascript(lib=['https://assets.pyecharts.org/assets/v4/echarts.min.js']) + try: + obj_1.load_javascript_contents() + except RuntimeError: + pass diff --git a/test/test_global_options.py b/test/test_global_options.py index 794996c6f..76254b7a6 100644 --- a/test/test_global_options.py +++ b/test/test_global_options.py @@ -205,7 +205,6 @@ def test_legend_options_remove_none(): "itemHeight": 14, "backgroundColor": "transparent", "borderColor": "#ccc", - "borderWidth": 1, "borderRadius": 0, "pageButtonItemGap": 5, "pageButtonPosition": "end", diff --git a/test/test_utils.py b/test/test_utils.py index 5df998b62..6e197c38e 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -52,3 +52,22 @@ def test_utils_remove_key_with_none_value(): mock_data_none = None none_res = utils.remove_key_with_none_value(mock_data_none) assert none_res == mock_data_none + + +def test_utils_remove_key_with_none_value_raise_value_error(): + import numpy as np + import pandas as pd + + mock_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + mock_numpy_data = np.array(mock_data) + tmp_df = pd.DataFrame({"x": mock_data}) + mock_series_data = tmp_df["x"] + try: + utils.remove_key_with_none_value({"data": mock_numpy_data}) + except ValueError: + pass + + try: + utils.remove_key_with_none_value({"data": mock_series_data}) + except ValueError: + pass From c842f9162a82d1d98bceb3506f342fcfed469657 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Fri, 24 Nov 2023 16:10:56 +0800 Subject: [PATCH 101/150] update radar.py --- pyecharts/charts/basic_charts/radar.py | 8 ++++++++ pyecharts/types.py | 1 + 2 files changed, 9 insertions(+) diff --git a/pyecharts/charts/basic_charts/radar.py b/pyecharts/charts/basic_charts/radar.py index a9bb5f62a..1386993b3 100644 --- a/pyecharts/charts/basic_charts/radar.py +++ b/pyecharts/charts/basic_charts/radar.py @@ -22,6 +22,10 @@ def add_schema( 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, @@ -50,6 +54,10 @@ def add_schema( "splitLine": splitline_opt, "splitArea": splitarea_opt, "axisLine": axisline_opt, + "axisTick": axistick_opt, + "minorTick": minor_tick_opts, + "axisLabel": axislabel_opt, + "axisPointer": axispointer_opt, } ) return self diff --git a/pyecharts/types.py b/pyecharts/types.py index 881849a89..7a087de1a 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -52,6 +52,7 @@ 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] From c415d8b7ad321be197ba3dad9593697bd5104334 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Mon, 27 Nov 2023 10:12:51 +0800 Subject: [PATCH 102/150] fix sankey.py --- pyecharts/charts/basic_charts/sankey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyecharts/charts/basic_charts/sankey.py b/pyecharts/charts/basic_charts/sankey.py index 0e5d641c5..f28f22b2b 100644 --- a/pyecharts/charts/basic_charts/sankey.py +++ b/pyecharts/charts/basic_charts/sankey.py @@ -54,7 +54,7 @@ def add( "nodeWidth": node_width, "nodeGap": node_gap, "nodeAlign": node_align, - "layoutIteration": layout_iterations, + "layoutIterations": layout_iterations, "orient": orient, "draggable": is_draggable, "edgeLabel": edge_label_opt, From 20990b2c6e838efd55fe44bfe30f50b82bc6cfe4 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Mon, 27 Nov 2023 15:33:59 +0800 Subject: [PATCH 103/150] update sankey layout_iterations configuration --- pyecharts/charts/basic_charts/sankey.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyecharts/charts/basic_charts/sankey.py b/pyecharts/charts/basic_charts/sankey.py index f28f22b2b..cbdd6b967 100644 --- a/pyecharts/charts/basic_charts/sankey.py +++ b/pyecharts/charts/basic_charts/sankey.py @@ -26,7 +26,7 @@ def add( node_width: types.Numeric = 20, node_gap: types.Numeric = 8, node_align: str = "justify", - layout_iterations: types.Numeric = 32, + layout_iterations: types.Optional[types.Numeric] = None, orient: str = "horizontal", is_draggable: bool = True, edge_label_opt: types.Label = None, @@ -37,9 +37,6 @@ def add( itemstyle_opts: types.ItemStyle = None, emphasis_opts: types.Emphasis = None, ): - if layout_iterations < 32: - layout_iterations = 32 - self._append_legend(series_name) self.options.get("series").append( { From 662595ecc9c62fdfd7fb0f56597dfa590a216135 Mon Sep 17 00:00:00 2001 From: greatofdream Date: Tue, 16 Jan 2024 14:02:59 +0800 Subject: [PATCH 104/150] The type of VisualMapOpts::out_of_range changed from Sequence to dict Add VisualMapOpts::range_ variable inherited from VisualMap::range in echarts --- pyecharts/options/global_options.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 67c32a97f..cb4600ba2 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -633,6 +633,7 @@ def __init__( 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, @@ -652,7 +653,7 @@ def __init__( is_inverse: bool = False, precision: Optional[int] = None, pieces: Optional[Sequence] = None, - out_of_range: Optional[Sequence] = None, + out_of_range: Optional[dict] = None, item_width: int = 0, item_height: int = 0, background_color: Optional[str] = None, @@ -684,6 +685,7 @@ def __init__( "max": max_, "text": range_text, "textStyle": textstyle_opts, + "range": range_, "inRange": _inrange_op, "calculable": is_calculable, "inverse": is_inverse, From 49181d48ff364f2e947f1fc2a507a8b4f3b622c5 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 16 Jan 2024 16:10:20 +0800 Subject: [PATCH 105/150] fix test_sankey.py error --- test/test_sankey.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_sankey.py b/test/test_sankey.py index a7b86dd27..278f74bca 100644 --- a/test/test_sankey.py +++ b/test/test_sankey.py @@ -62,10 +62,11 @@ def test_sankey_new_opts(fake_writer): ], linestyle_opt=opts.LineStyleOpts(opacity=0.2, curve=0.5, color="source"), label_opts=opts.LabelOpts(position="right"), + layout_iterations=30, ) c.render() _, content = fake_writer.call_args[0] assert_in("bottom", content) assert_in("orient", content) assert_in("levels", content) - assert_in("layoutIteration", content) + assert_in("layoutIterations", content) From 81b55b3f018194a48b48801980b1725bad41c0e2 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Sun, 3 Mar 2024 14:50:21 +0800 Subject: [PATCH 106/150] update _version.py --- pyecharts/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyecharts/_version.py b/pyecharts/_version.py index 8e2ccd70c..205f89717 100644 --- a/pyecharts/_version.py +++ b/pyecharts/_version.py @@ -1,2 +1,2 @@ -__version__ = "2.0.4" +__version__ = "2.0.5" __author__ = "chenjiandongx" From 75056b3c60cecce6d6fc4085b329726ae4a028e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:44:38 +0000 Subject: [PATCH 107/150] Bump black from 19.3b0 to 24.3.0 Bumps [black](https://github.com/psf/black) from 19.3b0 to 24.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits/24.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f2c978bea..6c89d8d35 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,2 @@ -black==19.3b0 +black==24.3.0 isort==4.3.16 From 1e9d9aeed30e6f781846d14f2f6dde75ed314cad Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 22 Mar 2024 16:42:45 +0800 Subject: [PATCH 108/150] fix parallel missing parallel_item --- pyecharts/charts/basic_charts/parallel.py | 2 +- pyecharts/datasets/__init__.py | 5 +--- pyecharts/options/__init__.py | 1 + pyecharts/options/charts_options.py | 30 ++++++++++++++++++++++ test/test_parallel.py | 31 +++++++++++++++++++++++ 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/pyecharts/charts/basic_charts/parallel.py b/pyecharts/charts/basic_charts/parallel.py index d578fe16c..2130700d0 100644 --- a/pyecharts/charts/basic_charts/parallel.py +++ b/pyecharts/charts/basic_charts/parallel.py @@ -41,7 +41,7 @@ def add_schema( def add( self, series_name: str, - data: types.Sequence[types.Union[dict]], + data: types.Sequence[types.Union[opts.ParallelItem, dict]], *, is_smooth: bool = False, linestyle_opts: types.LineStyle = opts.LineStyleOpts(), diff --git a/pyecharts/datasets/__init__.py b/pyecharts/datasets/__init__.py index 630291ca6..c76dffe99 100644 --- a/pyecharts/datasets/__init__.py +++ b/pyecharts/datasets/__init__.py @@ -72,10 +72,7 @@ def _search(self, lookfor: typing.Any, stop_on_first: bool = False): return best_ratio >= self.cutoff, best_key, best_match, best_ratio def __contains__(self, item: typing.Any): - if self._search(item, True)[0]: - return True - else: - return False + return self._search(item, True)[0] def __getitem__(self, lookfor: typing.Any): matched, key, item, ratio = self._search(lookfor) diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index 550aabaa3..f270f8d9d 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -45,6 +45,7 @@ Map3DRealisticMaterialOpts, Map3DViewControlOpts, PageLayoutOpts, + ParallelItem, PieItem, PieLabelLineOpts, PieEmptyCircleStyle, diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index c0a3ec92b..71bf48053 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -1489,6 +1489,36 @@ def __init__( } +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, diff --git a/test/test_parallel.py b/test/test_parallel.py index bd29d3839..e693c9b71 100644 --- a/test/test_parallel.py +++ b/test/test_parallel.py @@ -36,6 +36,37 @@ def test_parallel_base(fake_writer): assert_equal(c.renderer, "canvas") +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_parallel_item_base(fake_writer): + item_data = [ + opts.ParallelItem(value=[1, 91, 45, 125, 0.82, 34]), + opts.ParallelItem(value=[2, 65, 27, 78, 0.86, 45]), + opts.ParallelItem(value=[3, 83, 60, 84, 1.09, 73]), + opts.ParallelItem(value=[4, 109, 81, 121, 1.28, 68]), + opts.ParallelItem(value=[5, 106, 77, 114, 1.07, 55]), + opts.ParallelItem(value=[6, 109, 81, 121, 1.28, 68]), + ] + + c = ( + Parallel() + .add_schema( + [ + {"dim": 0, "name": "data"}, + {"dim": 1, "name": "AQI"}, + {"dim": 2, "name": "PM2.5"}, + {"dim": 3, "name": "PM10"}, + {"dim": 4, "name": "CO"}, + {"dim": 5, "name": "NO2"}, + ] + ) + .add("parallel", item_data) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") def test_parallel_base_v1(fake_writer): data = [ From cd6868e06cfd53765522548cf16e9c6da6fd9344 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 22 Mar 2024 16:58:26 +0800 Subject: [PATCH 109/150] update BarItem, MapItem, PieItem with group_id argument for data drill-down --- pyecharts/options/charts_options.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 71bf48053..24fbd7945 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -1321,14 +1321,22 @@ def __init__( name: Union[int, str], 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, ): self.opts: dict = { "name": name, "value": value, + "groupId": group_id, "label": label_opts, + "labelLine": { + "show": is_show_label_line, + "lineStyle": label_line_linestyle_opts, + }, "itemStyle": itemstyle_opts, "tooltip": tooltip_opts, } @@ -1458,6 +1466,7 @@ 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, @@ -1466,6 +1475,7 @@ def __init__( self.opts: dict = { "name": name, "value": value, + "groupId": group_id, "selected": is_selected, "label": label_opts, "itemStyle": itemstyle_opts, @@ -1524,6 +1534,7 @@ 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, @@ -1533,6 +1544,7 @@ def __init__( self.opts: dict = { "name": name, "value": value, + "groupId": group_id, "selected": is_selected, "label": label_opts, "labelLine": label_line_opts, From c1e90494c2259abc4224cb0f4c84fa4e3e06c6da Mon Sep 17 00:00:00 2001 From: sunhailin Date: Wed, 27 Mar 2024 17:19:55 +0800 Subject: [PATCH 110/150] update BarItem and Graphic Opts --- pyecharts/options/charts_options.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 24fbd7945..015822ee6 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -397,6 +397,7 @@ def __init__( 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, @@ -406,6 +407,7 @@ def __init__( "x": pos_x, "y": pos_y, "font": font, + "fontSize": font_size, "textAlign": text_align, "textVerticalAlign": text_vertical_align, } @@ -1318,7 +1320,7 @@ def __init__( class BarItem(BasicOpts): def __init__( self, - name: Union[int, str], + name: Union[int, str, None], value: Numeric, *, group_id: Optional[str] = None, @@ -1328,15 +1330,19 @@ def __init__( 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": { - "show": is_show_label_line, - "lineStyle": label_line_linestyle_opts, - }, + "labelLine": label_line, "itemStyle": itemstyle_opts, "tooltip": tooltip_opts, } From c1a30082fae86b3e5dcbcd6ab50d5c5d4c929bcc Mon Sep 17 00:00:00 2001 From: sunhailin Date: Tue, 4 Jun 2024 18:53:29 +0800 Subject: [PATCH 111/150] update DataZoomOpts, ThreeAxisChart and add Emphasis3DOpts --- pyecharts/charts/chart.py | 5 ++++ pyecharts/options/__init__.py | 1 + pyecharts/options/global_options.py | 43 +++++++++++++++++++++++++++++ pyecharts/types.py | 1 + 4 files changed, 50 insertions(+) diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index 13cb2d33d..fd8cb816e 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -338,6 +338,7 @@ 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), @@ -347,6 +348,7 @@ def add( 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, @@ -366,6 +368,7 @@ def add( { "type": self._3d_chart_type, "name": series_name, + "coordinateSystem": coordinate_system, "data": data, "label": label_opts, "shading": shading, @@ -385,11 +388,13 @@ def add( { "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, } ) diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index f270f8d9d..c76897f52 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -84,6 +84,7 @@ DataZoomOpts, DatasetTransformOpts, EmphasisOpts, + Emphasis3DOpts, Grid3DOpts, GridOpts, InitOpts, diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index cb4600ba2..c4672e09b 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -509,6 +509,7 @@ 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, @@ -516,15 +517,27 @@ def __init__( 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, @@ -536,10 +549,18 @@ def __init__( "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, @@ -547,6 +568,16 @@ def __init__( "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__( @@ -1491,3 +1522,15 @@ def __init__( "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, + } diff --git a/pyecharts/types.py b/pyecharts/types.py index 7a087de1a..f133bacda 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -30,6 +30,7 @@ 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] From 7cc0a380a0699c315a66a6cf8841175647e1dd62 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 4 Jun 2024 21:59:35 +0800 Subject: [PATCH 112/150] update test code and workflows --- .github/workflows/python-app.yml | 6 ++++++ test/test_bar.py | 25 +++++++++++++++++++++++ test/test_chart_options.py | 1 + test/test_global_options.py | 34 ++++++++++++++++++++++++++++++-- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index dbca687f5..ad5182a9e 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -8,6 +8,12 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + exclude: # Python < v3.8 does not support Apple Silicon ARM64. + - python: "3.7" + platform: macos-latest + include: # So run those legacy versions on Intel CPUs. + - python: "3.7" + platform: macos-13 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/test/test_bar.py b/test/test_bar.py index a4d6380f4..1b1bf9650 100644 --- a/test/test_bar.py +++ b/test/test_bar.py @@ -51,6 +51,31 @@ def test_bar_item_base(fake_writer): assert_equal(c.renderer, "canvas") +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_bar_item_show_label_line(fake_writer): + x_axis = ["A", "B", "C"] + bar_item_1 = [ + opts.BarItem(name=d[0], value=d[1], is_show_label_line=True) + for d in list(zip(x_axis, [1, 2, 3])) + ] + bar_item_2 = [ + opts.BarItem(name=d[0], value=d[1], is_show_label_line=True) + for d in list(zip(x_axis, [4, 5, 6])) + ] + + c = ( + Bar() + .add_xaxis(x_axis) + .add_yaxis("series0", bar_item_1) + .add_yaxis("series1", bar_item_2) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_greater(len(content), 2000) + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") def test_bar_base_with_animation(fake_writer): c = ( diff --git a/test/test_chart_options.py b/test/test_chart_options.py index 6ee79f475..bef8b443a 100644 --- a/test/test_chart_options.py +++ b/test/test_chart_options.py @@ -124,6 +124,7 @@ def test_graphic_text_style_opts_remove_none(): expected = { "x": 0, "y": 0, + "fontSize": 0, "textAlign": "left", "fill": "#000", } diff --git a/test/test_global_options.py b/test/test_global_options.py index 76254b7a6..2f8fb0c3d 100644 --- a/test/test_global_options.py +++ b/test/test_global_options.py @@ -10,6 +10,7 @@ AxisTickOpts, CalendarYearLabelOpts, DatasetTransformOpts, + Emphasis3DOpts, InitOpts, ParallelAxisOpts, RadiusAxisItem, @@ -180,7 +181,9 @@ def test_brush_options_remove_none(): def test_data_zoom_options_remove_none(): option = DataZoomOpts() - expected = { + + # slider + slider_expected = { "end": 80, "filterMode": "filter", "orient": "horizontal", @@ -192,7 +195,28 @@ def test_data_zoom_options_remove_none(): "showDetail": True, "showDataShadow": True, } - assert_equal(expected, remove_key_with_none_value(option.opts)) + assert_equal(slider_expected, remove_key_with_none_value(option.opts)) + + # insider + option_1 = DataZoomOpts(type_="inside") + inside_expected = { + "end": 80, + "filterMode": "filter", + "orient": "horizontal", + "realtime": True, + "show": True, + "start": 20, + "type": "inside", + "zoomLock": False, + "showDetail": True, + "showDataShadow": True, + "disabled": False, + "zoomOnMouseWheel": True, + "moveOnMouseMove": True, + "moveOnMouseWheel": True, + "preventDefaultMouseMove": True, + } + assert_equal(inside_expected, remove_key_with_none_value(option_1.opts)) def test_legend_options_remove_none(): @@ -348,3 +372,9 @@ def test_dataset_transform_options_remove_none(): option = DatasetTransformOpts() expected = {"type": "filter", "print": False} assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_emphasis_3d_options_remove_none(): + option = Emphasis3DOpts() + expected = {} + assert_equal(expected, remove_key_with_none_value(option.opts)) From 15bbd4a73b92d43ef7426a1e1e98f45e258aafec Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Tue, 4 Jun 2024 22:06:21 +0800 Subject: [PATCH 113/150] fix key name in python-app.yml --- .github/workflows/python-app.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index ad5182a9e..190957a7c 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -9,11 +9,11 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] exclude: # Python < v3.8 does not support Apple Silicon ARM64. - - python: "3.7" - platform: macos-latest + - python-version: "3.7" + os: macos-latest include: # So run those legacy versions on Intel CPUs. - - python: "3.7" - platform: macos-13 + - python-version: "3.7" + os: macos-13 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 From 939966b9dcfe44ba1787f44766c3578814345359 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Wed, 5 Jun 2024 18:25:12 +0800 Subject: [PATCH 114/150] update BarBackgroundStyleOpts, bar_chart, line_chart, geo_chart and timeline; add BlurOpts; update test code --- pyecharts/charts/basic_charts/bar.py | 16 ++++++ pyecharts/charts/basic_charts/geo.py | 2 +- pyecharts/charts/basic_charts/line.py | 10 ++++ pyecharts/charts/composite_charts/timeline.py | 9 ++- pyecharts/options/__init__.py | 1 + pyecharts/options/charts_options.py | 4 +- pyecharts/options/global_options.py | 18 ++++++ pyecharts/types.py | 1 + test/test_bar3d.py | 28 +++++++++ test/test_chart_options.py | 2 +- test/test_datasets.py | 2 +- test/test_engine.py | 54 ++++++++++++++++++ test/test_global_options.py | 7 +++ test/test_lines3d.py | 57 ++++++++++++++++++- 14 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 test/test_engine.py diff --git a/pyecharts/charts/basic_charts/bar.py b/pyecharts/charts/basic_charts/bar.py index 321e06d36..0a658eac9 100644 --- a/pyecharts/charts/basic_charts/bar.py +++ b/pyecharts/charts/basic_charts/bar.py @@ -19,6 +19,9 @@ def add_yaxis( *, 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, @@ -36,6 +39,9 @@ def add_yaxis( 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, @@ -45,9 +51,11 @@ def add_yaxis( 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, encode: types.Union[types.JSFunc, dict, None] = None, ): self._append_color(color) @@ -62,6 +70,9 @@ def add_yaxis( "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, @@ -79,6 +90,9 @@ def add_yaxis( "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, @@ -88,9 +102,11 @@ def add_yaxis( "label": label_opts, "markPoint": markpoint_opts, "markLine": markline_opts, + "markArea": markarea_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, "emphasis": emphasis_opts, + "blur": blur_opts, "encode": encode, } ) diff --git a/pyecharts/charts/basic_charts/geo.py b/pyecharts/charts/basic_charts/geo.py index 9a430b8b8..8e3199b29 100644 --- a/pyecharts/charts/basic_charts/geo.py +++ b/pyecharts/charts/basic_charts/geo.py @@ -68,7 +68,7 @@ def add( encode: types.Union[types.JsCode, dict] = None, ): self._zlevel += 1 - data = self._feed_data(data_pair, type_) + data = self._feed_data(data_pair, type_) if data_pair else data_pair self._append_color(color) self._append_legend(series_name) diff --git a/pyecharts/charts/basic_charts/line.py b/pyecharts/charts/basic_charts/line.py index ffa04a8c8..13c45aa00 100644 --- a/pyecharts/charts/basic_charts/line.py +++ b/pyecharts/charts/basic_charts/line.py @@ -20,11 +20,15 @@ def add_yaxis( 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, @@ -37,6 +41,7 @@ def add_yaxis( 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(), @@ -72,6 +77,9 @@ def add_yaxis( "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, @@ -79,6 +87,7 @@ def add_yaxis( "clip": is_clip, "step": is_step, "stack": stack, + "stackStrategy": stack_strategy, "data": data, "hoverAnimation": is_hover_animation, "label": label_opts, @@ -92,6 +101,7 @@ def add_yaxis( "emphasis": emphasis_opts, "markPoint": markpoint_opts, "markLine": markline_opts, + "markArea": markarea_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, "zlevel": z_level, diff --git a/pyecharts/charts/composite_charts/timeline.py b/pyecharts/charts/composite_charts/timeline.py index 2abbd80ad..e641da80b 100644 --- a/pyecharts/charts/composite_charts/timeline.py +++ b/pyecharts/charts/composite_charts/timeline.py @@ -1,3 +1,5 @@ +from typing import Sequence + from ... import options as opts from ... import types from ...charts.chart import Base @@ -130,4 +132,9 @@ def __check_components(self, chart: Base): for component in components: c = chart.options.get(component, None) if c is not None: - self.options.get("baseOption").update({component: c}) + # 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}) diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index c76897f52..deb62a14d 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -76,6 +76,7 @@ AxisOpts, AxisPointerOpts, AxisTickOpts, + BlurOpts, BrushOpts, CalendarOpts, CalendarDayLabelOpts, diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index 015822ee6..c7857441d 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -866,7 +866,7 @@ def __init__( border_color: str = "#000", border_width: Numeric = 0, border_type: str = "solid", - bar_border_radius: Union[Numeric, Sequence] = 0, + border_radius: Union[Numeric, Sequence] = 0, shadow_blur: Optional[Numeric] = None, shadow_color: Optional[str] = None, shadow_offset_x: Numeric = 0, @@ -878,7 +878,7 @@ def __init__( "borderColor": border_color, "borderWidth": border_width, "borderType": border_type, - "barBorderRadius": bar_border_radius, + "borderRadius": border_radius, "shadowBlur": shadow_blur, "shadowColor": shadow_color, "shadowOffsetX": shadow_offset_x, diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index c4672e09b..d4ab6ced6 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -1534,3 +1534,21 @@ def __init__( "itemStyle": itemstyle_opts, "label": label_opts, } + + +class BlurOpts(BasicOpts): + def __init__( + self, + 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, + ): + self.opts: dict = { + "label": label_opts, + "labelLine": { + "show": is_show_label_line, + "lineStyle": label_linestyle_opts + }, + "itemStyle": itemstyle_opts, + } diff --git a/pyecharts/types.py b/pyecharts/types.py index f133bacda..aaeb2be36 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -25,6 +25,7 @@ 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] diff --git a/test/test_bar3d.py b/test/test_bar3d.py index 1be8c0cd5..cbf9e31ef 100644 --- a/test/test_bar3d.py +++ b/test/test_bar3d.py @@ -54,3 +54,31 @@ def test_bar3d_stack(fake_writer): c.render() _, content = fake_writer.call_args[0] assert_in("stack", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_bar3d_with_globe(fake_writer): + c = ( + Bar3D(init_opts=opts.InitOpts(bg_color="#000")) + .add( + series_name="Population", + coordinate_system="globe", + data=[[120, 30, 1.11112222]], + itemstyle_opts=opts.ItemStyleOpts(color="orange"), + xaxis3d_opts=None, + yaxis3d_opts=None, + zaxis3d_opts=None, + grid3d_opts=None, + ) + .add_globe( + base_texture="world.topo.bathy.200401.jpg", + height_texture="world.topo.bathy.200401.jpg", + shading="lambert", + environment="starfield.jpg", + light_opts=opts.Map3DLightOpts(main_intensity=2), + view_control_opts={"autoRotate": False} + ) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_in("globe", content) diff --git a/test/test_chart_options.py b/test/test_chart_options.py index bef8b443a..913e86f39 100644 --- a/test/test_chart_options.py +++ b/test/test_chart_options.py @@ -26,7 +26,7 @@ def test_bar_background_style_options_remove_none(): "borderColor": "#000", "borderWidth": 0, "borderType": "solid", - "barBorderRadius": 0, + "borderRadius": 0, "shadowOffsetX": 0, "shadowOffsetY": 0, } diff --git a/test/test_datasets.py b/test/test_datasets.py index b881f6d0b..dfb3c4086 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -2,7 +2,7 @@ import urllib.error from unittest.mock import patch -from nose.tools import assert_equal, assert_in, raises +from nose.tools import assert_equal, assert_in, assert_raises, raises from pyecharts.datasets import ( EXTRA, diff --git a/test/test_engine.py b/test/test_engine.py new file mode 100644 index 000000000..c54cb3d46 --- /dev/null +++ b/test/test_engine.py @@ -0,0 +1,54 @@ +from unittest.mock import patch + +from pyecharts.charts import Bar +from pyecharts.render.engine import RenderEngine +from pyecharts.datasets import EXTRA, FILENAMES +from pyecharts.globals import CurrentConfig + + +def test_generate_js_link(): + FILENAMES.update({ + "existing_dep": ("file_path", "ext") + }) + EXTRA.update({ + "https://extra_host.com": { + "dep": ("extra_file_path", "extra_ext"), + } + }) + + # Bar 图 + chart = Bar() + chart.js_host = None + chart.js_dependencies.items = [ + "https://api.map.baidu.com/test", + "existing_dep", + "dep" + ] + + RenderEngine.generate_js_link(chart) + + assert chart.js_host == CurrentConfig.ONLINE_HOST + + expected_links = [ + "https://api.map.baidu.com/test", + "{}file_path.ext".format(CurrentConfig.ONLINE_HOST), + "https://extra_host.comextra_file_path.extra_ext", + ] + assert chart.dependencies == expected_links + + +def test_import_iterable_new_location(): + # 在collections.abc不可用的情况下,尝试从collections导入 + with patch('collections.abc', side_effect=ImportError): + import collections + try: + assert collections.Iterable.__module__ == 'collections' + except AttributeError: + pass + + +def test_import_iterable_old_location(): + with patch.dict('sys.modules', {'collections.abc': object()}): + import collections.abc + # importlib.reload(collections.abc) # 重新加载模块以应用补丁 + assert collections.abc.Iterable.__module__ == 'collections.abc' diff --git a/test/test_global_options.py b/test/test_global_options.py index 2f8fb0c3d..d703decd7 100644 --- a/test/test_global_options.py +++ b/test/test_global_options.py @@ -8,6 +8,7 @@ AriaLabelOpts, AriaDecalOpts, AxisTickOpts, + BlurOpts, CalendarYearLabelOpts, DatasetTransformOpts, Emphasis3DOpts, @@ -378,3 +379,9 @@ def test_emphasis_3d_options_remove_none(): option = Emphasis3DOpts() expected = {} assert_equal(expected, remove_key_with_none_value(option.opts)) + + +def test_blur_options_remove_none(): + option = BlurOpts() + expected = {"labelLine": {"show": False}} + assert_equal(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_lines3d.py b/test/test_lines3d.py index a84729b7c..85c6ce39e 100644 --- a/test/test_lines3d.py +++ b/test/test_lines3d.py @@ -4,7 +4,8 @@ from nose.tools import assert_in, assert_equal from pyecharts import options as opts -from pyecharts.charts import Lines3D +from pyecharts.charts import Lines3D, Map3D +from pyecharts.globals import ChartType @patch("pyecharts.render.engine.write_utf8_html_file") @@ -52,3 +53,57 @@ def _inner_func(idx): assert_equal(c.renderer, "canvas") assert_in("baseTexture", content) assert_in("heightTexture", content) + + +@patch("pyecharts.render.engine.write_utf8_html_file") +def test_lines3d_with_map3d_base(fake_writer): + example_data = [ + [[119.107078, 36.70925, 1000], [116.587245, 35.415393, 1000]], + [[117.000923, 36.675807], [120.355173, 36.082982]], + [[118.047648, 36.814939], [118.66471, 37.434564]], + [[121.391382, 37.539297], [119.107078, 36.70925]], + [[116.587245, 35.415393], [122.116394, 37.509691]], + [[119.461208, 35.428588], [118.326443, 35.065282]], + [[116.307428, 37.453968], [115.469381, 35.246531]], + ] + c = ( + Map3D() + .add_schema( + maptype="山东", + itemstyle_opts=opts.ItemStyleOpts( + color="rgb(5,101,123)", + opacity=1, + border_width=0.8, + border_color="rgb(62,215,213)", + ), + light_opts=opts.Map3DLightOpts( + main_color="#fff", + main_intensity=1.2, + is_main_shadow=False, + main_alpha=55, + main_beta=10, + ambient_intensity=0.3, + ), + view_control_opts=opts.Map3DViewControlOpts(center=[-10, 0, 10]), + post_effect_opts=opts.Map3DPostEffectOpts(is_enable=False), + ) + .add( + series_name="", + data_pair=example_data, + type_=ChartType.LINES3D, + effect=opts.Lines3DEffectOpts( + is_show=True, + period=4, + trail_width=3, + trail_length=0.5, + trail_color="#f00", + trail_opacity=1, + ), + linestyle_opts=opts.LineStyleOpts(is_show=False, color="#fff", opacity=0), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Lines3D")) + ) + c.render() + _, content = fake_writer.call_args[0] + assert_equal(c.theme, "white") + assert_equal(c.renderer, "canvas") From 6998c69708a3d65fa238f7769b9a0995c5ef1cf9 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Thu, 6 Jun 2024 09:36:12 +0800 Subject: [PATCH 115/150] update test_engine.py --- test/test_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_engine.py b/test/test_engine.py index c54cb3d46..5db0b4386 100644 --- a/test/test_engine.py +++ b/test/test_engine.py @@ -43,7 +43,7 @@ def test_import_iterable_new_location(): import collections try: assert collections.Iterable.__module__ == 'collections' - except AttributeError: + except (AttributeError, AssertionError): pass From 20276f90dd020d493dd34a21e1879571d8d2fb13 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 7 Jun 2024 15:33:04 +0800 Subject: [PATCH 116/150] update test code and add RenderSepType --- pyecharts/globals.py | 6 +++--- pyecharts/render/engine.py | 7 +++++-- test/test_base.py | 2 -- test/test_datasets.py | 3 +-- test/test_globals.py | 20 ++++++++++++++++++++ test/test_graph_gl.py | 4 +--- test/test_graphic.py | 6 +----- test/test_grid.py | 1 - test/test_lines3d.py | 4 +--- test/test_snapshot.py | 20 +++++++++++++++++++- test/test_utils.py | 1 - test/test_wordcloud.py | 1 - 12 files changed, 51 insertions(+), 24 deletions(-) create mode 100644 test/test_globals.py diff --git a/pyecharts/globals.py b/pyecharts/globals.py index 619e7909f..16d86405d 100644 --- a/pyecharts/globals.py +++ b/pyecharts/globals.py @@ -132,8 +132,8 @@ class _OnlineHost: NOTEBOOK_HOST = "http://localhost:8888/nbextensions/assets/" -class _WarningControl: - ShowWarning = True +class _RenderSepType: + SepType = os.linesep RenderType = _RenderType() @@ -145,7 +145,7 @@ class _WarningControl: BMapType = _BMapType NotebookType = _NotebookType() OnlineHostType = _OnlineHost() -WarningType = _WarningControl() +RenderSepType = _RenderSepType() class _CurrentConfig: diff --git a/pyecharts/render/engine.py b/pyecharts/render/engine.py index 5edcb79d5..63b834972 100644 --- a/pyecharts/render/engine.py +++ b/pyecharts/render/engine.py @@ -9,13 +9,16 @@ from ..commons import utils from ..datasets import EXTRA, FILENAMES -from ..globals import CurrentConfig, NotebookType +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_name, "w+", encoding="utf-8") as html_file: + with open(file=file_name, + mode="w+", + encoding="utf-8", + newline=RenderSepType.SepType) as html_file: html_file.write(html_content) diff --git a/test/test_base.py b/test/test_base.py index dbb8a651e..af414f9f2 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -4,8 +4,6 @@ from nose.tools import assert_equal, assert_in, assert_not_in from pyecharts.charts import Bar -from pyecharts.commons import utils -from pyecharts.datasets import EXTRA from pyecharts.options import InitOpts, RenderOpts from pyecharts.globals import CurrentConfig from pyecharts.charts.base import Base, default diff --git a/test/test_datasets.py b/test/test_datasets.py index dfb3c4086..c304b65af 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -1,8 +1,7 @@ import os -import urllib.error from unittest.mock import patch -from nose.tools import assert_equal, assert_in, assert_raises, raises +from nose.tools import assert_equal, assert_in, raises from pyecharts.datasets import ( EXTRA, diff --git a/test/test_globals.py b/test/test_globals.py new file mode 100644 index 000000000..38c58732f --- /dev/null +++ b/test/test_globals.py @@ -0,0 +1,20 @@ +import os + +from nose.tools import assert_equal, assert_not_equal + +from pyecharts.globals import RenderSepType + + +def test_render_sep_type(): + # normal test + render_sep_type = RenderSepType.SepType + assert_equal(os.linesep, render_sep_type) + + # test modification + before_modify_type = RenderSepType.SepType + RenderSepType.SepType = "\t" + after_modify_type = RenderSepType.SepType + assert_not_equal(before_modify_type, after_modify_type) + + # Restore config + RenderSepType.SepType = os.linesep diff --git a/test/test_graph_gl.py b/test/test_graph_gl.py index 1c303981e..9e1c59bc4 100644 --- a/test/test_graph_gl.py +++ b/test/test_graph_gl.py @@ -1,9 +1,7 @@ -import os -import json import random from unittest.mock import patch -from nose.tools import assert_equal, assert_in +from nose.tools import assert_equal from pyecharts import options as opts from pyecharts.charts import GraphGL diff --git a/test/test_graphic.py b/test/test_graphic.py index 39f9d2a9f..6855571a0 100644 --- a/test/test_graphic.py +++ b/test/test_graphic.py @@ -1,8 +1,4 @@ -import os -import json -from unittest.mock import patch - -from nose.tools import assert_equal, assert_in +from nose.tools import assert_equal from pyecharts.commons.utils import remove_key_with_none_value from pyecharts import options as opts diff --git a/test/test_grid.py b/test/test_grid.py index 48f22b45d..fc9b2132d 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -6,7 +6,6 @@ from pyecharts.charts import Bar, Grid, Line, Radar, Geo, Map from pyecharts.globals import ThemeType, ChartType from pyecharts.faker import Faker -from pyecharts.commons.utils import JsCode def _chart_for_grid() -> Bar: diff --git a/test/test_lines3d.py b/test/test_lines3d.py index 85c6ce39e..75e28684e 100644 --- a/test/test_lines3d.py +++ b/test/test_lines3d.py @@ -10,9 +10,7 @@ @patch("pyecharts.render.engine.write_utf8_html_file") def test_lines3d_base(fake_writer): - test_main_url: str = ( - "https://cdn.jsdelivr.net/gh/apache/echarts-website@asf-site/examples" - ) + test_main_url: str = "https://echarts.apache.org/examples" data_json_url = test_main_url + "/data-gl/asset/data/flights.json" base_texture = test_main_url + "/data-gl/asset/world.topo.bathy.200401.jpg" height_texture = test_main_url + "/data-gl/asset/bathymetry_bw_composite_4k.jpg" diff --git a/test/test_snapshot.py b/test/test_snapshot.py index a7d346750..c83280edf 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -1,5 +1,7 @@ import os -from unittest.mock import patch +import unittest +from io import BytesIO +from unittest.mock import patch, MagicMock from nose.tools import assert_equal, raises @@ -109,3 +111,19 @@ def test_make_snapshot_text_v1(fake_writer): ) _ = fake_writer.call_args[0] assert_equal("test ok", "test ok") + + +class TestSaveAs(unittest.TestCase): + @patch('builtins.__import__', side_effect=ModuleNotFoundError) + def test_save_as_module_not_found(self, mock_import): + image_data = b'fake_image_data' + output_name = 'output.jpg' + file_type = 'JPEG' + + with self.assertRaises(Exception) as context: + save_as(image_data, output_name, file_type) + + # 检查异常消息是否包含期望的提示信息 + self.assertTrue( + f"Please install PIL for {file_type} image type" in str(context.exception), + ) diff --git a/test/test_utils.py b/test/test_utils.py index 6e197c38e..8c56ba960 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,7 +1,6 @@ from nose.tools import assert_equal from pyecharts.commons import utils -from pyecharts.datasets import EXTRA def test_utils_produce_require_dict(): diff --git a/test/test_wordcloud.py b/test/test_wordcloud.py index 837e89571..824693dc1 100644 --- a/test/test_wordcloud.py +++ b/test/test_wordcloud.py @@ -3,7 +3,6 @@ from nose.tools import assert_equal, assert_in, assert_not_in from pyecharts.charts import WordCloud -from pyecharts.commons.utils import JsCode from pyecharts.exceptions import WordCloudMaskImageException words = [ From 0de395a03f04201cc5f380360973661aed4bb780 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 7 Jun 2024 18:22:28 +0800 Subject: [PATCH 117/150] migrate from nose to nose2 --- test.py | 5 +- test/requirements.txt | 1 - test/test_bar.py | 674 +++++++++++++------------- test/test_bar3d.py | 141 +++--- test/test_base.py | 155 +++--- test/test_bmap.py | 240 +++++----- test/test_boxplot.py | 162 +++---- test/test_calendar.py | 112 ++--- test/test_chart.py | 505 ++++++++++---------- test/test_chart_options.py | 273 +++++------ test/test_custom.py | 77 ++- test/test_datasets.py | 128 +++-- test/test_display.py | 94 ++-- test/test_effectscatter.py | 43 +- test/test_engine.py | 95 ++-- test/test_exception.py | 67 ++- test/test_faker.py | 28 +- test/test_funnel.py | 48 +- test/test_gauge.py | 65 ++- test/test_geo.py | 823 ++++++++++++++++---------------- test/test_global_options.py | 679 +++++++++++++------------- test/test_globals.py | 26 +- test/test_graph.py | 255 +++++----- test/test_graph_gl.py | 98 ++-- test/test_graphic.py | 112 +++-- test/test_grid.py | 811 +++++++++++++++---------------- test/test_heatmap.py | 30 +- test/test_image.py | 59 ++- test/test_item_style_options.py | 8 - test/test_kline.py | 109 +++-- test/test_line.py | 270 +++++------ test/test_line3d.py | 56 +-- test/test_lines3d.py | 185 +++---- test/test_liquid.py | 75 ++- test/test_map.py | 424 ++++++++-------- test/test_map3d.py | 609 ++++++++++++----------- test/test_map_globe.py | 80 ++-- test/test_mixins.py | 20 +- test/test_page.py | 282 ++++++----- test/test_parallel.py | 209 ++++---- test/test_pictorialbar.py | 40 +- test/test_pie.py | 162 ++++--- test/test_polar.py | 96 ++-- test/test_radar.py | 158 +++--- test/test_sankey.py | 127 +++-- test/test_scatter.py | 420 ++++++++-------- test/test_scatter3d.py | 36 +- test/test_series_options.py | 286 +++++------ test/test_snapshot.py | 173 ++++--- test/test_sunburst.py | 107 ++--- test/test_surface3d.py | 62 +-- test/test_tab.py | 131 +++-- test/test_table.py | 74 ++- test/test_themeriver.py | 42 +- test/test_timeline.py | 47 +- test/test_tree.py | 70 ++- test/test_treemap.py | 68 +-- test/test_utils.py | 141 +++--- test/test_wordcloud.py | 102 ++-- 59 files changed, 5166 insertions(+), 5309 deletions(-) delete mode 100644 test/test_item_style_options.py diff --git a/test.py b/test.py index 967afe580..44a764566 100644 --- a/test.py +++ b/test.py @@ -1,9 +1,6 @@ import os -# older nose -# os.system("nosetests --with-coverage --cover-package pyecharts --cover-package .") - -# current nose +# nose2 os.system( "nose2 --with-coverage --coverage pyecharts " "--coverage-config .coveragerc -s test" diff --git a/test/requirements.txt b/test/requirements.txt index eb85a8e61..15fffef16 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,4 +1,3 @@ -nose nose2 codecov coverage diff --git a/test/test_bar.py b/test/test_bar.py index 1b1bf9650..4454d9944 100644 --- a/test/test_bar.py +++ b/test/test_bar.py @@ -1,11 +1,10 @@ import re import sys +import unittest from io import StringIO from test import stdout_redirect from unittest.mock import patch -from nose.tools import assert_equal, assert_greater, assert_in, assert_not_in - from pyecharts import options as opts from pyecharts.charts import Bar from pyecharts.commons.utils import JsCode @@ -13,363 +12,348 @@ from pyecharts.render.display import HTML -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_base(fake_writer): - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_greater(len(content), 2000) - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_item_base(fake_writer): - x_axis = ["A", "B", "C"] - bar_item_1 = [ - opts.BarItem(name=d[0], value=d[1]) for d in list(zip(x_axis, [1, 2, 3])) - ] - bar_item_2 = [ - opts.BarItem(name=d[0], value=d[1]) for d in list(zip(x_axis, [4, 5, 6])) - ] - - c = ( - Bar() - .add_xaxis(x_axis) - .add_yaxis("series0", bar_item_1) - .add_yaxis("series1", bar_item_2) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_greater(len(content), 2000) - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_item_show_label_line(fake_writer): - x_axis = ["A", "B", "C"] - bar_item_1 = [ - opts.BarItem(name=d[0], value=d[1], is_show_label_line=True) - for d in list(zip(x_axis, [1, 2, 3])) - ] - bar_item_2 = [ - opts.BarItem(name=d[0], value=d[1], is_show_label_line=True) - for d in list(zip(x_axis, [4, 5, 6])) - ] - - c = ( - Bar() - .add_xaxis(x_axis) - .add_yaxis("series0", bar_item_1) - .add_yaxis("series1", bar_item_2) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_greater(len(content), 2000) - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - +class TestBarChart(unittest.TestCase): -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_base_with_animation(fake_writer): - c = ( - Bar( - init_opts=opts.InitOpts( - animation_opts=opts.AnimationOpts(animation_delay=1000) + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_base(self, fake_writer): + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertGreater(len(content), 2000) + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_item_base(self, fake_writer): + x_axis = ["A", "B", "C"] + bar_item_1 = [ + opts.BarItem(name=d[0], value=d[1]) for d in list(zip(x_axis, [1, 2, 3])) + ] + bar_item_2 = [ + opts.BarItem(name=d[0], value=d[1]) for d in list(zip(x_axis, [4, 5, 6])) + ] + + c = ( + Bar() + .add_xaxis(x_axis) + .add_yaxis("series0", bar_item_1) + .add_yaxis("series1", bar_item_2) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertGreater(len(content), 2000) + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_item_show_label_line(self, fake_writer): + x_axis = ["A", "B", "C"] + bar_item_1 = [ + opts.BarItem(name=d[0], value=d[1], is_show_label_line=True) + for d in list(zip(x_axis, [1, 2, 3])) + ] + bar_item_2 = [ + opts.BarItem(name=d[0], value=d[1], is_show_label_line=True) + for d in list(zip(x_axis, [4, 5, 6])) + ] + + c = ( + Bar() + .add_xaxis(x_axis) + .add_yaxis("series0", bar_item_1) + .add_yaxis("series1", bar_item_2) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertGreater(len(content), 2000) + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_base_with_animation(self, fake_writer): + c = ( + Bar( + init_opts=opts.InitOpts( + animation_opts=opts.AnimationOpts(animation_delay=1000) + ) ) + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) ) - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("animationDelay", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_base_with_custom_background_image(fake_writer): - c = ( - Bar( - init_opts=opts.InitOpts( - bg_color={ - "type": "pattern", - "image": JsCode("img"), - "repeat": "no-repeat", + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("animationDelay", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_base_with_custom_background_image(self, fake_writer): + c = ( + Bar( + init_opts=opts.InitOpts( + bg_color={ + "type": "pattern", + "image": JsCode("img"), + "repeat": "no-repeat", + } + ) + ) + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) + .set_global_opts( + title_opts=opts.TitleOpts( + title="Bar-背景图基本示例", + subtitle="我是副标题", + title_textstyle_opts=opts.TextStyleOpts(color="white"), + ) + ) + ) + c.add_js_funcs( + """ + var img = new Image(); img.src = 'https://s2.ax1x.com/2019/07/08/ZsS0fK.jpg'; + """ + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("image", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_base_dict_config(self, fake_writer): + c = ( + Bar({"theme": ThemeType.MACARONS}) + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) + .set_global_opts( + title_opts={ + "text": "Bar-dict-setting", + "subtext": "subtext also set by dict", } ) ) - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - .set_global_opts( - title_opts=opts.TitleOpts( - title="Bar-背景图基本示例", - subtitle="我是副标题", - title_textstyle_opts=opts.TextStyleOpts(color="white"), + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "macarons") + self.assertEqual(c.renderer, "canvas") + self.assertIn("Bar-dict-setting", content) + self.assertIn("subtext also set by dict", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_colors(self, fake_writer): + c = Bar().add_xaxis(["A", "B", "C"]).add_yaxis("series0", [1, 2, 4]) + c.set_colors(["#AABBCC", "#BBCCDD", "#CCDDEE"] + c.colors) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("#AABBCC", content) + self.assertIn("#BBCCDD", content) + self.assertIn("#CCDDEE", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_series_stack(self, fake_writer): + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) + .add_yaxis("series2", [5, 8, 7]) + .set_series_opts(stack="MY_STACK_NAME") + ) + c.render() + _, content = fake_writer.call_args[0] + stack_cnt = re.findall("MY_STACK_NAME", content) + self.assertEqual(3, len(stack_cnt)) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_title_options(self, fake_writer): + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .set_global_opts( + title_opts=opts.TitleOpts( + title="This is title.", subtitle="This is subtitle." + ) ) ) - ) - c.add_js_funcs( - """ - var img = new Image(); img.src = 'https://s2.ax1x.com/2019/07/08/ZsS0fK.jpg'; - """ - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("image", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_base_dict_config(fake_writer): - c = ( - Bar({"theme": ThemeType.MACARONS}) - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - .set_global_opts( - title_opts={ - "text": "Bar-dict-setting", - "subtext": "subtext also set by dict", - } + c.render() + file_name, content = fake_writer.call_args[0] + self.assertEqual("render.html", file_name) + self.assertIn("This is title.", content) + self.assertIn("This is subtitle.", content) + self.assertNotIn("null", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_default_set_function(self, fake_writer): + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .set_global_opts() + .set_series_opts() ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "macarons") - assert_equal(c.renderer, "canvas") - assert_in("Bar-dict-setting", content) - assert_in("subtext also set by dict", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_colors(fake_writer): - c = Bar().add_xaxis(["A", "B", "C"]).add_yaxis("series0", [1, 2, 4]) - c.set_colors(["#AABBCC", "#BBCCDD", "#CCDDEE"] + c.colors) - c.render() - _, content = fake_writer.call_args[0] - assert_in("#AABBCC", content) - assert_in("#BBCCDD", content) - assert_in("#CCDDEE", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_series_stack(fake_writer): - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - .add_yaxis("series2", [5, 8, 7]) - .set_series_opts(stack="MY_STACK_NAME") - ) - c.render() - _, content = fake_writer.call_args[0] - stack_cnt = re.findall("MY_STACK_NAME", content) - assert_equal(3, len(stack_cnt)) - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_title_options(fake_writer): - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .set_global_opts( - title_opts=opts.TitleOpts( - title="This is title.", subtitle="This is subtitle." + c.render("my_chart.html") + file_name, content = fake_writer.call_args[0] + self.assertEqual("my_chart.html", file_name) + self.assertNotIn("null", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_default_remote_host(self, fake_writer): + c = Bar().add_xaxis(["A", "B", "C"]).add_yaxis("series0", [1, 2, 4]) + c.render() + self.assertEqual(c.js_host, "https://assets.pyecharts.org/assets/v5/") + _, content = fake_writer.call_args[0] + self.assertIn("https://assets.pyecharts.org/assets/v5/echarts.min.js", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_custom_remote_host(self, fake_writer): + c = ( + Bar(init_opts=opts.InitOpts(js_host="http://localhost:8000/assets/")) + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + ) + c.render() + self.assertEqual(c.js_host, "http://localhost:8000/assets/") + _, content = fake_writer.call_args[0] + self.assertIn("http://localhost:8000/assets/echarts.min.js", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_graphic(self, fake_writer): + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .set_global_opts( + graphic_opts=[ + opts.GraphicImage( + graphic_item=opts.GraphicItem( + id_="logo", + right=20, + top=20, + z=-10, + bounding="raw", + origin=[75, 75], + ), + graphic_imagestyle_opts=opts.GraphicImageStyleOpts( + image="http://echarts.baidu.com/images/favicon.png", + width=150, + height=150, + opacity=0.4, + graphic_basicstyle_opts=opts.GraphicBasicStyleOpts(), + ), + ) + ] ) ) - ) - c.render() - file_name, content = fake_writer.call_args[0] - assert_equal("render.html", file_name) - assert_in("This is title.", content) - assert_in("This is subtitle.", content) - assert_not_in("null", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_default_set_function(fake_writer): - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .set_global_opts() - .set_series_opts() - ) - - c.render("my_chart.html") - file_name, content = fake_writer.call_args[0] - assert_equal("my_chart.html", file_name) - assert_not_in("null", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_default_remote_host(fake_writer): - c = Bar().add_xaxis(["A", "B", "C"]).add_yaxis("series0", [1, 2, 4]) - c.render() - assert_equal(c.js_host, "https://assets.pyecharts.org/assets/v5/") - _, content = fake_writer.call_args[0] - assert_in("https://assets.pyecharts.org/assets/v5/echarts.min.js", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_custom_remote_host(fake_writer): - c = ( - Bar(init_opts=opts.InitOpts(js_host="http://localhost:8000/assets/")) - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - ) - c.render() - assert_equal(c.js_host, "http://localhost:8000/assets/") - _, content = fake_writer.call_args[0] - assert_in("http://localhost:8000/assets/echarts.min.js", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_graphic(fake_writer): - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .set_global_opts( - graphic_opts=[ - opts.GraphicImage( - graphic_item=opts.GraphicItem( - id_="logo", - right=20, - top=20, - z=-10, - bounding="raw", - origin=[75, 75], - ), - graphic_imagestyle_opts=opts.GraphicImageStyleOpts( - image="http://echarts.baidu.com/images/favicon.png", - width=150, - height=150, - opacity=0.4, - graphic_basicstyle_opts=opts.GraphicBasicStyleOpts(), - ), - ) - ] + c.render() + file_name, content = fake_writer.call_args[0] + self.assertEqual("render.html", file_name) + self.assertIn("graphic", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_graphic_v1(self, fake_writer): + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .set_global_opts( + graphic_opts=[ + opts.GraphicImage( + graphic_item=opts.GraphicItem( + id_="logo", + right=20, + top=20, + z=-10, + bounding="raw", + origin=[75, 75], + ), + graphic_imagestyle_opts=opts.GraphicImageStyleOpts( + image="http://echarts.baidu.com/images/favicon.png", + width=150, + height=150, + opacity=0.4, + ), + ) + ] + ) ) - ) - c.render() - file_name, content = fake_writer.call_args[0] - assert_equal("render.html", file_name) - assert_in("graphic", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_graphic_v1(fake_writer): - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .set_global_opts( - graphic_opts=[ - opts.GraphicImage( - graphic_item=opts.GraphicItem( - id_="logo", - right=20, - top=20, - z=-10, - bounding="raw", - origin=[75, 75], - ), - graphic_imagestyle_opts=opts.GraphicImageStyleOpts( - image="http://echarts.baidu.com/images/favicon.png", - width=150, - height=150, - opacity=0.4, - ), - ) - ] + c.render() + file_name, content = fake_writer.call_args[0] + self.assertEqual("render.html", file_name) + self.assertIn("graphic", content) + + def test_bar_render_nteract(self): + CurrentConfig.NOTEBOOK_TYPE = NotebookType.NTERACT + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) ) - ) - c.render() - file_name, content = fake_writer.call_args[0] - assert_equal("render.html", file_name) - assert_in("graphic", content) - - -def test_bar_render_nteract(): - CurrentConfig.NOTEBOOK_TYPE = NotebookType.NTERACT - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - ) - nteract_code = c.render_notebook() - assert_equal(isinstance(nteract_code, HTML), True) - - -def test_bar_render_zeppelin(): - CurrentConfig.NOTEBOOK_TYPE = NotebookType.ZEPPELIN - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - ) - # Block Console stdout - stdout_redirect.fp = StringIO() - temp_stdout, sys.stdout = sys.stdout, stdout_redirect - - # render - c.render_notebook() - sys.stdout = temp_stdout - - # Block Result - assert_in("%html", stdout_redirect.fp.getvalue()) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_with_brush(fake_writer): - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - .set_global_opts( - title_opts=opts.TitleOpts(title="Bar-Brush示例", subtitle="我是副标题"), - brush_opts=opts.BrushOpts(), + nteract_code = c.render_notebook() + self.assertEqual(isinstance(nteract_code, HTML), True) + + def test_bar_render_zeppelin(self): + CurrentConfig.NOTEBOOK_TYPE = NotebookType.ZEPPELIN + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("brush", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar_add_dataset(fake_writer): - c = ( - Bar() - .add_dataset( - source=[ - ["product", "2015", "2016", "2017"], - ["Matcha Latte", 43.3, 85.8, 93.7], - ["Milk Tea", 83.1, 73.4, 55.1], - ["Cheese Cocoa", 86.4, 65.2, 82.5], - ["Walnut Brownie", 72.4, 53.9, 39.1], - ] + # Block Console stdout + stdout_redirect.fp = StringIO() + temp_stdout, sys.stdout = sys.stdout, stdout_redirect + + # render + c.render_notebook() + sys.stdout = temp_stdout + + # Block Result + self.assertIn("%html", stdout_redirect.fp.getvalue()) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_with_brush(self, fake_writer): + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) + .set_global_opts( + title_opts=opts.TitleOpts(title="Bar-Brush示例", subtitle="我是副标题"), + brush_opts=opts.BrushOpts(), + ) ) - .add_yaxis(series_name="2015", y_axis=[]) - .add_yaxis(series_name="2016", y_axis=[]) - .add_yaxis(series_name="2017", y_axis=[]) - .set_global_opts( - title_opts=opts.TitleOpts(title="Dataset simple bar example"), - xaxis_opts=opts.AxisOpts(type_="category"), + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("brush", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar_add_dataset(self, fake_writer): + c = ( + Bar() + .add_dataset( + source=[ + ["product", "2015", "2016", "2017"], + ["Matcha Latte", 43.3, 85.8, 93.7], + ["Milk Tea", 83.1, 73.4, 55.1], + ["Cheese Cocoa", 86.4, 65.2, 82.5], + ["Walnut Brownie", 72.4, 53.9, 39.1], + ] + ) + .add_yaxis(series_name="2015", y_axis=[]) + .add_yaxis(series_name="2016", y_axis=[]) + .add_yaxis(series_name="2017", y_axis=[]) + .set_global_opts( + title_opts=opts.TitleOpts(title="Dataset simple bar example"), + xaxis_opts=opts.AxisOpts(type_="category"), + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("dataset", content) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("dataset", content) diff --git a/test/test_bar3d.py b/test/test_bar3d.py index cbf9e31ef..f84d9d6bf 100644 --- a/test/test_bar3d.py +++ b/test/test_bar3d.py @@ -1,84 +1,83 @@ import random +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Bar3D from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar3d_base(fake_writer): - data = [(i, j, random.randint(0, 12)) for i in range(6) for j in range(24)] - c = ( - Bar3D() - .add( - "", - [[d[1], d[0], d[2]] for d in data], - xaxis3d_opts=opts.Axis3DOpts(Faker.clock, type_="category"), - yaxis3d_opts=opts.Axis3DOpts(Faker.week_en, type_="category"), - zaxis3d_opts=opts.Axis3DOpts(type_="value"), - ) - .set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=20)) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - +class TestBar3DChart(unittest.TestCase): -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar3d_stack(fake_writer): - data1 = [(i, j, random.randint(0, 12)) for i in range(6) for j in range(24)] - data2 = [(i, j, random.randint(13, 20)) for i in range(6) for j in range(24)] - c = ( - Bar3D() - .add( - "1", - [[d[1], d[0], d[2]] for d in data1], - xaxis3d_opts=opts.Axis3DOpts(Faker.clock, type_="category"), - yaxis3d_opts=opts.Axis3DOpts(Faker.week_en, type_="category"), - zaxis3d_opts=opts.Axis3DOpts(type_="value"), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar3d_base(self, fake_writer): + data = [(i, j, random.randint(0, 12)) for i in range(6) for j in range(24)] + c = ( + Bar3D() + .add( + "", + [[d[1], d[0], d[2]] for d in data], + xaxis3d_opts=opts.Axis3DOpts(Faker.clock, type_="category"), + yaxis3d_opts=opts.Axis3DOpts(Faker.week_en, type_="category"), + zaxis3d_opts=opts.Axis3DOpts(type_="value"), + ) + .set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=20)) ) - .add( - "2", - [[d[1], d[0], d[2]] for d in data2], - xaxis3d_opts=opts.Axis3DOpts(Faker.clock, type_="category"), - yaxis3d_opts=opts.Axis3DOpts(Faker.week_en, type_="category"), - zaxis3d_opts=opts.Axis3DOpts(type_="value"), - ) - .set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=20)) - .set_series_opts(**{"stack": "stack"}) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("stack", content) - + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bar3d_with_globe(fake_writer): - c = ( - Bar3D(init_opts=opts.InitOpts(bg_color="#000")) - .add( - series_name="Population", - coordinate_system="globe", - data=[[120, 30, 1.11112222]], - itemstyle_opts=opts.ItemStyleOpts(color="orange"), - xaxis3d_opts=None, - yaxis3d_opts=None, - zaxis3d_opts=None, - grid3d_opts=None, + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar3d_stack(self, fake_writer): + data1 = [(i, j, random.randint(0, 12)) for i in range(6) for j in range(24)] + data2 = [(i, j, random.randint(13, 20)) for i in range(6) for j in range(24)] + c = ( + Bar3D() + .add( + "1", + [[d[1], d[0], d[2]] for d in data1], + xaxis3d_opts=opts.Axis3DOpts(Faker.clock, type_="category"), + yaxis3d_opts=opts.Axis3DOpts(Faker.week_en, type_="category"), + zaxis3d_opts=opts.Axis3DOpts(type_="value"), + ) + .add( + "2", + [[d[1], d[0], d[2]] for d in data2], + xaxis3d_opts=opts.Axis3DOpts(Faker.clock, type_="category"), + yaxis3d_opts=opts.Axis3DOpts(Faker.week_en, type_="category"), + zaxis3d_opts=opts.Axis3DOpts(type_="value"), + ) + .set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=20)) + .set_series_opts(**{"stack": "stack"}) ) - .add_globe( - base_texture="world.topo.bathy.200401.jpg", - height_texture="world.topo.bathy.200401.jpg", - shading="lambert", - environment="starfield.jpg", - light_opts=opts.Map3DLightOpts(main_intensity=2), - view_control_opts={"autoRotate": False} + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("stack", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bar3d_with_globe(self, fake_writer): + c = ( + Bar3D(init_opts=opts.InitOpts(bg_color="#000")) + .add( + series_name="Population", + coordinate_system="globe", + data=[[120, 30, 1.11112222]], + itemstyle_opts=opts.ItemStyleOpts(color="orange"), + xaxis3d_opts=None, + yaxis3d_opts=None, + zaxis3d_opts=None, + grid3d_opts=None, + ) + .add_globe( + base_texture="world.topo.bathy.200401.jpg", + height_texture="world.topo.bathy.200401.jpg", + shading="lambert", + environment="starfield.jpg", + light_opts=opts.Map3DLightOpts(main_intensity=2), + view_control_opts={"autoRotate": False}, + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("globe", content) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("globe", content) diff --git a/test/test_base.py b/test/test_base.py index af414f9f2..c2a5c7a3a 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -1,8 +1,7 @@ +import unittest from datetime import datetime from unittest.mock import patch -from nose.tools import assert_equal, assert_in, assert_not_in - from pyecharts.charts import Bar from pyecharts.options import InitOpts, RenderOpts from pyecharts.globals import CurrentConfig @@ -10,80 +9,78 @@ from pyecharts.options.global_options import AnimationOpts -def test_base_add_functions(): - c = Base() - c.add_js_funcs("console.log('hello')", "console.log('hello')") - assert_equal(1, len(c.js_functions.items)) - assert_equal(["console.log('hello')"], c.js_functions.items) - - -def test_base_init_funcs(): - c0 = Base({"width": "100px", "height": "200px"}) - assert_equal(c0.width, "100px") - assert_equal(c0.height, "200px") - - c1 = Base(dict(width="110px", height="210px")) - assert_equal(c1.width, "110px") - assert_equal(c1.height, "210px") - assert_not_in(c1.js_host, ["", None]) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_render(fake_writer): - my_render_content = "my_render_content" - bar = Bar() - bar.add_xaxis(["1"]).add_yaxis("", [1]).render(my_render_content=my_render_content) - assert "test ok" == "test ok" - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_render_js_host_none(fake_writer): - my_render_content = "my_render_content" - bar = Bar() - bar.add_xaxis(["1"]).add_yaxis("", [1]) - # Hack to test - bar.js_host = None - # Render - bar.render(my_render_content=my_render_content) - assert_equal(bar.js_host, CurrentConfig.ONLINE_HOST) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_render_embed_js(_): - c = Base(render_opts=RenderOpts(is_embed_js=True)) - # Embedded JavaScript - content = c.render_embed() - assert_not_in(CurrentConfig.ONLINE_HOST, content, "Embedding JavaScript fails") - # No embedded JavaScript - c.render_options.update(embed_js=False) - content = c.render_embed() - assert_in( - CurrentConfig.ONLINE_HOST, content, "Embedded JavaScript cannot be closed" - ) - - -def test_base_render_options(): - c0 = Base(render_opts=RenderOpts(is_embed_js=True)) - assert_equal(c0.render_options.get("embed_js"), True) - - -def test_base_iso_format(): - mock_time_str = "2022-04-14 14:42:00" - mock_time = datetime.strptime(mock_time_str, "%Y-%m-%d %H:%M:%S") - assert default(mock_time) == "2022-04-14T14:42:00" - - -def test_base_animation_option(): - c0 = Base(init_opts=InitOpts(animation_opts=AnimationOpts(animation=False))) - assert_equal(c0.options.get("animation"), False) - - c1 = Base({"animationOpts": {"animation": False}}) - assert_equal(c1.options.get("animation"), False) - - -def test_base_chart_id(): - c0 = Base(init_opts=InitOpts(chart_id="1234567")) - assert_equal(c0.chart_id, "1234567") - - c1 = Base(init_opts=InitOpts(chart_id="1234567")) - assert_equal(c1.get_chart_id(), "1234567") +class TestBaseClass(unittest.TestCase): + + def test_base_add_functions(self): + c = Base() + c.add_js_funcs("console.log('hello')", "console.log('hello')") + self.assertEqual(1, len(c.js_functions.items)) + self.assertEqual(["console.log('hello')"], c.js_functions.items) + + def test_base_init_funcs(self): + c0 = Base({"width": "100px", "height": "200px"}) + self.assertEqual(c0.width, "100px") + self.assertEqual(c0.height, "200px") + + c1 = Base(dict(width="110px", height="210px")) + self.assertEqual(c1.width, "110px") + self.assertEqual(c1.height, "210px") + self.assertNotIn(c1.js_host, ["", None]) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_render(self, fake_writer): + my_render_content = "my_render_content" + bar = Bar() + bar.add_xaxis(["1"]).add_yaxis("", [1]).render( + my_render_content=my_render_content + ) + assert "test ok" == "test ok" + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_render_js_host_none(self, fake_writer): + my_render_content = "my_render_content" + bar = Bar() + bar.add_xaxis(["1"]).add_yaxis("", [1]) + # Hack to test + bar.js_host = None + # Render + bar.render(my_render_content=my_render_content) + self.assertEqual(bar.js_host, CurrentConfig.ONLINE_HOST) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_render_embed_js(self, _): + c = Base(render_opts=RenderOpts(is_embed_js=True)) + # Embedded JavaScript + content = c.render_embed() + self.assertNotIn( + CurrentConfig.ONLINE_HOST, content, "Embedding JavaScript fails" + ) + # No embedded JavaScript + c.render_options.update(embed_js=False) + content = c.render_embed() + self.assertIn( + CurrentConfig.ONLINE_HOST, content, "Embedded JavaScript cannot be closed" + ) + + def test_base_render_options(self): + c0 = Base(render_opts=RenderOpts(is_embed_js=True)) + self.assertEqual(c0.render_options.get("embed_js"), True) + + def test_base_iso_format(self): + mock_time_str = "2022-04-14 14:42:00" + mock_time = datetime.strptime(mock_time_str, "%Y-%m-%d %H:%M:%S") + assert default(mock_time) == "2022-04-14T14:42:00" + + def test_base_animation_option(self): + c0 = Base(init_opts=InitOpts(animation_opts=AnimationOpts(animation=False))) + self.assertEqual(c0.options.get("animation"), False) + + c1 = Base({"animationOpts": {"animation": False}}) + self.assertEqual(c1.options.get("animation"), False) + + def test_base_chart_id(self): + c0 = Base(init_opts=InitOpts(chart_id="1234567")) + self.assertEqual(c0.chart_id, "1234567") + + c1 = Base(init_opts=InitOpts(chart_id="1234567")) + self.assertEqual(c1.get_chart_id(), "1234567") diff --git a/test/test_bmap.py b/test/test_bmap.py index 50f90264f..d12c5e0de 100644 --- a/test/test_bmap.py +++ b/test/test_bmap.py @@ -1,7 +1,6 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_in - from pyecharts import options as opts from pyecharts.charts import BMap from pyecharts.globals import BMapType, ChartType @@ -13,130 +12,127 @@ TEST_VALUE = [1] -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bmap(fake_writer): - bmap = ( - BMap() - .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) - .add_coordinate("London", -0.118092, 51.509865) - .add( - "bmap", - [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], - label_opts=opts.LabelOpts(formatter="{b}"), - ) - ) - bmap.render() - content = fake_writer.call_args[0][1] - assert_in(f'src="{BAIDU_MAP_API_PREFIX}&ak={FAKE_API_KEY}"', content) - assert_in('"coordinateSystem": "bmap"', content, "non bmap found") - +class TestBMapChart(unittest.TestCase): -def test_bmap_heatmap(): - bmap = ( - BMap() - .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) - .add_coordinate("London", -0.118092, 51.509865) - .add( - "bmap", - [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], - label_opts=opts.LabelOpts(formatter="{b}"), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bmap(self, fake_writer): + bmap = ( + BMap() + .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "bmap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) ) - ) - data = bmap.options.get("series")[0]["data"] - for item in data: - assert_in("name", item) - assert_in("value", item) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bmap_effect_trail_length(fake_writer): - bmap = ( - BMap() - .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) - .add_coordinate("London", -0.118092, 51.509865) - .add( - "bmap", - [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], - type_=ChartType.LINES, - effect_opts=opts.EffectOpts(trail_length=0.5), - label_opts=opts.LabelOpts(formatter="{b}"), + bmap.render() + content = fake_writer.call_args[0][1] + self.assertIn(f'src="{BAIDU_MAP_API_PREFIX}&ak={FAKE_API_KEY}"', content) + self.assertIn('"coordinateSystem": "bmap"', content, "non bmap found") + + def test_bmap_heatmap(self): + bmap = ( + BMap() + .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "bmap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) ) - ) - bmap.render("render.html") - content = fake_writer.call_args[0][1] - assert_in('"trailLength": 0.5', content, "trainLength parameter is error") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bmap_polyline_and_large(fake_writer): - bmap = ( - BMap() - .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) - .add_coordinate("London", -0.118092, 51.509865) - .add( - "bmap", - [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], - is_polyline=True, - is_large=True, - type_=ChartType.LINES, - label_opts=opts.LabelOpts(formatter="{b}"), + data = bmap.options.get("series")[0]["data"] + for item in data: + self.assertIn("name", item) + self.assertIn("value", item) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bmap_effect_trail_length(self, fake_writer): + bmap = ( + BMap() + .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "bmap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + type_=ChartType.LINES, + effect_opts=opts.EffectOpts(trail_length=0.5), + label_opts=opts.LabelOpts(formatter="{b}"), + ) ) - ) - bmap.render() - content = fake_writer.call_args[0][1] - assert_in('"polyline": true', content, "polyline parameter is error") - assert_in('"large": true', content, "large parameter is error") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bmap_map_control_panel(fake_writer): - bmap = ( - BMap() - .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) - .add_coordinate("London", -0.118092, 51.509865) - .add( - "bmap", - [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], - type_=ChartType.LINES, - label_opts=opts.LabelOpts(formatter="{b}"), + bmap.render("render.html") + content = fake_writer.call_args[0][1] + self.assertIn('"trailLength": 0.5', content, "trainLength parameter is error") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bmap_polyline_and_large(self, fake_writer): + bmap = ( + BMap() + .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "bmap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + is_polyline=True, + is_large=True, + type_=ChartType.LINES, + label_opts=opts.LabelOpts(formatter="{b}"), + ) ) - .add_control_panel( - copyright_control_opts=opts.BMapCopyrightTypeOpts(position=3), - maptype_control_opts=opts.BMapTypeControlOpts( - type_=BMapType.MAPTYPE_CONTROL_DROPDOWN - ), - scale_control_opts=opts.BMapScaleControlOpts(), - overview_map_opts=opts.BMapOverviewMapControlOpts(is_open=True), - navigation_control_opts=opts.BMapNavigationControlOpts(), - geo_location_control_opts=opts.BMapGeoLocationControlOpts(), + bmap.render() + content = fake_writer.call_args[0][1] + self.assertIn('"polyline": true', content, "polyline parameter is error") + self.assertIn('"large": true', content, "large parameter is error") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bmap_map_control_panel(self, fake_writer): + bmap = ( + BMap() + .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "bmap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + type_=ChartType.LINES, + label_opts=opts.LabelOpts(formatter="{b}"), + ) + .add_control_panel( + copyright_control_opts=opts.BMapCopyrightTypeOpts(position=3), + maptype_control_opts=opts.BMapTypeControlOpts( + type_=BMapType.MAPTYPE_CONTROL_DROPDOWN + ), + scale_control_opts=opts.BMapScaleControlOpts(), + overview_map_opts=opts.BMapOverviewMapControlOpts(is_open=True), + navigation_control_opts=opts.BMapNavigationControlOpts(), + geo_location_control_opts=opts.BMapGeoLocationControlOpts(), + ) ) - ) - bmap.render() - content = fake_writer.call_args[0][1] - assert_in("new BMap.CopyrightControl", content) - assert_in("new BMap.MapTypeControl", content) - assert_in("new BMap.ScaleControl", content) - assert_in("new BMap.OverviewMapControl", content) - assert_in("new BMap.NavigationControl", content) - assert_in("new BMap.GeolocationControl", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_bmap_progressive_options(fake_writer): - bmap = ( - BMap() - .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) - .add_coordinate("London", -0.118092, 51.509865) - .add( - "bmap", - type_="lines", - data_pair=[list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], - label_opts=opts.LabelOpts(formatter="{b}"), - progressive=200, - progressive_threshold=500, + bmap.render() + content = fake_writer.call_args[0][1] + self.assertIn("new BMap.CopyrightControl", content) + self.assertIn("new BMap.MapTypeControl", content) + self.assertIn("new BMap.ScaleControl", content) + self.assertIn("new BMap.OverviewMapControl", content) + self.assertIn("new BMap.NavigationControl", content) + self.assertIn("new BMap.GeolocationControl", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_bmap_progressive_options(self, fake_writer): + bmap = ( + BMap() + .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "bmap", + type_="lines", + data_pair=[list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + progressive=200, + progressive_threshold=500, + ) ) - ) - bmap.render() - content = fake_writer.call_args[0][1] - assert_in("progressive", content) - assert_in("progressiveThreshold", content) + bmap.render() + content = fake_writer.call_args[0][1] + self.assertIn("progressive", content) + self.assertIn("progressiveThreshold", content) diff --git a/test/test_boxplot.py b/test/test_boxplot.py index 71456abda..84e8a4637 100644 --- a/test/test_boxplot.py +++ b/test/test_boxplot.py @@ -1,92 +1,94 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Boxplot -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_boxplot_base(fake_writer): - v1 = [ - [850, 740, 900, 1070, 930, 850, 950, 980, 980, 880, 1000, 980], - [960, 940, 960, 940, 880, 800, 850, 880, 900, 840, 830, 790], - ] - v2 = [ - [890, 810, 810, 820, 800, 770, 760, 740, 750, 760, 910, 920], - [890, 840, 780, 810, 760, 810, 790, 810, 820, 850, 870, 870], - ] - c = Boxplot() - c.add_xaxis(["expr1", "expr2"]).add_yaxis( - "A", c.prepare_data(v1), box_width=40 - ).add_yaxis("B", c.prepare_data(v2)) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("boxWidth", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_boxplot_base_v1(fake_writer): - v1 = [ - [1000, 1000, 1000], - [200, 200, 200], - ] - v2 = [ - [1000, 1000, 1000], - [200, 200, 200], - ] - c = Boxplot() - c.add_xaxis(["expr1", "expr2"]).add_yaxis( - "A", c.prepare_data(v1), box_width=40 - ).add_yaxis("B", c.prepare_data(v2)) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("boxWidth", content) +class TestBoxplotChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_boxplot_base(self, fake_writer): + v1 = [ + [850, 740, 900, 1070, 930, 850, 950, 980, 980, 880, 1000, 980], + [960, 940, 960, 940, 880, 800, 850, 880, 900, 840, 830, 790], + ] + v2 = [ + [890, 810, 810, 820, 800, 770, 760, 740, 750, 760, 910, 920], + [890, 840, 780, 810, 760, 810, 790, 810, 820, 850, 870, 870], + ] + c = Boxplot() + c.add_xaxis(["expr1", "expr2"]).add_yaxis( + "A", c.prepare_data(v1), box_width=40 + ).add_yaxis("B", c.prepare_data(v2)) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("boxWidth", content) -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_boxplot_base_v2(fake_writer): - v1 = [ - None, - [200, 200, 200], - ] - v2 = [ - [1000, None, 1000], - [200, 200, 200], - ] - c = Boxplot() - c.add_xaxis(["expr1", "expr2"]).add_yaxis( - "A", c.prepare_data(v1), box_width=40 - ).add_yaxis("B", c.prepare_data(v2)) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("boxWidth", content) + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_boxplot_base_v1(self, fake_writer): + v1 = [ + [1000, 1000, 1000], + [200, 200, 200], + ] + v2 = [ + [1000, 1000, 1000], + [200, 200, 200], + ] + c = Boxplot() + c.add_xaxis(["expr1", "expr2"]).add_yaxis( + "A", c.prepare_data(v1), box_width=40 + ).add_yaxis("B", c.prepare_data(v2)) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("boxWidth", content) + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_boxplot_base_v2(self, fake_writer): + v1 = [ + None, + [200, 200, 200], + ] + v2 = [ + [1000, None, 1000], + [200, 200, 200], + ] + c = Boxplot() + c.add_xaxis(["expr1", "expr2"]).add_yaxis( + "A", c.prepare_data(v1), box_width=40 + ).add_yaxis("B", c.prepare_data(v2)) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("boxWidth", content) -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_boxplot_item_base(fake_writer): - x_axis = ["expr1", "expr2"] - v1 = [ - [850, 740, 900, 1070, 930, 850, 950, 980, 980, 880, 1000, 980], - [960, 940, 960, 940, 880, 800, 850, 880, 900, 840, 830, 790], - ] - v2 = [ - [890, 810, 810, 820, 800, 770, 760, 740, 750, 760, 910, 920], - [890, 840, 780, 810, 760, 810, 790, 810, 820, 850, 870, 870], - ] - c = Boxplot() + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_boxplot_item_base(self, fake_writer): + x_axis = ["expr1", "expr2"] + v1 = [ + [850, 740, 900, 1070, 930, 850, 950, 980, 980, 880, 1000, 980], + [960, 940, 960, 940, 880, 800, 850, 880, 900, 840, 830, 790], + ] + v2 = [ + [890, 810, 810, 820, 800, 770, 760, 740, 750, 760, 910, 920], + [890, 840, 780, 810, 760, 810, 790, 810, 820, 850, 870, 870], + ] + c = Boxplot() - series_a = [opts.BoxplotItem(name=x_axis[0], value=d) for d in c.prepare_data(v1)] - series_b = [opts.BoxplotItem(name=x_axis[1], value=d) for d in c.prepare_data(v2)] + series_a = [ + opts.BoxplotItem(name=x_axis[0], value=d) for d in c.prepare_data(v1) + ] + series_b = [ + opts.BoxplotItem(name=x_axis[1], value=d) for d in c.prepare_data(v2) + ] - c.add_xaxis(xaxis_data=x_axis).add_yaxis("A", series_a).add_yaxis("B", series_b) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.add_xaxis(xaxis_data=x_axis).add_yaxis("A", series_a).add_yaxis("B", series_b) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_calendar.py b/test/test_calendar.py index d8b6b2415..a8064f51e 100644 --- a/test/test_calendar.py +++ b/test/test_calendar.py @@ -1,26 +1,61 @@ import datetime import random +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Calendar -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_calendar_base(fake_writer): - begin = datetime.date(2017, 1, 1) - end = datetime.date(2017, 12, 31) - data = [ - [str(begin + datetime.timedelta(days=i)), random.randint(1000, 25000)] - for i in range((end - begin).days + 1) - ] +class TestCalendarChart(unittest.TestCase): + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_calendar_base(self, fake_writer): + begin = datetime.date(2017, 1, 1) + end = datetime.date(2017, 12, 31) + data = [ + [str(begin + datetime.timedelta(days=i)), random.randint(1000, 25000)] + for i in range((end - begin).days + 1) + ] + + c = ( + Calendar() + .add("", data, calendar_opts=opts.CalendarOpts(range_="2017")) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts( + max_=20000, + min_=500, + orient="horizontal", + is_piecewise=True, + pos_top="230px", + pos_left="100px", + ) + ) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("visualMap", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_calendar_setting(self, fake_writer): + begin = datetime.date(2017, 1, 1) + end = datetime.date(2017, 12, 31) + data = [ + [str(begin + datetime.timedelta(days=i)), random.randint(1000, 25000)] + for i in range((end - begin).days + 1) + ] - c = ( - Calendar() - .add("", data, calendar_opts=opts.CalendarOpts(range_="2017")) - .set_global_opts( + c = Calendar().add( + "", + data, + calendar_opts=opts.CalendarOpts( + range_="2017", + cell_size=15, + daylabel_opts=opts.CalendarDayLabelOpts(name_map="cn"), + monthlabel_opts=opts.CalendarMonthLabelOpts(name_map="cn"), + ), visualmap_opts=opts.VisualMapOpts( max_=20000, min_=500, @@ -28,46 +63,11 @@ def test_calendar_base(fake_writer): is_piecewise=True, pos_top="230px", pos_left="100px", - ) + ), ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("visualMap", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_calendar_setting(fake_writer): - begin = datetime.date(2017, 1, 1) - end = datetime.date(2017, 12, 31) - data = [ - [str(begin + datetime.timedelta(days=i)), random.randint(1000, 25000)] - for i in range((end - begin).days + 1) - ] - - c = Calendar().add( - "", - data, - calendar_opts=opts.CalendarOpts( - range_="2017", - cell_size=15, - daylabel_opts=opts.CalendarDayLabelOpts(name_map="cn"), - monthlabel_opts=opts.CalendarMonthLabelOpts(name_map="cn"), - ), - visualmap_opts=opts.VisualMapOpts( - max_=20000, - min_=500, - orient="horizontal", - is_piecewise=True, - pos_top="230px", - pos_left="100px", - ), - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("cellSize", content) - assert_in("dayLabel", content) - assert_in("monthLabel", content) - assert_in("visualMap", content) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("cellSize", content) + self.assertIn("dayLabel", content) + self.assertIn("monthLabel", content) + self.assertIn("visualMap", content) diff --git a/test/test_chart.py b/test/test_chart.py index 1c4764ba7..e40b42bce 100644 --- a/test/test_chart.py +++ b/test/test_chart.py @@ -1,270 +1,259 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Line, Bar -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_dark_mode(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_dark_mode() - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("darkMode", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_line_style_opts(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_series_opts(linestyle_opts=opts.LineStyleOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("lineStyle", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_split_line_style_opts(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_series_opts(splitline_opts=opts.SplitLineOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("splitLine", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_area_style_opts(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_series_opts(areastyle_opts=opts.AreaStyleOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("areaStyle", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_axis_line_opts(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_series_opts(axisline_opts=opts.AxisLineOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("axisLine", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_mark_point_opts(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_series_opts(markpoint_opts=opts.MarkPointOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("markPoint", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_mark_line_opts(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_series_opts(markline_opts=opts.MarkLineOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("markLine", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_mark_area_opts(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_series_opts(markarea_opts=opts.MarkAreaOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("markArea", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_tooltip_opts(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_series_opts(tooltip_opts=opts.TooltipOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("tooltip", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_item_style_opts(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - .set_series_opts(itemstyle_opts=opts.ItemStyleOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("itemStyle", content) +class TestChartClass(unittest.TestCase): - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_append_color(fake_writer): - x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] - y_data1 = [140, 232, 101, 264, 90, 340, 250] - y_data2 = [120, 282, 111, 234, 220, 340, 310] - - c = ( - Line() - .add_xaxis(xaxis_data=x_data) - .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") - .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") - ) - c.render() - _, content = fake_writer.call_args[0] - # Old Version (Before 2.0) - # default_colors = ( - # "#c23531 #2f4554 #61a0a8 #d48265 #749f83 #ca8622 #bda29a #6e7074 " - # "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa " - # "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597" - # ).split() - - # New Version - default_colors = ( - "#5470c6 #91cc75 #fac858 #ee6666 #73c0de #3ba272 #fc8452 #9a60b4 " "#ea7ccc" - ).split() - expected_result = ["#80FFA5", "#00DDFF", *default_colors] - assert_equal(c.colors, expected_result) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_add_dataset(fake_writer): - c = ( - Bar() - .add_dataset( - source=[ - ["product", "2015", "2016", "2017"], - ["Matcha Latte", 43.3, 85.8, 93.7], - ["Milk Tea", 83.1, 73.4, 55.1], - ["Cheese Cocoa", 86.4, 65.2, 82.5], - ["Walnut Brownie", 72.4, 53.9, 39.1], - ] + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_dark_mode(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_dark_mode() ) - .add_dataset(from_dataset_index=1, from_transform_result=1) - .add_yaxis(series_name="2015", y_axis=[]) - .add_yaxis(series_name="2016", y_axis=[]) - .add_yaxis(series_name="2017", y_axis=[]) - .set_global_opts( - title_opts=opts.TitleOpts(title="Dataset simple bar example"), - xaxis_opts=opts.AxisOpts(type_="category"), + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("darkMode", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_line_style_opts(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(linestyle_opts=opts.LineStyleOpts()) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("dataset", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_chart_extend_axis(fake_writer): - v1 = [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3] - v2 = [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3] - v3 = [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2] - - bar = ( - Bar() - .add_xaxis([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - .add_yaxis("蒸发量", v1) - .add_yaxis("降水量", v2) - .extend_axis( - xaxis=opts.AxisOpts(), - yaxis=opts.AxisOpts( - axislabel_opts=opts.LabelOpts(formatter="{value} °C"), interval=5 - ), + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("lineStyle", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_split_line_style_opts(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(splitline_opts=opts.SplitLineOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("splitLine", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_area_style_opts(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(areastyle_opts=opts.AreaStyleOpts()) ) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts( - title_opts=opts.TitleOpts(title="Overlap-bar+line"), - yaxis_opts=opts.AxisOpts( - axislabel_opts=opts.LabelOpts(formatter="{value} ml") - ), + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("areaStyle", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_axis_line_opts(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(axisline_opts=opts.AxisLineOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("axisLine", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_mark_point_opts(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(markpoint_opts=opts.MarkPointOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("markPoint", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_mark_line_opts(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(markline_opts=opts.MarkLineOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("markLine", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_mark_area_opts(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(markarea_opts=opts.MarkAreaOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("markArea", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_tooltip_opts(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(tooltip_opts=opts.TooltipOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("tooltip", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_item_style_opts(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + .set_series_opts(itemstyle_opts=opts.ItemStyleOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("itemStyle", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_append_color(self, fake_writer): + x_data = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + y_data1 = [140, 232, 101, 264, 90, 340, 250] + y_data2 = [120, 282, 111, 234, 220, 340, 310] + + c = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis(series_name="品类 1", y_axis=y_data1, color="#80FFA5") + .add_yaxis(series_name="品类 2", y_axis=y_data2, color="#00DDFF") + ) + c.render() + _, content = fake_writer.call_args[0] + # Old Version (Before 2.0) + # default_colors = ( + # "#c23531 #2f4554 #61a0a8 #d48265 #749f83 #ca8622 #bda29a #6e7074 " + # "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa " + # "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597" + # ).split() + + # New Version + default_colors = ( + "#5470c6 #91cc75 #fac858 #ee6666 #73c0de #3ba272 #fc8452 #9a60b4 " "#ea7ccc" + ).split() + expected_result = ["#80FFA5", "#00DDFF", *default_colors] + self.assertEqual(c.colors, expected_result) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_add_dataset(self, fake_writer): + c = ( + Bar() + .add_dataset( + source=[ + ["product", "2015", "2016", "2017"], + ["Matcha Latte", 43.3, 85.8, 93.7], + ["Milk Tea", 83.1, 73.4, 55.1], + ["Cheese Cocoa", 86.4, 65.2, 82.5], + ["Walnut Brownie", 72.4, 53.9, 39.1], + ] + ) + .add_dataset(from_dataset_index=1, from_transform_result=1) + .add_yaxis(series_name="2015", y_axis=[]) + .add_yaxis(series_name="2016", y_axis=[]) + .add_yaxis(series_name="2017", y_axis=[]) + .set_global_opts( + title_opts=opts.TitleOpts(title="Dataset simple bar example"), + xaxis_opts=opts.AxisOpts(type_="category"), + ) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("dataset", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_extend_axis(self, fake_writer): + v1 = [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3] + v2 = [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3] + v3 = [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2] + + bar = ( + Bar() + .add_xaxis([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + .add_yaxis("蒸发量", v1) + .add_yaxis("降水量", v2) + .extend_axis( + xaxis=opts.AxisOpts(), + yaxis=opts.AxisOpts( + axislabel_opts=opts.LabelOpts(formatter="{value} °C"), interval=5 + ), + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts( + title_opts=opts.TitleOpts(title="Overlap-bar+line"), + yaxis_opts=opts.AxisOpts( + axislabel_opts=opts.LabelOpts(formatter="{value} ml") + ), + ) ) - ) - line = ( - Line() - .add_xaxis([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - .add_yaxis("平均温度", v3, yaxis_index=0, z_level=999) - .set_global_opts(xaxis_opts=opts.AxisOpts(type_="value")) - ) - bar.overlap(line) - bar.render() - _, content = fake_writer.call_args[0] - assert_in("xAxis", content) + line = ( + Line() + .add_xaxis([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + .add_yaxis("平均温度", v3, yaxis_index=0, z_level=999) + .set_global_opts(xaxis_opts=opts.AxisOpts(type_="value")) + ) + bar.overlap(line) + bar.render() + _, content = fake_writer.call_args[0] + self.assertIn("xAxis", content) diff --git a/test/test_chart_options.py b/test/test_chart_options.py index 913e86f39..8cc273bed 100644 --- a/test/test_chart_options.py +++ b/test/test_chart_options.py @@ -1,4 +1,4 @@ -from nose.tools import assert_equal +import unittest from pyecharts.commons.utils import remove_key_with_none_value from pyecharts.options.charts_options import ( @@ -19,145 +19,132 @@ ) -def test_bar_background_style_options_remove_none(): - option = BarBackgroundStyleOpts() - expected = { - "color": "rgba(180, 180, 180, 0.2)", - "borderColor": "#000", - "borderWidth": 0, - "borderType": "solid", - "borderRadius": 0, - "shadowOffsetX": 0, - "shadowOffsetY": 0, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_globe_layers_options_remove_none(): - option = GlobeLayersOpts() - expected = { - "show": True, - "type": "overlay", - "blendTo": "albedo", - "intensity": 1, - "shading": "lambert", - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_theme_river_item_remove_none(): - item = ThemeRiverItem() - expected = {} - assert_equal(expected, remove_key_with_none_value(item.opts)) - - -def test_tree_river_item_remove_none(): - item = TreeItem() - expected = {} - assert_equal(expected, remove_key_with_none_value(item.opts)) - - -def test_timeline_check_pointer_style_options_remove_none(): - option = TimelineCheckPointerStyle(symbol_offset=None) - expected = { - "symbol": "circle", - "symbolSize": 13, - "symbolKeepAspect": False, - "symbolOffset": [0, 0], - "color": "#c23531", - "borderWidth": 5, - "borderColor": "rgba(194,53,49,0.5)", - "animation": True, - "animationDuration": 300, - "animationEasing": "quinticInOut", - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_timeline_control_style_options_remove_none(): - option = TimelineControlStyle() - expected = { - "show": True, - "showPlayBtn": True, - "showPrevBtn": True, - "showNextBtn": True, - "itemSize": 22, - "itemGap": 12, - "position": "left", - "color": "#304654", - "borderColor": "#304654", - "borderWidth": 1, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_graph_category_item_remove_none(): - item = GraphCategory() - expected = {} - assert_equal(expected, remove_key_with_none_value(item.opts)) - - -def test_tree_map_item_style_options_remove_none(): - option = TreeMapItemStyleOpts() - expected = { - "gapWidth": 0, - "borderWidth": 0, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_graphic_image_style_opts_remove_none(): - option = GraphicImageStyleOpts(graphic_basicstyle_opts={"fill": "#000"}) - expected = { - "x": 0, - "y": 0, - "width": 0, - "height": 0, - "opacity": 1, - "fill": "#000", - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_graphic_text_style_opts_remove_none(): - option = GraphicTextStyleOpts(graphic_basicstyle_opts={"fill": "#000"}) - expected = { - "x": 0, - "y": 0, - "fontSize": 0, - "textAlign": "left", - "fill": "#000", - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_geo_region_opts_remove_none(): - option = GeoRegionsOpts() - expected = { - "blur": {}, - "emphasis": {}, - "select": {}, - "selected": False, - "silent": False, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_sunburst_label_line_opts_remove_none(): - option = SunburstLabelLineOpts() - expected = { - "smooth": False - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_sunburst_label_layout_opts_remove_none(): - option = SunburstLabelLayoutOpts() - expected = {} - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_sunburst_level_opts_remove_none(): - option = SunburstLevelOpts() - expected = {} - assert_equal(expected, remove_key_with_none_value(option.opts)) +class TestChartOptions(unittest.TestCase): + + def test_bar_background_style_options_remove_none(self): + option = BarBackgroundStyleOpts() + expected = { + "color": "rgba(180, 180, 180, 0.2)", + "borderColor": "#000", + "borderWidth": 0, + "borderType": "solid", + "borderRadius": 0, + "shadowOffsetX": 0, + "shadowOffsetY": 0, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_globe_layers_options_remove_none(self): + option = GlobeLayersOpts() + expected = { + "show": True, + "type": "overlay", + "blendTo": "albedo", + "intensity": 1, + "shading": "lambert", + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_theme_river_item_remove_none(self): + item = ThemeRiverItem() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(item.opts)) + + def test_tree_river_item_remove_none(self): + item = TreeItem() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(item.opts)) + + def test_timeline_check_pointer_style_options_remove_none(self): + option = TimelineCheckPointerStyle(symbol_offset=None) + expected = { + "symbol": "circle", + "symbolSize": 13, + "symbolKeepAspect": False, + "symbolOffset": [0, 0], + "color": "#c23531", + "borderWidth": 5, + "borderColor": "rgba(194,53,49,0.5)", + "animation": True, + "animationDuration": 300, + "animationEasing": "quinticInOut", + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_timeline_control_style_options_remove_none(self): + option = TimelineControlStyle() + expected = { + "show": True, + "showPlayBtn": True, + "showPrevBtn": True, + "showNextBtn": True, + "itemSize": 22, + "itemGap": 12, + "position": "left", + "color": "#304654", + "borderColor": "#304654", + "borderWidth": 1, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_graph_category_item_remove_none(self): + item = GraphCategory() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(item.opts)) + + def test_tree_map_item_style_options_remove_none(self): + option = TreeMapItemStyleOpts() + expected = { + "gapWidth": 0, + "borderWidth": 0, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_graphic_image_style_opts_remove_none(self): + option = GraphicImageStyleOpts(graphic_basicstyle_opts={"fill": "#000"}) + expected = { + "x": 0, + "y": 0, + "width": 0, + "height": 0, + "opacity": 1, + "fill": "#000", + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_graphic_text_style_opts_remove_none(self): + option = GraphicTextStyleOpts(graphic_basicstyle_opts={"fill": "#000"}) + expected = { + "x": 0, + "y": 0, + "fontSize": 0, + "textAlign": "left", + "fill": "#000", + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_geo_region_opts_remove_none(self): + option = GeoRegionsOpts() + expected = { + "blur": {}, + "emphasis": {}, + "select": {}, + "selected": False, + "silent": False, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_sunburst_label_line_opts_remove_none(self): + option = SunburstLabelLineOpts() + expected = {"smooth": False} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_sunburst_label_layout_opts_remove_none(self): + option = SunburstLabelLayoutOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_sunburst_level_opts_remove_none(self): + option = SunburstLevelOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_custom.py b/test/test_custom.py index bc82bdec3..b3e5915cd 100644 --- a/test/test_custom.py +++ b/test/test_custom.py @@ -1,45 +1,44 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_greater, assert_in - - from pyecharts.charts import Custom from pyecharts.commons.utils import JsCode -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_custom_base(fake_writer): - c = Custom().add( - series_name="", - render_item=JsCode( - """ - function (params, api) { - var categoryIndex = api.value(0); - var start = api.coord([api.value(1), categoryIndex]); - var end = api.coord([api.value(2), categoryIndex]); - var height = api.size([0, 1])[1] * 0.6; - var rectShape = echarts.graphic.clipRectByRect({ - x: start[0], - y: start[1] - height / 2, - width: end[0] - start[0], - height: height - }, { - x: params.coordSys.x, - y: params.coordSys.y, - width: params.coordSys.width, - height: params.coordSys.height - }); - return rectShape && { - type: 'rect', - shape: rectShape, - style: api.style() - }; - } - """ - ), - data=None, - ) - c.render() - _, content = fake_writer.call_args[0] - assert_greater(len(content), 2000) - assert_in("renderItem", content) +class TestCustom(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_custom_base(self, fake_writer): + c = Custom().add( + series_name="", + render_item=JsCode( + """ + function (params, api) { + var categoryIndex = api.value(0); + var start = api.coord([api.value(1), categoryIndex]); + var end = api.coord([api.value(2), categoryIndex]); + var height = api.size([0, 1])[1] * 0.6; + var rectShape = echarts.graphic.clipRectByRect({ + x: start[0], + y: start[1] - height / 2, + width: end[0] - start[0], + height: height + }, { + x: params.coordSys.x, + y: params.coordSys.y, + width: params.coordSys.width, + height: params.coordSys.height + }); + return rectShape && { + type: 'rect', + shape: rectShape, + style: api.style() + }; + } + """ + ), + data=None, + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertGreater(len(content), 2000) + self.assertIn("renderItem", content) diff --git a/test/test_datasets.py b/test/test_datasets.py index c304b65af..ca65070cd 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -1,8 +1,7 @@ import os +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in, raises - from pyecharts.datasets import ( EXTRA, FuzzyDict, @@ -12,67 +11,64 @@ ) -@patch("pyecharts.datasets.urllib.request.urlopen") -def test_register_url(fake): - current_path = os.path.dirname(__file__) - fake_registry = os.path.join(current_path, "fixtures", "registry.json") - file_name = ["shape-with-internal-borders/an1_hui1_an1_qing4", "js"] - with open(fake_registry, encoding="utf8") as f: - fake.return_value = f - register_url("http://register.url/is/used") - # set maxDiff - assert_equal.__self__.maxDiff = None - assert_equal( - EXTRA["http://register.url/is/used/js/"], - { - "安庆": file_name, - "English Name": file_name, - }, - ) - - fake_registry_1 = os.path.join(current_path, "fixtures", "registry_1.json") - with open(fake_registry_1, encoding="utf8") as f: - fake.return_value = f - register_url("http://register.url/is/used") - assert_equal( - EXTRA["http://register.url/is/used/"], - { - "安庆": file_name, - "English Name": file_name, - }, - ) - - -# TODO: Github Workflow cannot test it well...Fix it future... -# def test_register_url_error(): -# try: -# register_url("http://127.0.0.1") -# except (urllib.error.HTTPError, ConnectionRefusedError) as err: -# assert_in(type(err), [urllib.error.HTTPError, ConnectionRefusedError]) - -def test_register_url_error(): - try: - register_url("error_asset_url") - except ValueError as err: - assert_in(type(err), [ValueError]) - - -def test_fuzzy_search_dict(): - fd = FuzzyDict() - fd.update({"我是北京市": [1, 2]}) - assert_equal(fd["我是北京"], [1, 2]) - - -@raises(KeyError) -def test_fuzzy_search_key_error(): - fd = FuzzyDict() - fd.cutoff = 0.9 - _ = fd["我是北京"] - - -def test_register_files(): - register_files(asset_files={"x": 1}) - - -def test_register_coords(): - register_coords(coords={"深圳": [113, 23]}) +class TestDatasets(unittest.TestCase): + + @patch("pyecharts.datasets.urllib.request.urlopen") + def test_register_url(self, fake): + current_path = os.path.dirname(__file__) + fake_registry = os.path.join(current_path, "fixtures", "registry.json") + file_name = ["shape-with-internal-borders/an1_hui1_an1_qing4", "js"] + with open(fake_registry, encoding="utf8") as f: + fake.return_value = f + register_url("http://register.url/is/used") + # set maxDiff + self.assertEqual.__self__.maxDiff = None + self.assertEqual( + EXTRA["http://register.url/is/used/js/"], + { + "安庆": file_name, + "English Name": file_name, + }, + ) + + fake_registry_1 = os.path.join(current_path, "fixtures", "registry_1.json") + with open(fake_registry_1, encoding="utf8") as f: + fake.return_value = f + register_url("http://register.url/is/used") + self.assertEqual( + EXTRA["http://register.url/is/used/"], + { + "安庆": file_name, + "English Name": file_name, + }, + ) + + # TODO: Github Workflow cannot test it well...Fix it future... + # def test_register_url_error(): + # try: + # register_url("http://127.0.0.1") + # except (urllib.error.HTTPError, ConnectionRefusedError) as err: + # assert_in(type(err), [urllib.error.HTTPError, ConnectionRefusedError]) + + def test_register_url_error(self): + try: + register_url("error_asset_url") + except ValueError as err: + self.assertIn(type(err), [ValueError]) + + def test_fuzzy_search_dict(self): + fd = FuzzyDict() + fd.update({"我是北京市": [1, 2]}) + self.assertEqual(fd["我是北京"], [1, 2]) + + def test_fuzzy_search_key_error(self): + with self.assertRaises(KeyError): + fd = FuzzyDict() + fd.cutoff = 0.9 + _ = fd["我是北京"] + + def test_register_files(self): + register_files(asset_files={"x": 1}) + + def test_register_coords(self): + register_coords(coords={"深圳": [113, 23]}) diff --git a/test/test_display.py b/test/test_display.py index a9fca5c0a..ad95f85e9 100644 --- a/test/test_display.py +++ b/test/test_display.py @@ -1,49 +1,53 @@ -from nose.tools import assert_equal, assert_in +import unittest from pyecharts.render.display import HTML, Javascript -def test_display_html(): - html_content = "

hello world

" - obj = HTML(html_content) - assert_equal(obj.data, html_content) - assert_equal(obj.__html__(), html_content) - - -def test_display_javascript(): - js_content = "console.log('hello world')" - obj = Javascript(js_content) - assert_equal(obj.data, js_content) - assert_equal(obj._repr_javascript_(), js_content) - - -def test_display_javascript_v1(): - js_content = "console.log('hello world')" - obj = Javascript(js_content, lib="test lib", css="test css") - assert_equal(obj.data, js_content) - - obj_1 = Javascript( - data=js_content, - lib=["lib1", "lib2"], - css=["css1", "css2"], - ) - assert_equal(obj_1.data, js_content) - assert_in(js_content, obj_1._repr_javascript_()) - - -def test_display_javascript_v2(): - import ssl - ssl._create_default_https_context = ssl._create_unverified_context - - obj = Javascript(lib=['https://assets.pyecharts.org/assets/v5/echarts.min.js']) - obj.load_javascript_contents() - assert_in( - "echarts", - obj.javascript_contents["https://assets.pyecharts.org/assets/v5/echarts.min.js"], - ) - - obj_1 = Javascript(lib=['https://assets.pyecharts.org/assets/v4/echarts.min.js']) - try: - obj_1.load_javascript_contents() - except RuntimeError: - pass +class TestDisplay(unittest.TestCase): + + def test_display_html(self): + html_content = "

hello world

" + obj = HTML(html_content) + self.assertEqual(obj.data, html_content) + self.assertEqual(obj.__html__(), html_content) + + def test_display_javascript(self): + js_content = "console.log('hello world')" + obj = Javascript(js_content) + self.assertEqual(obj.data, js_content) + self.assertEqual(obj._repr_javascript_(), js_content) + + def test_display_javascript_v1(self): + js_content = "console.log('hello world')" + obj = Javascript(js_content, lib="test lib", css="test css") + self.assertEqual(obj.data, js_content) + + obj_1 = Javascript( + data=js_content, + lib=["lib1", "lib2"], + css=["css1", "css2"], + ) + self.assertEqual(obj_1.data, js_content) + self.assertIn(js_content, obj_1._repr_javascript_()) + + def test_display_javascript_v2(self): + import ssl + + ssl._create_default_https_context = ssl._create_unverified_context + + obj = Javascript(lib=["https://assets.pyecharts.org/assets/v5/echarts.min.js"]) + obj.load_javascript_contents() + self.assertIn( + "echarts", + obj.javascript_contents[ + "https://assets.pyecharts.org/assets/v5/echarts.min.js" + ], + ) + + obj_1 = Javascript( + lib=["https://assets.pyecharts.org/assets/v4/echarts.min.js"] + ) + try: + obj_1.load_javascript_contents() + except RuntimeError: + pass diff --git a/test/test_effectscatter.py b/test/test_effectscatter.py index b95263cf6..1c81464a4 100644 --- a/test/test_effectscatter.py +++ b/test/test_effectscatter.py @@ -1,31 +1,30 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import EffectScatter from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_effectscatter_base(fake_writer): - c = EffectScatter().add_xaxis(Faker.choose()).add_yaxis("", Faker.values()) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - +class TestEffectScatterChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_effectscatter_base(self, fake_writer): + c = EffectScatter().add_xaxis(Faker.choose()).add_yaxis("", Faker.values()) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_effectscatter_item_base(fake_writer): - x_axis = Faker.choose() - chart_item = [ - opts.EffectScatterItem(name=d[0], value=d[1]) - for d in list(zip(x_axis, Faker.values())) - ] + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_effectscatter_item_base(self, fake_writer): + x_axis = Faker.choose() + chart_item = [ + opts.EffectScatterItem(name=d[0], value=d[1]) + for d in list(zip(x_axis, Faker.values())) + ] - c = EffectScatter().add_xaxis(x_axis).add_yaxis("", chart_item) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c = EffectScatter().add_xaxis(x_axis).add_yaxis("", chart_item) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_engine.py b/test/test_engine.py index 5db0b4386..579c7411e 100644 --- a/test/test_engine.py +++ b/test/test_engine.py @@ -1,3 +1,4 @@ +import unittest from unittest.mock import patch from pyecharts.charts import Bar @@ -6,49 +7,51 @@ from pyecharts.globals import CurrentConfig -def test_generate_js_link(): - FILENAMES.update({ - "existing_dep": ("file_path", "ext") - }) - EXTRA.update({ - "https://extra_host.com": { - "dep": ("extra_file_path", "extra_ext"), - } - }) - - # Bar 图 - chart = Bar() - chart.js_host = None - chart.js_dependencies.items = [ - "https://api.map.baidu.com/test", - "existing_dep", - "dep" - ] - - RenderEngine.generate_js_link(chart) - - assert chart.js_host == CurrentConfig.ONLINE_HOST - - expected_links = [ - "https://api.map.baidu.com/test", - "{}file_path.ext".format(CurrentConfig.ONLINE_HOST), - "https://extra_host.comextra_file_path.extra_ext", - ] - assert chart.dependencies == expected_links - - -def test_import_iterable_new_location(): - # 在collections.abc不可用的情况下,尝试从collections导入 - with patch('collections.abc', side_effect=ImportError): - import collections - try: - assert collections.Iterable.__module__ == 'collections' - except (AttributeError, AssertionError): - pass - - -def test_import_iterable_old_location(): - with patch.dict('sys.modules', {'collections.abc': object()}): - import collections.abc - # importlib.reload(collections.abc) # 重新加载模块以应用补丁 - assert collections.abc.Iterable.__module__ == 'collections.abc' +class TestEngine(unittest.TestCase): + + def test_generate_js_link(self): + FILENAMES.update({"existing_dep": ("file_path", "ext")}) + EXTRA.update( + { + "https://extra_host.com": { + "dep": ("extra_file_path", "extra_ext"), + } + } + ) + + # Bar 图 + chart = Bar() + chart.js_host = None + chart.js_dependencies.items = [ + "https://api.map.baidu.com/test", + "existing_dep", + "dep", + ] + + RenderEngine.generate_js_link(chart) + + assert chart.js_host == CurrentConfig.ONLINE_HOST + + expected_links = [ + "https://api.map.baidu.com/test", + "{}file_path.ext".format(CurrentConfig.ONLINE_HOST), + "https://extra_host.comextra_file_path.extra_ext", + ] + assert chart.dependencies == expected_links + + def test_import_iterable_new_location(self): + # 在collections.abc不可用的情况下,尝试从collections导入 + with patch("collections.abc", side_effect=ImportError): + import collections + + try: + assert collections.Iterable.__module__ == "collections" + except (AttributeError, AssertionError): + pass + + def test_import_iterable_old_location(self): + with patch.dict("sys.modules", {"collections.abc": object()}): + import collections.abc + + # importlib.reload(collections.abc) # 重新加载模块以应用补丁 + assert collections.abc.Iterable.__module__ == "collections.abc" diff --git a/test/test_exception.py b/test/test_exception.py index 695e9f96a..664f863f3 100644 --- a/test/test_exception.py +++ b/test/test_exception.py @@ -1,4 +1,4 @@ -from nose.tools import assert_equal +import unittest from pyecharts import options as opts from pyecharts.charts import Geo, BMap @@ -8,34 +8,47 @@ FAKE_API_KEY = "fake_application_key" -def test_geo_catch_nonexistent_coord_exception(): - try: +class TestException(unittest.TestCase): + + def test_geo_catch_nonexistent_coord_exception(self): + try: + ( + Geo() + .add_schema(maptype="china") + .add("geo", [["NonexistentLocation", 123]]) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) + ) + except NonexistentCoordinatesException as err: + self.assertEqual(type(err), NonexistentCoordinatesException) + assert err.__str__() != "" + + def test_geo_ignore_nonexistent_coord_exception(self): ( - Geo() + Geo(is_ignore_nonexistent_coord=True) .add_schema(maptype="china") .add("geo", [["NonexistentLocation", 123]]) .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) .set_global_opts(visualmap_opts=opts.VisualMapOpts()) ) - except NonexistentCoordinatesException as err: - assert_equal(type(err), NonexistentCoordinatesException) - assert err.__str__() != "" - - -def test_geo_ignore_nonexistent_coord_exception(): - ( - Geo(is_ignore_nonexistent_coord=True) - .add_schema(maptype="china") - .add("geo", [["NonexistentLocation", 123]]) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) + def test_bmap_catch_nonexistent_coord_exception(self): + try: + ( + BMap() + .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add( + "bmap", + [["NonexistentLocation", 123]], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + except NonexistentCoordinatesException as err: + self.assertEqual(type(err), NonexistentCoordinatesException) -def test_bmap_catch_nonexistent_coord_exception(): - try: + def test_bmap_ignore_nonexistent_coord_exception(self): ( - BMap() + BMap(is_ignore_nonexistent_coord=True) .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) .add( "bmap", @@ -43,17 +56,3 @@ def test_bmap_catch_nonexistent_coord_exception(): label_opts=opts.LabelOpts(formatter="{b}"), ) ) - except NonexistentCoordinatesException as err: - assert_equal(type(err), NonexistentCoordinatesException) - - -def test_bmap_ignore_nonexistent_coord_exception(): - ( - BMap(is_ignore_nonexistent_coord=True) - .add_schema(baidu_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) - .add( - "bmap", - [["NonexistentLocation", 123]], - label_opts=opts.LabelOpts(formatter="{b}"), - ) - ) diff --git a/test/test_faker.py b/test/test_faker.py index 046648017..bb5e4d193 100644 --- a/test/test_faker.py +++ b/test/test_faker.py @@ -1,23 +1,21 @@ -from unittest.mock import patch - -from nose.tools import assert_equal +import unittest from pyecharts.faker import Faker, Collector -def test_rand_color(): - rand_color = Faker.rand_color() - assert rand_color is not None - +class TestFaker(unittest.TestCase): -def test_img_path(): - assert_equal(Faker.img_path(path="/usr/local"), "/usr/local") + def test_rand_color(self): + rand_color = Faker.rand_color() + assert rand_color is not None + def test_img_path(self): + self.assertEqual(Faker.img_path(path="/usr/local"), "/usr/local") -def test_collector(): - def _add(x, y): - return x + y + def test_collector(self): + def _add(x, y): + return x + y - c = Collector() - c.funcs(_add) - assert_equal(c.charts[0][1], "_add") + c = Collector() + c.funcs(_add) + self.assertEqual(c.charts[0][1], "_add") diff --git a/test/test_funnel.py b/test/test_funnel.py index 1ac895044..ae27b93f0 100644 --- a/test/test_funnel.py +++ b/test/test_funnel.py @@ -1,33 +1,33 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import Funnel from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_funnel_base(fake_writer): - c = Funnel().add("商品", [list(z) for z in zip(Faker.choose(), Faker.values())]) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") +class TestFunnelChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_funnel_base(self, fake_writer): + c = Funnel().add("商品", [list(z) for z in zip(Faker.choose(), Faker.values())]) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_funnel_item_base(fake_writer): - funnel_item = [ - opts.FunnelItem(name=d[0], value=d[1]) - for d in list(zip(Faker.choose(), Faker.values())) - ] - c = ( - Funnel() - .add("商品", funnel_item) - .set_global_opts(title_opts=opts.TitleOpts(title="Funnel-基本示例")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_funnel_item_base(self, fake_writer): + funnel_item = [ + opts.FunnelItem(name=d[0], value=d[1]) + for d in list(zip(Faker.choose(), Faker.values())) + ] + c = ( + Funnel() + .add("商品", funnel_item) + .set_global_opts(title_opts=opts.TitleOpts(title="Funnel-基本示例")) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_gauge.py b/test/test_gauge.py index fd91793da..a27376cc5 100644 --- a/test/test_gauge.py +++ b/test/test_gauge.py @@ -1,39 +1,38 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts.charts import Gauge from pyecharts import options as opts -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_gauge_base(fake_writer): - c = Gauge().add("", [("完成率", 66.6)]) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_gauage_label_setting(fake_writer): - c = Gauge().add( - "", - [("完成率", 66.6)], - detail_label_opts=opts.GaugeDetailOpts(formatter="{value}"), - title_label_opts=opts.GaugeTitleOpts( - font_size=40, color="blue", font_family="Microsoft YaHei" - ), - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("title", content) - assert_in("detail", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_gauage_radius_setting(fake_writer): - c = Gauge().add("", [("完成率", 66.6)], radius="50%") - c.render() - _, content = fake_writer.call_args[0] - assert_in("radius", content) +class TestGaugeChart(unittest.TestCase): + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_gauge_base(self, fake_writer): + c = Gauge().add("", [("完成率", 66.6)]) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_gauage_label_setting(self, fake_writer): + c = Gauge().add( + "", + [("完成率", 66.6)], + detail_label_opts=opts.GaugeDetailOpts(formatter="{value}"), + title_label_opts=opts.GaugeTitleOpts( + font_size=40, color="blue", font_family="Microsoft YaHei" + ), + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("title", content) + self.assertIn("detail", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_gauage_radius_setting(self, fake_writer): + c = Gauge().add("", [("完成率", 66.6)], radius="50%") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("radius", content) diff --git a/test/test_geo.py b/test/test_geo.py index a475835b5..29cc487d4 100644 --- a/test/test_geo.py +++ b/test/test_geo.py @@ -1,442 +1,423 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Geo from pyecharts.faker import Faker from pyecharts.globals import ChartType, SymbolType -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_base(fake_writer): - c = ( - Geo() - .add_schema(maptype="china") - .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) - assert_equal(c.theme, "white") - c.render() - _, content = fake_writer.call_args[0] - assert_in("canvas", content) - - -def test_geo_add_coord_json(): - c = ( - Geo() - .add_schema(maptype="china") - .add_coordinate_json(json_file="test/fixtures/city_coordinates.json") - .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) - assert_equal(c.theme, "white") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_custom(fake_writer): - c = ( - Geo() - .add_schema(maptype="china") - .add( - "geo", - [list(z) for z in zip(Faker.provinces, Faker.values())], - type_=ChartType.CUSTOM, +class TestGeoChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_base(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) ) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(title_opts=opts.TitleOpts(title="Geo-EffectScatter")) - ) - assert_equal(c.theme, "white") - c.render() - _, content = fake_writer.call_args[0] - assert_in("canvas", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_effectscatter(fake_writer): - c = ( - Geo() - .add_schema(maptype="china") - .add( - "geo", - [list(z) for z in zip(Faker.provinces, Faker.values())], - type_=ChartType.EFFECT_SCATTER, + self.assertEqual(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("canvas", content) + + def test_geo_add_coord_json(self): + c = ( + Geo() + .add_schema(maptype="china") + .add_coordinate_json(json_file="test/fixtures/city_coordinates.json") + .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) ) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(title_opts=opts.TitleOpts(title="Geo-EffectScatter")) - ) - assert_equal(c.theme, "white") - c.render() - _, content = fake_writer.call_args[0] - assert_in("canvas", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_heatmap(fake_writer): - c = ( - Geo() - .add_schema(maptype="china") - .add( - "geo", - [list(z) for z in zip(Faker.provinces, Faker.values())], - type_=ChartType.HEATMAP, + self.assertEqual(c.theme, "white") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_custom(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + [list(z) for z in zip(Faker.provinces, Faker.values())], + type_=ChartType.CUSTOM, + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(title_opts=opts.TitleOpts(title="Geo-EffectScatter")) ) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts( - visualmap_opts=opts.VisualMapOpts(), - title_opts=opts.TitleOpts(title="Geo-HeatMap"), + self.assertEqual(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("canvas", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_effectscatter(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + [list(z) for z in zip(Faker.provinces, Faker.values())], + type_=ChartType.EFFECT_SCATTER, + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(title_opts=opts.TitleOpts(title="Geo-EffectScatter")) ) - ) - assert_equal(c.theme, "white") - c.render() - _, content = fake_writer.call_args[0] - assert_in("canvas", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_lines(fake_writer): - c = ( - Geo() - .add_schema(maptype="china") - .add( - "", - [("广州", 55), ("北京", 66), ("杭州", 77), ("重庆", 88)], - type_=ChartType.EFFECT_SCATTER, - color="white", + self.assertEqual(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("canvas", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_heatmap(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + [list(z) for z in zip(Faker.provinces, Faker.values())], + type_=ChartType.HEATMAP, + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(), + title_opts=opts.TitleOpts(title="Geo-HeatMap"), + ) ) - .add( - "geo", - [("广州", "上海"), ("广州", "北京"), ("广州", "杭州"), ("广州", "重庆")], - type_=ChartType.LINES, - effect_opts=opts.EffectOpts( - symbol=SymbolType.ARROW, symbol_size=6, color="blue" - ), - linestyle_opts=opts.LineStyleOpts(curve=0.2), - ) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(title_opts=opts.TitleOpts(title="Geo-Lines")) - ) - assert_equal(c.theme, "white") - c.render() - _, content = fake_writer.call_args[0] - assert_in("canvas", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_extra_geo_parameters(fake_writer): - c = ( - Geo() - .add_schema(maptype="china", center=[39, 117.7], zoom=9) - .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - c.render() - _, content = fake_writer.call_args[0] - center_string = """ - "center": [ - 39, - 117.7 - ], - """ - assert_in(center_string, content) - assert_in('"zoom": 9', content) - - -def _geo_chart() -> Geo: - return ( - Geo() - .add_schema(maptype="china", center=[39, 117.7], zoom=9) - .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) - - -def test_geo_dump_options(): - c = _geo_chart() - formatter = """"formatter": function (params) { return params.name + ' : ' + params.value[2]; }""" # noqa - assert_in(formatter, c.dump_options()) - - -def test_geo_dump_options_with_quotes(): - c = _geo_chart() - formatter = """"formatter": "function (params) { return params.name + ' : ' + params.value[2]; }""" # noqa - assert_in(formatter, c.dump_options_with_quotes()) - - -def test_geo_add_geo_json(): - geo_json = { - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": {}, - "geometry": { - "coordinates": [ - [ - [118.57812, 26.557402], - [118.590394, 26.553081], - [118.617087, 26.553059], - [118.616111, 26.527755], - [118.604148, 26.504904], - [118.608376, 26.497294], - [118.59623, 26.478385], - [118.578807, 26.473754], - [118.56458, 26.465373], - [118.554474, 26.445927], - [118.566533, 26.432851], - [118.54632, 26.417928], - [118.548079, 26.396854], - [118.560868, 26.378456], - [118.566104, 26.358824], - [118.589836, 26.373463], - [118.590222, 26.359802], - [118.595801, 26.347859], - [118.60369, 26.350515], - [118.621191, 26.361783], - [118.631475, 26.354788], - [118.656162, 26.340797], - [118.662964, 26.322656], - [118.653222, 26.313452], - [118.661204, 26.296272], - [118.664809, 26.270524], - [118.678076, 26.279442], - [118.68585, 26.28836], - [118.697277, 26.287725], - [118.693599, 26.305557], - [118.699533, 26.311078], - [118.724644, 26.337465], - [118.75152, 26.330542], - [118.76682, 26.327157], - [118.775794, 26.325234], - [118.797129, 26.340542], - [118.800059, 26.357136], - [118.785137, 26.365115], - [118.771085, 26.385377], - [118.750534, 26.390836], - [118.761617, 26.420202], - [118.745161, 26.427236], - [118.732826, 26.437957], - [118.734248, 26.45448], - [118.737767, 26.472405], - [118.748152, 26.481723], - [118.754353, 26.487893], - [118.748195, 26.494063], - [118.748238, 26.506403], - [118.762057, 26.508959], - [118.780881, 26.504741], - [118.784598, 26.491919], - [118.798615, 26.484013], - [118.805766, 26.474876], - [118.794544, 26.459227], - [118.809178, 26.445137], - [118.805487, 26.430479], - [118.822825, 26.420838], - [118.85278, 26.438461], - [118.878615, 26.469605], - [118.898227, 26.473649], - [118.91784, 26.472777], - [118.949511, 26.464885], - [118.951619, 26.487056], - [118.943747, 26.493838], - [118.942741, 26.507994], - [118.962064, 26.530816], - [118.997866, 26.528448], - [119.000968, 26.556946], - [119.009563, 26.560258], - [119.015767, 26.581621], - [119.032957, 26.594999], - [119.062507, 26.602235], - [119.075945, 26.587083], - [119.06329, 26.569473], - [119.076433, 26.569869], - [119.092152, 26.562702], - [119.101005, 26.572732], - [119.103604, 26.555941], - [119.119788, 26.539555], - [119.132, 26.538732], - [119.144213, 26.536681], - [119.171383, 26.548551], - [119.194922, 26.568383], - [119.212185, 26.547589], - [119.218757, 26.53412], - [119.229448, 26.526792], - [119.251174, 26.53241], - [119.253859, 26.546561], - [119.272336, 26.541055], - [119.281139, 26.534343], - [119.287711, 26.577995], - [119.301835, 26.593391], - [119.329006, 26.578699], - [119.354338, 26.599587], - [119.363191, 26.603281], - [119.376544, 26.604515], - [119.381656, 26.62171], - [119.397376, 26.627859], - [119.387272, 26.641423], - [119.375795, 26.639028], - [119.358824, 26.632951], - [119.351467, 26.648969], - [119.326158, 26.676212], - [119.261119, 26.690807], - [119.241593, 26.742684], - [119.226535, 26.728854], - [119.204611, 26.727288], - [119.182686, 26.729403], - [119.167628, 26.711891], - [119.146452, 26.731884], - [119.115663, 26.73961], - [119.047905, 26.728693], - [119.045955, 26.74669], - [119.054992, 26.764685], - [119.051092, 26.776147], - [119.042385, 26.776575], - [119.033679, 26.770872], - [119.012146, 26.761305], - [118.982497, 26.773075], - [118.945981, 26.77013], - [118.926007, 26.749652], - [118.882686, 26.738982], - [118.887478, 26.757469], - [118.903255, 26.76737], - [118.923824, 26.795751], - [118.890803, 26.813278], - [118.843328, 26.798569], - [118.827143, 26.786924], - [118.795852, 26.783859], - [118.771428, 26.805618], - [118.741511, 26.818793], - [118.728195, 26.830895], - [118.722432, 26.851573], - [118.739746, 26.856167], - [118.696095, 26.869031], - [118.664633, 26.86214], - [118.63317, 26.855248], - [118.620069, 26.839818], - [118.635807, 26.832963], - [118.638444, 26.810674], - [118.625974, 26.788073], - [118.623117, 26.769147], - [118.600382, 26.7093], - [118.582339, 26.703842], - [118.569788, 26.680592], - [118.57902, 26.673354], - [118.594415, 26.673546], - [118.594991, 26.656748], - [118.587905, 26.631739], - [118.57812, 26.557402], - ] - ], - "type": "Polygon", - }, - } - ], - } - - c = Geo() - c.add_geo_json(geo_json=geo_json) - assert_equal(c._geo_json, geo_json) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_scatter_gl(fake_writer): - c = ( - Geo() - .add_schema(maptype="china") - .add( - "geo", - [list(z) for z in zip(Faker.provinces, Faker.values())], - type_=ChartType.SCATTERGL, + self.assertEqual(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("canvas", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_lines(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "", + [("广州", 55), ("北京", 66), ("杭州", 77), ("重庆", 88)], + type_=ChartType.EFFECT_SCATTER, + color="white", + ) + .add( + "geo", + [ + ("广州", "上海"), + ("广州", "北京"), + ("广州", "杭州"), + ("广州", "重庆"), + ], + type_=ChartType.LINES, + effect_opts=opts.EffectOpts( + symbol=SymbolType.ARROW, symbol_size=6, color="blue" + ), + linestyle_opts=opts.LineStyleOpts(curve=0.2), + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(title_opts=opts.TitleOpts(title="Geo-Lines")) ) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) - assert_equal(c.theme, "white") - c.render() - _, content = fake_writer.call_args[0] - assert_in("canvas", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_lines_gl(fake_writer): - c = ( - Geo() - .add_schema(maptype="china") - .add( - "geo", - [list(z) for z in zip(Faker.provinces, Faker.values())], - type_=ChartType.LINESGL, + self.assertEqual(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("canvas", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_extra_geo_parameters(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china", center=[39, 117.7], zoom=9) + .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) ) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) - assert_equal(c.theme, "white") - c.render() - _, content = fake_writer.call_args[0] - assert_in("canvas", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_flow_gl(fake_writer): - c = ( - Geo() - .add_schema(maptype="china") - .add( - "geo", - [list(z) for z in zip(Faker.provinces, Faker.values())], - type_=ChartType.FLOWGL, + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + center_string = "center" + self.assertIn(center_string, content) + self.assertIn('"zoom": 9', content) + + @staticmethod + def _geo_chart() -> Geo: + return ( + Geo() + .add_schema(maptype="china", center=[39, 117.7], zoom=9) + .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) ) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) - assert_equal(c.theme, "white") - c.render() - _, content = fake_writer.call_args[0] - assert_in("canvas", content) - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_pie(fake_writer): - c = ( - Geo() - .add_schema(maptype="china") - .add( - series_name="广东省", - data_pair=[ - ("A", 10), ("B", 20), ("C", 30), ("D", 50) + def test_geo_dump_options(self): + c = self._geo_chart() + formatter = """"formatter": function (params) { return params.name + ' : ' + params.value[2]; }""" # noqa + self.assertIn(formatter, c.dump_options()) + + def test_geo_dump_options_with_quotes(self): + c = self._geo_chart() + formatter = """"formatter": "function (params) { return params.name + ' : ' + params.value[2]; }""" # noqa + self.assertIn(formatter, c.dump_options_with_quotes()) + + def test_geo_add_geo_json(self): + geo_json = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "coordinates": [ + [ + [118.57812, 26.557402], + [118.590394, 26.553081], + [118.617087, 26.553059], + [118.616111, 26.527755], + [118.604148, 26.504904], + [118.608376, 26.497294], + [118.59623, 26.478385], + [118.578807, 26.473754], + [118.56458, 26.465373], + [118.554474, 26.445927], + [118.566533, 26.432851], + [118.54632, 26.417928], + [118.548079, 26.396854], + [118.560868, 26.378456], + [118.566104, 26.358824], + [118.589836, 26.373463], + [118.590222, 26.359802], + [118.595801, 26.347859], + [118.60369, 26.350515], + [118.621191, 26.361783], + [118.631475, 26.354788], + [118.656162, 26.340797], + [118.662964, 26.322656], + [118.653222, 26.313452], + [118.661204, 26.296272], + [118.664809, 26.270524], + [118.678076, 26.279442], + [118.68585, 26.28836], + [118.697277, 26.287725], + [118.693599, 26.305557], + [118.699533, 26.311078], + [118.724644, 26.337465], + [118.75152, 26.330542], + [118.76682, 26.327157], + [118.775794, 26.325234], + [118.797129, 26.340542], + [118.800059, 26.357136], + [118.785137, 26.365115], + [118.771085, 26.385377], + [118.750534, 26.390836], + [118.761617, 26.420202], + [118.745161, 26.427236], + [118.732826, 26.437957], + [118.734248, 26.45448], + [118.737767, 26.472405], + [118.748152, 26.481723], + [118.754353, 26.487893], + [118.748195, 26.494063], + [118.748238, 26.506403], + [118.762057, 26.508959], + [118.780881, 26.504741], + [118.784598, 26.491919], + [118.798615, 26.484013], + [118.805766, 26.474876], + [118.794544, 26.459227], + [118.809178, 26.445137], + [118.805487, 26.430479], + [118.822825, 26.420838], + [118.85278, 26.438461], + [118.878615, 26.469605], + [118.898227, 26.473649], + [118.91784, 26.472777], + [118.949511, 26.464885], + [118.951619, 26.487056], + [118.943747, 26.493838], + [118.942741, 26.507994], + [118.962064, 26.530816], + [118.997866, 26.528448], + [119.000968, 26.556946], + [119.009563, 26.560258], + [119.015767, 26.581621], + [119.032957, 26.594999], + [119.062507, 26.602235], + [119.075945, 26.587083], + [119.06329, 26.569473], + [119.076433, 26.569869], + [119.092152, 26.562702], + [119.101005, 26.572732], + [119.103604, 26.555941], + [119.119788, 26.539555], + [119.132, 26.538732], + [119.144213, 26.536681], + [119.171383, 26.548551], + [119.194922, 26.568383], + [119.212185, 26.547589], + [119.218757, 26.53412], + [119.229448, 26.526792], + [119.251174, 26.53241], + [119.253859, 26.546561], + [119.272336, 26.541055], + [119.281139, 26.534343], + [119.287711, 26.577995], + [119.301835, 26.593391], + [119.329006, 26.578699], + [119.354338, 26.599587], + [119.363191, 26.603281], + [119.376544, 26.604515], + [119.381656, 26.62171], + [119.397376, 26.627859], + [119.387272, 26.641423], + [119.375795, 26.639028], + [119.358824, 26.632951], + [119.351467, 26.648969], + [119.326158, 26.676212], + [119.261119, 26.690807], + [119.241593, 26.742684], + [119.226535, 26.728854], + [119.204611, 26.727288], + [119.182686, 26.729403], + [119.167628, 26.711891], + [119.146452, 26.731884], + [119.115663, 26.73961], + [119.047905, 26.728693], + [119.045955, 26.74669], + [119.054992, 26.764685], + [119.051092, 26.776147], + [119.042385, 26.776575], + [119.033679, 26.770872], + [119.012146, 26.761305], + [118.982497, 26.773075], + [118.945981, 26.77013], + [118.926007, 26.749652], + [118.882686, 26.738982], + [118.887478, 26.757469], + [118.903255, 26.76737], + [118.923824, 26.795751], + [118.890803, 26.813278], + [118.843328, 26.798569], + [118.827143, 26.786924], + [118.795852, 26.783859], + [118.771428, 26.805618], + [118.741511, 26.818793], + [118.728195, 26.830895], + [118.722432, 26.851573], + [118.739746, 26.856167], + [118.696095, 26.869031], + [118.664633, 26.86214], + [118.63317, 26.855248], + [118.620069, 26.839818], + [118.635807, 26.832963], + [118.638444, 26.810674], + [118.625974, 26.788073], + [118.623117, 26.769147], + [118.600382, 26.7093], + [118.582339, 26.703842], + [118.569788, 26.680592], + [118.57902, 26.673354], + [118.594415, 26.673546], + [118.594991, 26.656748], + [118.587905, 26.631739], + [118.57812, 26.557402], + ] + ], + "type": "Polygon", + }, + } ], - type_=ChartType.PIE, + } + + c = Geo() + c.add_geo_json(geo_json=geo_json) + self.assertEqual(c._geo_json, geo_json) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_scatter_gl(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + [list(z) for z in zip(Faker.provinces, Faker.values())], + type_=ChartType.SCATTERGL, + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) ) - .set_global_opts( - title_opts=opts.TitleOpts(title="Geo-Pie"), - legend_opts={} + self.assertEqual(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("canvas", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_lines_gl(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + [list(z) for z in zip(Faker.provinces, Faker.values())], + type_=ChartType.LINESGL, + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) ) - ) - assert_equal(c.theme, "white") - c.render() - _, content = fake_writer.call_args[0] - assert_in("canvas", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_geo_item_base(fake_writer): - data_pair = [ - opts.GeoItem(longitude=121.97, latitude=30.88, name="公园", value=12), - ] - c = ( - Geo() - .add_schema(maptype="china") - .add(series_name="测试数据", data_pair=data_pair) - .set_global_opts( - visualmap_opts=opts.VisualMapOpts(), - title_opts=opts.TitleOpts(title="Geo-基本示例"), + self.assertEqual(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("canvas", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_flow_gl(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + [list(z) for z in zip(Faker.provinces, Faker.values())], + type_=ChartType.FLOWGL, + ) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) + ) + self.assertEqual(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("canvas", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_pie(self, fake_writer): + c = ( + Geo() + .add_schema(maptype="china") + .add( + series_name="广东省", + data_pair=[("A", 10), ("B", 20), ("C", 30), ("D", 50)], + type_=ChartType.PIE, + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Geo-Pie"), legend_opts={}) + ) + self.assertEqual(c.theme, "white") + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("canvas", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_geo_item_base(self, fake_writer): + data_pair = [ + opts.GeoItem(longitude=121.97, latitude=30.88, name="公园", value=12), + ] + c = ( + Geo() + .add_schema(maptype="china") + .add(series_name="测试数据", data_pair=data_pair) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(), + title_opts=opts.TitleOpts(title="Geo-基本示例"), + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_global_options.py b/test/test_global_options.py index d703decd7..fc730221b 100644 --- a/test/test_global_options.py +++ b/test/test_global_options.py @@ -1,4 +1,4 @@ -from nose.tools import assert_equal +import unittest from pyecharts.commons.utils import remove_key_with_none_value from pyecharts.options.global_options import ( @@ -32,356 +32,337 @@ ) -def test_animation_options_remove_none(): - option = AnimationOpts() - expected = { - "animation": True, - "animationDelay": 0, - "animationDelayUpdate": 0, - "animationDuration": 1000, - "animationDurationUpdate": 300, - "animationEasing": "cubicOut", - "animationEasingUpdate": "cubicOut", - "animationThreshold": 2000, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_aria_label_options_remove_none(): - option = AriaLabelOpts() - expected = { - "enabled": True, - "general": { - "withTitle": "这是一个关于“{title}”的图表。", - "withoutTitle": "这是一个图表,", - }, - "series": { - "maxCount": 10, - "single": { - "withName": "图表类型是{seriesType},表示{seriesName}。", - "withoutName": "图表类型是{seriesType}。", +class TestGlobalOptions(unittest.TestCase): + def test_animation_options_remove_none(self): + option = AnimationOpts() + expected = { + "animation": True, + "animationDelay": 0, + "animationDelayUpdate": 0, + "animationDuration": 1000, + "animationDurationUpdate": 300, + "animationEasing": "cubicOut", + "animationEasingUpdate": "cubicOut", + "animationThreshold": 2000, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_aria_label_options_remove_none(self): + option = AriaLabelOpts() + expected = { + "enabled": True, + "general": { + "withTitle": "这是一个关于“{title}”的图表。", + "withoutTitle": "这是一个图表,", + }, + "series": { + "maxCount": 10, + "single": { + "withName": "图表类型是{seriesType},表示{seriesName}。", + "withoutName": "图表类型是{seriesType}。", + }, + "multiple": { + "prefix": "它由{seriesCount}个图表系列组成。", + "withName": "图表类型是{seriesType},表示{seriesName}。", + "withoutName": "图表类型是{seriesType}。", + "separator": { + "middle": ";", + "end": "。", + }, + }, }, - "multiple": { - "prefix": "它由{seriesCount}个图表系列组成。", - "withName": "图表类型是{seriesType},表示{seriesName}。", - "withoutName": "图表类型是{seriesType}。", + "data": { + "maxCount": 10, + "allData": "其数据是——", + "partialData": "其中,前{displayCnt}项是——", + "withName": "{name}的数据是{value}", + "withoutName": "{value}", "separator": { - "middle": ";", - "end": "。", + "middle": ",", }, }, - }, - "data": { - "maxCount": 10, - "allData": "其数据是——", - "partialData": "其中,前{displayCnt}项是——", - "withName": "{name}的数据是{value}", - "withoutName": "{value}", - "separator": { - "middle": ",", + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_aria_decal_options_remove_none(self): + option = AriaDecalOpts() + expected = { + "show": False, + "decals": { + "symbol": "rect", + "symbolSize": 1, + "symbolKeepAspect": True, + "color": "rgba(0, 0, 0, 0.2)", + "dashArrayX": 5, + "dashArrayY": 5, + "rotation": 0, + "maxTileWidth": 512, + "maxTileHeight": 512, }, - }, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_aria_decal_options_remove_none(): - option = AriaDecalOpts() - expected = { - "show": False, - "decals": { - "symbol": "rect", - "symbolSize": 1, - "symbolKeepAspect": True, - "color": "rgba(0, 0, 0, 0.2)", - "dashArrayX": 5, - "dashArrayY": 5, - "rotation": 0, - "maxTileWidth": 512, - "maxTileHeight": 512, - }, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_init_options_remove_none(): - option = InitOpts(animation_opts={}, aria_opts={}) - expected = { - "animationOpts": {}, - "height": "500px", - "page_title": "Awesome-pyecharts", - "renderer": "canvas", - "theme": "white", - "width": "900px", - "ariaOpts": {}, - "fill_bg": False, - "is_horizontal_center": False, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_toolbox_feature_options_remove_none(): - save_as_image = ToolBoxFeatureSaveAsImageOpts() - restore = ToolBoxFeatureRestoreOpts() - data_view = ToolBoxFeatureDataViewOpts() - data_zoom = ToolBoxFeatureDataZoomOpts() - magic_type = ToolBoxFeatureMagicTypeOpts() - brush = ToolBoxFeatureBrushOpts() - - option = ToolBoxFeatureOpts( - save_as_image=save_as_image, - restore=restore, - data_view=data_view, - data_zoom=data_zoom, - magic_type=magic_type, - brush=brush, - ) - expected = { - "saveAsImage": save_as_image, - "restore": restore, - "dataView": data_view, - "dataZoom": data_zoom, - "magicType": magic_type, - "brush": brush, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_toolbox_options_remove_none(): - option = ToolboxOpts(feature={}) - expected = { - "show": True, - "orient": "horizontal", - "itemSize": 15, - "itemGap": 10, - "left": "80%", - "feature": {}, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_brush_options_remove_none(): - option = BrushOpts() - expected = { - "brushMode": "single", - "brushStyle": { - "borderColor": "rgba(120,140,180,0.8)", - "borderWidth": 1, - "color": "rgba(120,140,180,0.3)", - }, - "brushType": "rect", - "removeOnClick": True, - "throttleDelay": 0, - "throttleType": "fixRate", - "toolbox": ["rect", "polygon", "keep", "clear"], - "transformable": True, - "z": 10000, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_data_zoom_options_remove_none(): - option = DataZoomOpts() - - # slider - slider_expected = { - "end": 80, - "filterMode": "filter", - "orient": "horizontal", - "realtime": True, - "show": True, - "start": 20, - "type": "slider", - "zoomLock": False, - "showDetail": True, - "showDataShadow": True, - } - assert_equal(slider_expected, remove_key_with_none_value(option.opts)) - - # insider - option_1 = DataZoomOpts(type_="inside") - inside_expected = { - "end": 80, - "filterMode": "filter", - "orient": "horizontal", - "realtime": True, - "show": True, - "start": 20, - "type": "inside", - "zoomLock": False, - "showDetail": True, - "showDataShadow": True, - "disabled": False, - "zoomOnMouseWheel": True, - "moveOnMouseMove": True, - "moveOnMouseWheel": True, - "preventDefaultMouseMove": True, - } - assert_equal(inside_expected, remove_key_with_none_value(option_1.opts)) - - -def test_legend_options_remove_none(): - option = LegendOpts() - expected = { - "show": True, - "padding": 5, - "itemGap": 10, - "itemWidth": 25, - "itemHeight": 14, - "backgroundColor": "transparent", - "borderColor": "#ccc", - "borderRadius": 0, - "pageButtonItemGap": 5, - "pageButtonPosition": "end", - "pageFormatter": "{current}/{total}", - "pageIconColor": "#2f4554", - "pageIconInactiveColor": "#aaa", - "pageIconSize": 15, - "animationDurationUpdate": 800, - "selector": False, - "selectorPosition": "auto", - "selectorItemGap": 7, - "selectorButtonGap": 10, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_visual_map_options_remove_none(): - option = VisualMapOpts(range_opacity=0.1) - expected = { - "calculable": True, - "inRange": {"color": ["#50a3ba", "#eac763", "#d94e5d"], "opacity": 0.1}, - "itemHeight": 140, - "itemWidth": 20, - "max": 100, - "min": 0, - "orient": "vertical", - "show": True, - "showLabel": True, - "inverse": False, - "splitNumber": 5, - "type": "continuous", - "hoverLink": True, - "padding": 5, - "borderWidth": 0, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_tool_tip_options_remove_none(): - option = TooltipOpts(textstyle_opts=None) - expected = { - "alwaysShowContent": False, - "axisPointer": {"type": "line"}, - "borderWidth": 0, - "hideDelay": 100, - "padding": 5, - "show": True, - "showContent": True, - "showDelay": 0, - "trigger": "item", - "enterable": False, - "confine": False, - "appendToBody": False, - "transitionDuration": 0.4, - "order": "seriesAsc", - "triggerOn": "mousemove|click", - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_axis_tick_options_remove_none(): - option = AxisTickOpts() - expected = { - "show": True, - "alignWithLabel": False, - "inside": False, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_parallel_axis_options_remove_none(): - option = ParallelAxisOpts(dim=1) - expected = { - "dim": 1, - "parallelIndex": 0, - "realtime": True, - "name_location": "end", - "name_gap": 15, - "inverse": False, - "scale": False, - "logBase": 10, - "silent": False, - "triggerEvent": False, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_calendar_year_label_options_remove_none(): - option = CalendarYearLabelOpts() - expected = { - "show": True, - "color": "#000", - "fontStyle": "normal", - "fontWeight": "normal", - "fontFamily": "sans-serif", - "fontSize": 12, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_angle_axis_item_remove_none(): - item = AngleAxisItem(textstyle_opts=None) - expected = {} - assert_equal(expected, remove_key_with_none_value(item.opts)) - - -def test_angle_axis_options_remove_none(): - mock_data = [AngleAxisItem(value="1"), AngleAxisItem(value="2")] - option = AngleAxisOpts(data=mock_data) - expected = { - "data": mock_data, - "startAngle": 90, - "clockwise": False, - "scale": False, - "splitNumber": 5, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_radius_axis_item_remove_none(): - item = RadiusAxisItem() - expected = {} - assert_equal(expected, remove_key_with_none_value(item.opts)) - - -def test_radius_axis_options_remove_none(): - mock_data = [RadiusAxisItem(value="1"), RadiusAxisItem(value="2")] - option = RadiusAxisOpts(data=mock_data) - expected = { - "data": mock_data, - "nameGap": 15, - "inverse": False, - "scale": False, - "splitNumber": 5, - "minInterval": 0, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_dataset_transform_options_remove_none(): - option = DatasetTransformOpts() - expected = {"type": "filter", "print": False} - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_emphasis_3d_options_remove_none(): - option = Emphasis3DOpts() - expected = {} - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_blur_options_remove_none(): - option = BlurOpts() - expected = {"labelLine": {"show": False}} - assert_equal(expected, remove_key_with_none_value(option.opts)) + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_init_options_remove_none(self): + option = InitOpts(animation_opts={}, aria_opts={}) + expected = { + "animationOpts": {}, + "height": "500px", + "page_title": "Awesome-pyecharts", + "renderer": "canvas", + "theme": "white", + "width": "900px", + "ariaOpts": {}, + "fill_bg": False, + "is_horizontal_center": False, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_toolbox_feature_options_remove_none(self): + save_as_image = ToolBoxFeatureSaveAsImageOpts() + restore = ToolBoxFeatureRestoreOpts() + data_view = ToolBoxFeatureDataViewOpts() + data_zoom = ToolBoxFeatureDataZoomOpts() + magic_type = ToolBoxFeatureMagicTypeOpts() + brush = ToolBoxFeatureBrushOpts() + + option = ToolBoxFeatureOpts( + save_as_image=save_as_image, + restore=restore, + data_view=data_view, + data_zoom=data_zoom, + magic_type=magic_type, + brush=brush, + ) + expected = { + "saveAsImage": save_as_image, + "restore": restore, + "dataView": data_view, + "dataZoom": data_zoom, + "magicType": magic_type, + "brush": brush, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_toolbox_options_remove_none(self): + option = ToolboxOpts(feature={}) + expected = { + "show": True, + "orient": "horizontal", + "itemSize": 15, + "itemGap": 10, + "left": "80%", + "feature": {}, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_brush_options_remove_none(self): + option = BrushOpts() + expected = { + "brushMode": "single", + "brushStyle": { + "borderColor": "rgba(120,140,180,0.8)", + "borderWidth": 1, + "color": "rgba(120,140,180,0.3)", + }, + "brushType": "rect", + "removeOnClick": True, + "throttleDelay": 0, + "throttleType": "fixRate", + "toolbox": ["rect", "polygon", "keep", "clear"], + "transformable": True, + "z": 10000, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_data_zoom_options_remove_none(self): + option = DataZoomOpts() + + # slider + slider_expected = { + "end": 80, + "filterMode": "filter", + "orient": "horizontal", + "realtime": True, + "show": True, + "start": 20, + "type": "slider", + "zoomLock": False, + "showDetail": True, + "showDataShadow": True, + } + self.assertEqual(slider_expected, remove_key_with_none_value(option.opts)) + + # insider + option_1 = DataZoomOpts(type_="inside") + inside_expected = { + "end": 80, + "filterMode": "filter", + "orient": "horizontal", + "realtime": True, + "show": True, + "start": 20, + "type": "inside", + "zoomLock": False, + "showDetail": True, + "showDataShadow": True, + "disabled": False, + "zoomOnMouseWheel": True, + "moveOnMouseMove": True, + "moveOnMouseWheel": True, + "preventDefaultMouseMove": True, + } + self.assertEqual(inside_expected, remove_key_with_none_value(option_1.opts)) + + def test_legend_options_remove_none(self): + option = LegendOpts() + expected = { + "show": True, + "padding": 5, + "itemGap": 10, + "itemWidth": 25, + "itemHeight": 14, + "backgroundColor": "transparent", + "borderColor": "#ccc", + "borderRadius": 0, + "pageButtonItemGap": 5, + "pageButtonPosition": "end", + "pageFormatter": "{current}/{total}", + "pageIconColor": "#2f4554", + "pageIconInactiveColor": "#aaa", + "pageIconSize": 15, + "animationDurationUpdate": 800, + "selector": False, + "selectorPosition": "auto", + "selectorItemGap": 7, + "selectorButtonGap": 10, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_visual_map_options_remove_none(self): + option = VisualMapOpts(range_opacity=0.1) + expected = { + "calculable": True, + "inRange": {"color": ["#50a3ba", "#eac763", "#d94e5d"], "opacity": 0.1}, + "itemHeight": 140, + "itemWidth": 20, + "max": 100, + "min": 0, + "orient": "vertical", + "show": True, + "showLabel": True, + "inverse": False, + "splitNumber": 5, + "type": "continuous", + "hoverLink": True, + "padding": 5, + "borderWidth": 0, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_tool_tip_options_remove_none(self): + option = TooltipOpts(textstyle_opts=None) + expected = { + "alwaysShowContent": False, + "axisPointer": {"type": "line"}, + "borderWidth": 0, + "hideDelay": 100, + "padding": 5, + "show": True, + "showContent": True, + "showDelay": 0, + "trigger": "item", + "enterable": False, + "confine": False, + "appendToBody": False, + "transitionDuration": 0.4, + "order": "seriesAsc", + "triggerOn": "mousemove|click", + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_axis_tick_options_remove_none(self): + option = AxisTickOpts() + expected = { + "show": True, + "alignWithLabel": False, + "inside": False, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_parallel_axis_options_remove_none(self): + option = ParallelAxisOpts(dim=1) + expected = { + "dim": 1, + "parallelIndex": 0, + "realtime": True, + "name_location": "end", + "name_gap": 15, + "inverse": False, + "scale": False, + "logBase": 10, + "silent": False, + "triggerEvent": False, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_calendar_year_label_options_remove_none(self): + option = CalendarYearLabelOpts() + expected = { + "show": True, + "color": "#000", + "fontStyle": "normal", + "fontWeight": "normal", + "fontFamily": "sans-serif", + "fontSize": 12, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_angle_axis_item_remove_none(self): + item = AngleAxisItem(textstyle_opts=None) + expected = {} + self.assertEqual(expected, remove_key_with_none_value(item.opts)) + + def test_angle_axis_options_remove_none(self): + mock_data = [AngleAxisItem(value="1"), AngleAxisItem(value="2")] + option = AngleAxisOpts(data=mock_data) + expected = { + "data": mock_data, + "startAngle": 90, + "clockwise": False, + "scale": False, + "splitNumber": 5, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_radius_axis_item_remove_none(self): + item = RadiusAxisItem() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(item.opts)) + + def test_radius_axis_options_remove_none(self): + mock_data = [RadiusAxisItem(value="1"), RadiusAxisItem(value="2")] + option = RadiusAxisOpts(data=mock_data) + expected = { + "data": mock_data, + "nameGap": 15, + "inverse": False, + "scale": False, + "splitNumber": 5, + "minInterval": 0, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_dataset_transform_options_remove_none(self): + option = DatasetTransformOpts() + expected = {"type": "filter", "print": False} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_emphasis_3d_options_remove_none(self): + option = Emphasis3DOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_blur_options_remove_none(self): + option = BlurOpts() + expected = {"labelLine": {"show": False}} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_globals.py b/test/test_globals.py index 38c58732f..6852433ba 100644 --- a/test/test_globals.py +++ b/test/test_globals.py @@ -1,20 +1,20 @@ import os - -from nose.tools import assert_equal, assert_not_equal +import unittest from pyecharts.globals import RenderSepType -def test_render_sep_type(): - # normal test - render_sep_type = RenderSepType.SepType - assert_equal(os.linesep, render_sep_type) +class TestGlobals(unittest.TestCase): + def test_render_sep_type(self): + # normal test + render_sep_type = RenderSepType.SepType + self.assertEqual(os.linesep, render_sep_type) - # test modification - before_modify_type = RenderSepType.SepType - RenderSepType.SepType = "\t" - after_modify_type = RenderSepType.SepType - assert_not_equal(before_modify_type, after_modify_type) + # test modification + before_modify_type = RenderSepType.SepType + RenderSepType.SepType = "\t" + after_modify_type = RenderSepType.SepType + self.assertNotEqual(before_modify_type, after_modify_type) - # Restore config - RenderSepType.SepType = os.linesep + # Restore config + RenderSepType.SepType = os.linesep diff --git a/test/test_graph.py b/test/test_graph.py index 295117bd9..373bf773f 100644 --- a/test/test_graph.py +++ b/test/test_graph.py @@ -1,138 +1,137 @@ import os import json +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Graph -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_graph_base(fake_writer): - nodes = [ - {"name": "结点1", "symbolSize": 10}, - {"name": "结点2", "symbolSize": 20}, - {"name": "结点3", "symbolSize": 30}, - {"name": "结点4", "symbolSize": 40}, - ] - links = [] - for i in nodes: - for j in nodes: - links.append({"source": i.get("name"), "target": j.get("name")}) - c = Graph().add("", nodes, links, repulsion=8000) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_graph_base_v1(fake_writer): - nodes = [ - opts.GraphNode(name="结点1", symbol_size=10), - opts.GraphNode(name="结点2", symbol_size=20), - opts.GraphNode(name="结点3", symbol_size=30), - opts.GraphNode(name="结点4", symbol_size=40), - opts.GraphNode(name="结点5", symbol_size=50), - ] - links = [ - opts.GraphLink(source="结点1", target="结点2"), - opts.GraphLink(source="结点2", target="结点3"), - opts.GraphLink(source="结点3", target="结点4"), - opts.GraphLink(source="结点4", target="结点5"), - opts.GraphLink(source="结点5", target="结点1"), - ] - c = ( - Graph() - .add("", nodes, links, repulsion=4000) - .set_global_opts(title_opts=opts.TitleOpts(title="Graph-GraphNode-GraphLink")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_graph_base_v2(fake_writer): - with open( - os.path.join("test/fixtures", "les-miserables.json"), "r", encoding="utf-8" - ) as f: - j = json.load(f) - nodes = j["nodes"] - links = j["links"] - categories = j["categories"] - - categories = [opts.GraphCategory(name=d.get("name")) for d in categories] - - c = ( - Graph(init_opts=opts.InitOpts(width="1000px", height="600px")) - .add( - "", - nodes=nodes, - links=links, - categories=categories, - layout="circular", - is_rotate_label=True, - linestyle_opts=opts.LineStyleOpts(color="source", curve=0.3), - label_opts=opts.LabelOpts(position="right"), +class TestGraphChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_graph_base(self, fake_writer): + nodes = [ + {"name": "结点1", "symbolSize": 10}, + {"name": "结点2", "symbolSize": 20}, + {"name": "结点3", "symbolSize": 30}, + {"name": "结点4", "symbolSize": 40}, + ] + links = [] + for i in nodes: + for j in nodes: + links.append({"source": i.get("name"), "target": j.get("name")}) + c = Graph().add("", nodes, links, repulsion=8000) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_graph_base_v1(self, fake_writer): + nodes = [ + opts.GraphNode(name="结点1", symbol_size=10), + opts.GraphNode(name="结点2", symbol_size=20), + opts.GraphNode(name="结点3", symbol_size=30), + opts.GraphNode(name="结点4", symbol_size=40), + opts.GraphNode(name="结点5", symbol_size=50), + ] + links = [ + opts.GraphLink(source="结点1", target="结点2"), + opts.GraphLink(source="结点2", target="结点3"), + opts.GraphLink(source="结点3", target="结点4"), + opts.GraphLink(source="结点4", target="结点5"), + opts.GraphLink(source="结点5", target="结点1"), + ] + c = ( + Graph() + .add("", nodes, links, repulsion=4000) + .set_global_opts( + title_opts=opts.TitleOpts(title="Graph-GraphNode-GraphLink") + ) ) - .set_global_opts( - title_opts=opts.TitleOpts(title="Graph-Les Miserables"), - legend_opts=opts.LegendOpts( - orient="vertical", pos_left="2%", pos_top="20%" - ), + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_graph_base_v2(self, fake_writer): + with open( + os.path.join("test/fixtures", "les-miserables.json"), "r", encoding="utf-8" + ) as f: + j = json.load(f) + nodes = j["nodes"] + links = j["links"] + categories = j["categories"] + + categories = [opts.GraphCategory(name=d.get("name")) for d in categories] + + c = ( + Graph(init_opts=opts.InitOpts(width="1000px", height="600px")) + .add( + "", + nodes=nodes, + links=links, + categories=categories, + layout="circular", + is_rotate_label=True, + linestyle_opts=opts.LineStyleOpts(color="source", curve=0.3), + label_opts=opts.LabelOpts(position="right"), + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="Graph-Les Miserables"), + legend_opts=opts.LegendOpts( + orient="vertical", pos_left="2%", pos_top="20%" + ), + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_graph_draggable_and_symbol_size(fake_writer): - nodes = [ - {"name": "结点1", "symbolSize": 10}, - {"name": "结点2", "symbolSize": 20}, - {"name": "结点3", "symbolSize": 30}, - {"name": "结点4", "symbolSize": 40}, - ] - links = [] - for i in nodes: - for j in nodes: - links.append({"source": i.get("name"), "target": j.get("name")}) - c = Graph().add("", nodes, links, repulsion=8000, is_draggable=True, symbol_size=50) - c.render() - _, content = fake_writer.call_args[0] - assert_in("draggable", content) - assert_in("symbolSize", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_graph_edge_label_opts(fake_writer): - nodes = [ - {"name": "结点1", "symbolSize": 10}, - {"name": "结点2", "symbolSize": 20}, - {"name": "结点3", "symbolSize": 30}, - {"name": "结点4", "symbolSize": 40}, - ] - links = [] - for i in nodes: - for j in nodes: - links.append({"source": i.get("name"), "target": j.get("name")}) - c = Graph().add( - "", nodes, links, repulsion=4000, edge_label=opts.LabelOpts(is_show=True) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("edgeLabel", content) - - -def test_graph_item(): - node_name, link_source = "test_node_name", "test_link_source" - node = opts.GraphNode(name=node_name) - link = opts.GraphLink(source=link_source) - assert_equal(node_name, node.opts.get("name")) - assert_equal(link_source, link.opts.get("source")) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_graph_draggable_and_symbol_size(self, fake_writer): + nodes = [ + {"name": "结点1", "symbolSize": 10}, + {"name": "结点2", "symbolSize": 20}, + {"name": "结点3", "symbolSize": 30}, + {"name": "结点4", "symbolSize": 40}, + ] + links = [] + for i in nodes: + for j in nodes: + links.append({"source": i.get("name"), "target": j.get("name")}) + c = Graph().add( + "", nodes, links, repulsion=8000, is_draggable=True, symbol_size=50 + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("draggable", content) + self.assertIn("symbolSize", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_graph_edge_label_opts(self, fake_writer): + nodes = [ + {"name": "结点1", "symbolSize": 10}, + {"name": "结点2", "symbolSize": 20}, + {"name": "结点3", "symbolSize": 30}, + {"name": "结点4", "symbolSize": 40}, + ] + links = [] + for i in nodes: + for j in nodes: + links.append({"source": i.get("name"), "target": j.get("name")}) + c = Graph().add( + "", nodes, links, repulsion=4000, edge_label=opts.LabelOpts(is_show=True) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("edgeLabel", content) + + def test_graph_item(self): + node_name, link_source = "test_node_name", "test_link_source" + node = opts.GraphNode(name=node_name) + link = opts.GraphLink(source=link_source) + self.assertEqual(node_name, node.opts.get("name")) + self.assertEqual(link_source, link.opts.get("source")) diff --git a/test/test_graph_gl.py b/test/test_graph_gl.py index 9e1c59bc4..738aca537 100644 --- a/test/test_graph_gl.py +++ b/test/test_graph_gl.py @@ -1,61 +1,63 @@ import random +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import GraphGL -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_graph_gl_base(fake_writer): - nodes = [] - for i in range(50): - for j in range(50): - nodes.append( - opts.GraphGLNode( - x=random.random() * 958, - y=random.random() * 777, - value=1, - ) - ) - - links = [] - for i in range(50): - for j in range(50): - if i < 50 - 1: - links.append( - opts.GraphGLLink( - source=i + j * 50, - target=i + 1 + j * 50, +class TestGraphGLChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_graph_gl_base(self, fake_writer): + nodes = [] + for i in range(50): + for j in range(50): + nodes.append( + opts.GraphGLNode( + x=random.random() * 958, + y=random.random() * 777, value=1, ) ) - if j < 50 - 1: - links.append( - opts.GraphGLLink( - source=i + j * 50, - target=i + (j + 1) * 50, - value=1, + + links = [] + for i in range(50): + for j in range(50): + if i < 50 - 1: + links.append( + opts.GraphGLLink( + source=i + j * 50, + target=i + 1 + j * 50, + value=1, + ) + ) + if j < 50 - 1: + links.append( + opts.GraphGLLink( + source=i + j * 50, + target=i + (j + 1) * 50, + value=1, + ) ) - ) - c = ( - GraphGL(init_opts=opts.InitOpts()) - .add( - series_name="", - nodes=nodes, - links=links, - itemstyle_opts=opts.ItemStyleOpts(color="rgba(255,255,255,0.8)"), - linestyle_opts=opts.LineStyleOpts(color="rgba(255,255,255,0.8)", width=3), - force_atlas2_opts=opts.GraphGLForceAtlas2Opts( - steps=5, - edge_weight_influence=4, - ), + c = ( + GraphGL(init_opts=opts.InitOpts()) + .add( + series_name="", + nodes=nodes, + links=links, + itemstyle_opts=opts.ItemStyleOpts(color="rgba(255,255,255,0.8)"), + linestyle_opts=opts.LineStyleOpts( + color="rgba(255,255,255,0.8)", width=3 + ), + force_atlas2_opts=opts.GraphGLForceAtlas2Opts( + steps=5, + edge_weight_influence=4, + ), + ) + .set_dark_mode() ) - .set_dark_mode() - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "dark") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "dark") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_graphic.py b/test/test_graphic.py index 6855571a0..f05900060 100644 --- a/test/test_graphic.py +++ b/test/test_graphic.py @@ -1,62 +1,60 @@ -from nose.tools import assert_equal +import unittest from pyecharts.commons.utils import remove_key_with_none_value from pyecharts import options as opts -def test_graphic_group(): - group = opts.GraphicGroup( - graphic_item={"item": 1}, - is_diff_children_by_name=False, - ) - expected = { - "type": "group", - "diffChildrenByName": False, - "item": 1, - } - assert_equal(expected, remove_key_with_none_value(group.opts)) - - -def test_graphic_image(): - image = opts.GraphicImage( - graphic_item={"item": 1}, graphic_imagestyle_opts={"opts": 1} - ) - expected = { - "type": "image", - "item": 1, - "style": { - "opts": 1, - }, - } - assert_equal(expected, remove_key_with_none_value(image.opts)) - - -def test_graphic_text(): - text = opts.GraphicText( - graphic_item={"item": 1}, graphic_textstyle_opts={"opts": 1} - ) - expected = { - "type": "text", - "item": 1, - "style": { - "opts": 1, - }, - } - assert_equal(expected, remove_key_with_none_value(text.opts)) - - -def test_graphic_rect(): - rect = opts.GraphicRect( - graphic_item={"item": 1}, - graphic_shape_opts={"shape": 1}, - graphic_basicstyle_opts={"opts": 1}, - ) - expected = { - "type": "rect", - "item": 1, - "shape": 1, - "style": { - "opts": 1, - }, - } - assert_equal(expected, remove_key_with_none_value(rect.opts)) +class TestGraphicComponent(unittest.TestCase): + def test_graphic_group(self): + group = opts.GraphicGroup( + graphic_item={"item": 1}, + is_diff_children_by_name=False, + ) + expected = { + "type": "group", + "diffChildrenByName": False, + "item": 1, + } + self.assertEqual(expected, remove_key_with_none_value(group.opts)) + + def test_graphic_image(self): + image = opts.GraphicImage( + graphic_item={"item": 1}, graphic_imagestyle_opts={"opts": 1} + ) + expected = { + "type": "image", + "item": 1, + "style": { + "opts": 1, + }, + } + self.assertEqual(expected, remove_key_with_none_value(image.opts)) + + def test_graphic_text(self): + text = opts.GraphicText( + graphic_item={"item": 1}, graphic_textstyle_opts={"opts": 1} + ) + expected = { + "type": "text", + "item": 1, + "style": { + "opts": 1, + }, + } + self.assertEqual(expected, remove_key_with_none_value(text.opts)) + + def test_graphic_rect(self): + rect = opts.GraphicRect( + graphic_item={"item": 1}, + graphic_shape_opts={"shape": 1}, + graphic_basicstyle_opts={"opts": 1}, + ) + expected = { + "type": "rect", + "item": 1, + "shape": 1, + "style": { + "opts": 1, + }, + } + self.assertEqual(expected, remove_key_with_none_value(rect.opts)) diff --git a/test/test_grid.py b/test/test_grid.py index fc9b2132d..5c0dab215 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -1,453 +1,464 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Bar, Grid, Line, Radar, Geo, Map from pyecharts.globals import ThemeType, ChartType from pyecharts.faker import Faker -def _chart_for_grid() -> Bar: - x_data = ["{}月".format(i) for i in range(1, 13)] - bar = ( - Bar() - .add_xaxis(x_data) - .add_yaxis( - "蒸发量", - [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], - yaxis_index=0, - ) - .add_yaxis( - "降水量", - [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], - yaxis_index=1, - ) - .extend_axis(yaxis=opts.AxisOpts(name="蒸发量", type_="value", position="right")) - .extend_axis(yaxis=opts.AxisOpts(type_="value", name="温度", position="left")) - .set_global_opts( - yaxis_opts=opts.AxisOpts(name="降水量", position="right", offset=80) +class TestGridComponent(unittest.TestCase): + def _chart_for_grid(self) -> Bar: + x_data = ["{}月".format(i) for i in range(1, 13)] + bar = ( + Bar() + .add_xaxis(x_data) + .add_yaxis( + "蒸发量", + [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], + yaxis_index=0, + ) + .add_yaxis( + "降水量", + [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], + yaxis_index=1, + ) + .extend_axis( + yaxis=opts.AxisOpts(name="蒸发量", type_="value", position="right") + ) + .extend_axis( + yaxis=opts.AxisOpts(type_="value", name="温度", position="left") + ) + .set_global_opts( + yaxis_opts=opts.AxisOpts(name="降水量", position="right", offset=80) + ) ) - ) - line = ( - Line() - .add_xaxis(x_data) - .add_yaxis( - "平均温度", - [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], - yaxis_index=2, - color="#675bba", - label_opts=opts.LabelOpts(is_show=False), + line = ( + Line() + .add_xaxis(x_data) + .add_yaxis( + "平均温度", + [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], + yaxis_index=2, + color="#675bba", + label_opts=opts.LabelOpts(is_show=False), + ) ) - ) - bar.overlap(line) - return bar - - -def _chart_for_grid_with_datazoom() -> Bar: - bar_1 = ( - Bar() - .add_xaxis(Faker.days_attrs) - .add_yaxis("商家", Faker.days_values) - .set_global_opts( - title_opts=opts.TitleOpts(title="Bar-DataZoom"), - datazoom_opts=opts.DataZoomOpts(), - toolbox_opts=opts.ToolboxOpts( - feature=opts.ToolBoxFeatureOpts( - save_as_image=opts.ToolBoxFeatureSaveAsImageOpts( - exclude_components=["dataZoom", "toolbox"], - ) + bar.overlap(line) + return bar + + def _chart_for_grid_with_datazoom(self) -> Bar: + bar_1 = ( + Bar() + .add_xaxis(Faker.days_attrs) + .add_yaxis("商家", Faker.days_values) + .set_global_opts( + title_opts=opts.TitleOpts(title="Bar-DataZoom"), + datazoom_opts=opts.DataZoomOpts(), + toolbox_opts=opts.ToolboxOpts( + feature=opts.ToolBoxFeatureOpts( + save_as_image=opts.ToolBoxFeatureSaveAsImageOpts( + exclude_components=["dataZoom", "toolbox"], + ) + ), ), - ), - legend_opts=opts.LegendOpts(), + legend_opts=opts.LegendOpts(), + ) ) - ) - return bar_1 - - -def test_grid_control_axis_index(): - bar = _chart_for_grid() - gc = Grid().add( - bar, opts.GridOpts(pos_left="5%", pos_right="20%"), is_control_axis_index=True - ) - expected_idx = (0, 1, 2) - for idx, series in enumerate(gc.options.get("series")): - assert_equal(series.get("yAxisIndex"), expected_idx[idx]) - - -def test_grid_do_not_control_axis_index(): - bar = _chart_for_grid() - gc = Grid().add(bar, opts.GridOpts(pos_left="5%", pos_right="20%")) - expected_idx = (0, 0, 0) - for idx, series in enumerate(gc.options.get("series")): - assert_equal(series.get("yAxisIndex"), expected_idx[idx]) - - -def test_grid_with_more_datazoom_opts(): - bar_1 = _chart_for_grid_with_datazoom() - bar_2 = _chart_for_grid_with_datazoom() - grid_1 = ( - Grid() - .add(chart=bar_1, grid_opts=opts.GridOpts()) - .add(chart=bar_2, grid_opts=opts.GridOpts()) - ) - expected_datazoom_opts_len = 2 - assert_equal(len(grid_1.options.get("dataZoom")), expected_datazoom_opts_len) + return bar_1 - bar_3 = _chart_for_grid() - bar_4 = _chart_for_grid_with_datazoom() - grid_2 = ( - Grid() - .add(chart=bar_3, grid_opts=opts.GridOpts()) - .add(chart=bar_4, grid_opts=opts.GridOpts()) - ) - expected_datazoom_opts_len = 1 - assert_equal(len(grid_2.options.get("dataZoom")), expected_datazoom_opts_len) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_grid_options(fake_writer): - bar = _chart_for_grid() - gc = Grid(init_opts=opts.InitOpts(theme=ThemeType.ESSOS)).add( - bar, opts.GridOpts(pos_left="5%", pos_right="20%", is_contain_label=True) - ) - gc.render() - _, content = fake_writer.call_args[0] - assert_in("containLabel", content) - assert_in("color", content) - - -def _chart_for_grid_v2(): - x_data = ["{}月".format(i) for i in range(1, 13)] - bar = ( - Bar() - .add_xaxis(x_data) - .add_yaxis( - "蒸发量", - [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], - yaxis_index=0, - color="red", + def test_grid_control_axis_index(self): + bar = self._chart_for_grid() + gc = Grid().add( + bar, + opts.GridOpts(pos_left="5%", pos_right="20%"), + is_control_axis_index=True, ) - .add_yaxis( - "降水量", - [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], - yaxis_index=1, - color="green", + expected_idx = (0, 1, 2) + for idx, series in enumerate(gc.options.get("series")): + self.assertEqual(series.get("yAxisIndex"), expected_idx[idx]) + + def test_grid_do_not_control_axis_index(self): + bar = self._chart_for_grid() + gc = Grid().add(bar, opts.GridOpts(pos_left="5%", pos_right="20%")) + expected_idx = (0, 0, 0) + for idx, series in enumerate(gc.options.get("series")): + self.assertEqual(series.get("yAxisIndex"), expected_idx[idx]) + + def test_grid_with_more_datazoom_opts(self): + bar_1 = self._chart_for_grid_with_datazoom() + bar_2 = self._chart_for_grid_with_datazoom() + grid_1 = ( + Grid() + .add(chart=bar_1, grid_opts=opts.GridOpts()) + .add(chart=bar_2, grid_opts=opts.GridOpts()) ) - .extend_axis(yaxis=opts.AxisOpts(name="蒸发量", type_="value", position="right")) - .extend_axis(yaxis=opts.AxisOpts(type_="value", name="温度", position="left")) - .set_global_opts( - yaxis_opts=opts.AxisOpts(name="降水量", position="right", offset=80) + expected_datazoom_opts_len = 2 + self.assertEqual( + len(grid_1.options.get("dataZoom")), expected_datazoom_opts_len ) - ) - line = ( - Line() - .add_xaxis(x_data) - .add_yaxis( - "平均温度", - [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], - yaxis_index=2, - color="blue", - label_opts=opts.LabelOpts(is_show=False), + bar_3 = self._chart_for_grid() + bar_4 = self._chart_for_grid_with_datazoom() + grid_2 = ( + Grid() + .add(chart=bar_3, grid_opts=opts.GridOpts()) + .add(chart=bar_4, grid_opts=opts.GridOpts()) + ) + expected_datazoom_opts_len = 1 + self.assertEqual( + len(grid_2.options.get("dataZoom")), expected_datazoom_opts_len ) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) - - bar.overlap(line) - assert_equal(bar.colors[:3], ["red", "green", "blue"]) - return bar - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_grid_example_1(fake_writer): - bar = _chart_for_grid_v2() - gc = Grid().add( - bar, opts.GridOpts(pos_left="5%", pos_right="20%"), is_control_axis_index=True - ) - expected_idx = (0, 1, 2) - for idx, series in enumerate(gc.options.get("series")): - assert_equal(series.get("yAxisIndex"), expected_idx[idx]) - gc.render() - _, content = fake_writer.call_args[0] - assert_in("yAxisIndex", content) - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_grid_geo_bar(fake_writer): - bar = ( - Bar() - .add_xaxis(Faker.choose()) - .add_yaxis("商家A", Faker.values()) - .add_yaxis("商家B", Faker.values()) - .set_global_opts( - title_opts=dict(title="Bar 图"), - legend_opts=opts.LegendOpts(pos_left="20%"), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_grid_options(self, fake_writer): + bar = self._chart_for_grid() + gc = Grid(init_opts=opts.InitOpts(theme=ThemeType.ESSOS)).add( + bar, opts.GridOpts(pos_left="5%", pos_right="20%", is_contain_label=True) ) - ) - geo = ( - Geo() - .add_schema(maptype="china") - .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts( - visualmap_opts=opts.VisualMapOpts(), - title_opts=dict(title="Geo 图"), + gc.render() + _, content = fake_writer.call_args[0] + self.assertIn("containLabel", content) + self.assertIn("color", content) + + def _chart_for_grid_v2(self): + x_data = ["{}月".format(i) for i in range(1, 13)] + bar = ( + Bar() + .add_xaxis(x_data) + .add_yaxis( + "蒸发量", + [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], + yaxis_index=0, + color="red", + ) + .add_yaxis( + "降水量", + [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], + yaxis_index=1, + color="green", + ) + .extend_axis( + yaxis=opts.AxisOpts(name="蒸发量", type_="value", position="right") + ) + .extend_axis( + yaxis=opts.AxisOpts(type_="value", name="温度", position="left") + ) + .set_global_opts( + yaxis_opts=opts.AxisOpts(name="降水量", position="right", offset=80) + ) ) - ) - grid = ( - Grid() - .add(bar, grid_opts=opts.GridOpts(pos_top="50%", pos_right="75%")) - .add(geo, grid_opts=opts.GridOpts(pos_left="60%")) - ) - grid.render() - _, content = fake_writer.call_args[0] - assert_in("geo", content) + line = ( + Line() + .add_xaxis(x_data) + .add_yaxis( + "平均温度", + [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], + yaxis_index=2, + color="blue", + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) + ) + bar.overlap(line) + self.assertEqual(bar.colors[:3], ["red", "green", "blue"]) + return bar -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_grid_two_radar(fake_writer): - schema = [ - opts.RadarIndicatorItem(name="销售", max_=6500), - opts.RadarIndicatorItem(name="管理", max_=16000), - opts.RadarIndicatorItem(name="信息技术", max_=30000), - opts.RadarIndicatorItem(name="客服", max_=38000), - opts.RadarIndicatorItem(name="研发", max_=52000), - opts.RadarIndicatorItem(name="市场", max_=25000), - ] - c = ( - Radar() - .add_schema(schema=schema, center=["25%", "50%"]) - .add( - series_name="预算分配", - data=[[4300, 10000, 28000, 35000, 50000, 19000]], - radar_index=0, + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_grid_example_1(self, fake_writer): + bar = self._chart_for_grid_v2() + gc = Grid().add( + bar, + opts.GridOpts(pos_left="5%", pos_right="20%"), + is_control_axis_index=True, ) - .set_global_opts(legend_opts=opts.LegendOpts(pos_left="20%")) - ) - c2 = ( - Radar() - .add_schema(schema=schema, center=["75%", "50%"]) - .add( - series_name="实际开销", - data=[[5000, 14000, 28000, 31000, 42000, 21000]], - radar_index=1, + expected_idx = (0, 1, 2) + for idx, series in enumerate(gc.options.get("series")): + self.assertEqual(series.get("yAxisIndex"), expected_idx[idx]) + gc.render() + _, content = fake_writer.call_args[0] + self.assertIn("yAxisIndex", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_grid_geo_bar(self, fake_writer): + bar = ( + Bar() + .add_xaxis(Faker.choose()) + .add_yaxis("商家A", Faker.values()) + .add_yaxis("商家B", Faker.values()) + .set_global_opts( + title_opts=dict(title="Bar 图"), + legend_opts=opts.LegendOpts(pos_left="20%"), + ) + ) + geo = ( + Geo() + .add_schema(maptype="china") + .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())]) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(), + title_opts=dict(title="Geo 图"), + ) ) - .set_global_opts(legend_opts=opts.LegendOpts(pos_right="20%")) - ) - - grid = Grid().add(c, grid_opts=opts.GridOpts()).add(c2, grid_opts=opts.GridOpts()) - grid.render() - _, content = fake_writer.call_args[0] - assert_in("radar", content) - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_grid_mutil_yaxis(fake_writer): - bar = ( - Bar() - .add_xaxis(["{}月".format(i) for i in range(1, 13)]) - .add_yaxis( - "蒸发量", - [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], - yaxis_index=0, - color="#d14a61", + grid = ( + Grid() + .add(bar, grid_opts=opts.GridOpts(pos_top="50%", pos_right="75%")) + .add(geo, grid_opts=opts.GridOpts(pos_left="60%")) ) - .add_yaxis( - "降水量", - [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], - yaxis_index=1, - color="#5793f3", + grid.render() + _, content = fake_writer.call_args[0] + self.assertIn("geo", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_grid_two_radar(self, fake_writer): + schema = [ + opts.RadarIndicatorItem(name="销售", max_=6500), + opts.RadarIndicatorItem(name="管理", max_=16000), + opts.RadarIndicatorItem(name="信息技术", max_=30000), + opts.RadarIndicatorItem(name="客服", max_=38000), + opts.RadarIndicatorItem(name="研发", max_=52000), + opts.RadarIndicatorItem(name="市场", max_=25000), + ] + c = ( + Radar() + .add_schema(schema=schema, center=["25%", "50%"]) + .add( + series_name="预算分配", + data=[[4300, 10000, 28000, 35000, 50000, 19000]], + radar_index=0, + ) + .set_global_opts(legend_opts=opts.LegendOpts(pos_left="20%")) ) - .extend_axis( - yaxis=opts.AxisOpts( - name="蒸发量", - type_="value", - min_=0, - max_=250, - position="right", - axisline_opts=opts.AxisLineOpts( - linestyle_opts=opts.LineStyleOpts(color="#d14a61") - ), - axislabel_opts=opts.LabelOpts(formatter="{value} ml"), + c2 = ( + Radar() + .add_schema(schema=schema, center=["75%", "50%"]) + .add( + series_name="实际开销", + data=[[5000, 14000, 28000, 31000, 42000, 21000]], + radar_index=1, ) + .set_global_opts(legend_opts=opts.LegendOpts(pos_right="20%")) + ) + + grid = ( + Grid().add(c, grid_opts=opts.GridOpts()).add(c2, grid_opts=opts.GridOpts()) ) - .extend_axis( - yaxis=opts.AxisOpts( - type_="value", - name="温度", - min_=0, - max_=25, - position="left", - axisline_opts=opts.AxisLineOpts( - linestyle_opts=opts.LineStyleOpts(color="#675bba") + grid.render() + _, content = fake_writer.call_args[0] + self.assertIn("radar", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_grid_mutil_yaxis(self, fake_writer): + bar = ( + Bar() + .add_xaxis(["{}月".format(i) for i in range(1, 13)]) + .add_yaxis( + "蒸发量", + [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], + yaxis_index=0, + color="#d14a61", + ) + .add_yaxis( + "降水量", + [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], + yaxis_index=1, + color="#5793f3", + ) + .extend_axis( + yaxis=opts.AxisOpts( + name="蒸发量", + type_="value", + min_=0, + max_=250, + position="right", + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#d14a61") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} ml"), + ) + ) + .extend_axis( + yaxis=opts.AxisOpts( + type_="value", + name="温度", + min_=0, + max_=25, + position="left", + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#675bba") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} °C"), + splitline_opts=opts.SplitLineOpts( + is_show=True, linestyle_opts=opts.LineStyleOpts(opacity=1) + ), + ) + ) + .set_global_opts( + yaxis_opts=opts.AxisOpts( + name="降水量", + min_=0, + max_=250, + position="right", + offset=80, + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#5793f3") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} ml"), ), - axislabel_opts=opts.LabelOpts(formatter="{value} °C"), - splitline_opts=opts.SplitLineOpts( - is_show=True, linestyle_opts=opts.LineStyleOpts(opacity=1) + title_opts=opts.TitleOpts(title="Grid-Overlap-多 X/Y 轴示例"), + tooltip_opts=opts.TooltipOpts( + trigger="axis", axis_pointer_type="cross" ), + legend_opts=opts.LegendOpts(pos_left="25%"), ) ) - .set_global_opts( - yaxis_opts=opts.AxisOpts( - name="降水量", - min_=0, - max_=250, - position="right", - offset=80, - axisline_opts=opts.AxisLineOpts( - linestyle_opts=opts.LineStyleOpts(color="#5793f3") - ), - axislabel_opts=opts.LabelOpts(formatter="{value} ml"), - ), - title_opts=opts.TitleOpts(title="Grid-Overlap-多 X/Y 轴示例"), - tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"), - legend_opts=opts.LegendOpts(pos_left="25%"), - ) - ) - line = ( - Line() - .add_xaxis(["{}月".format(i) for i in range(1, 13)]) - .add_yaxis( - "平均温度", - [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], - yaxis_index=2, - color="#675bba", - label_opts=opts.LabelOpts(is_show=False), + line = ( + Line() + .add_xaxis(["{}月".format(i) for i in range(1, 13)]) + .add_yaxis( + "平均温度", + [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], + yaxis_index=2, + color="#675bba", + label_opts=opts.LabelOpts(is_show=False), + ) ) - ) - bar1 = ( - Bar() - .add_xaxis(["{}月".format(i) for i in range(1, 13)]) - .add_yaxis( - "蒸发量 1", - [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], - color="#d14a61", - xaxis_index=1, - yaxis_index=3, - ) - .add_yaxis( - "降水量 2", - [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], - color="#5793f3", - xaxis_index=1, - yaxis_index=3, - ) - .extend_axis( - yaxis=opts.AxisOpts( - name="蒸发量", - type_="value", - min_=0, - max_=250, - position="right", - axisline_opts=opts.AxisLineOpts( - linestyle_opts=opts.LineStyleOpts(color="#d14a61") - ), - axislabel_opts=opts.LabelOpts(formatter="{value} ml"), + bar1 = ( + Bar() + .add_xaxis(["{}月".format(i) for i in range(1, 13)]) + .add_yaxis( + "蒸发量 1", + [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], + color="#d14a61", + xaxis_index=1, + yaxis_index=3, ) - ) - .extend_axis( - yaxis=opts.AxisOpts( - type_="value", - name="温度", - min_=0, - max_=25, - position="left", - axisline_opts=opts.AxisLineOpts( - linestyle_opts=opts.LineStyleOpts(color="#675bba") + .add_yaxis( + "降水量 2", + [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], + color="#5793f3", + xaxis_index=1, + yaxis_index=3, + ) + .extend_axis( + yaxis=opts.AxisOpts( + name="蒸发量", + type_="value", + min_=0, + max_=250, + position="right", + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#d14a61") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} ml"), + ) + ) + .extend_axis( + yaxis=opts.AxisOpts( + type_="value", + name="温度", + min_=0, + max_=25, + position="left", + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#675bba") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} °C"), + splitline_opts=opts.SplitLineOpts( + is_show=True, linestyle_opts=opts.LineStyleOpts(opacity=1) + ), + ) + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts(grid_index=1), + yaxis_opts=opts.AxisOpts( + name="降水量", + min_=0, + max_=250, + position="right", + offset=80, + grid_index=1, + axisline_opts=opts.AxisLineOpts( + linestyle_opts=opts.LineStyleOpts(color="#5793f3") + ), + axislabel_opts=opts.LabelOpts(formatter="{value} ml"), ), - axislabel_opts=opts.LabelOpts(formatter="{value} °C"), - splitline_opts=opts.SplitLineOpts( - is_show=True, linestyle_opts=opts.LineStyleOpts(opacity=1) + tooltip_opts=opts.TooltipOpts( + trigger="axis", axis_pointer_type="cross" ), + legend_opts=opts.LegendOpts(pos_left="65%"), ) ) - .set_global_opts( - xaxis_opts=opts.AxisOpts(grid_index=1), - yaxis_opts=opts.AxisOpts( - name="降水量", - min_=0, - max_=250, - position="right", - offset=80, - grid_index=1, - axisline_opts=opts.AxisLineOpts( - linestyle_opts=opts.LineStyleOpts(color="#5793f3") - ), - axislabel_opts=opts.LabelOpts(formatter="{value} ml"), - ), - tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"), - legend_opts=opts.LegendOpts(pos_left="65%"), - ) - ) - line1 = ( - Line() - .add_xaxis(["{}月".format(i) for i in range(1, 13)]) - .add_yaxis( - "平均温度 1", - [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], - color="#675bba", - label_opts=opts.LabelOpts(is_show=False), - xaxis_index=1, - yaxis_index=5, + line1 = ( + Line() + .add_xaxis(["{}月".format(i) for i in range(1, 13)]) + .add_yaxis( + "平均温度 1", + [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2], + color="#675bba", + label_opts=opts.LabelOpts(is_show=False), + xaxis_index=1, + yaxis_index=5, + ) ) - ) - overlap_1 = bar.overlap(line) - overlap_2 = bar1.overlap(line1) + overlap_1 = bar.overlap(line) + overlap_2 = bar1.overlap(line1) - grid = ( - Grid(init_opts=opts.InitOpts(width="1200px", height="800px")) - .add( - overlap_1, - grid_opts=opts.GridOpts(pos_right="58%"), - is_control_axis_index=True, - ) - .add( - overlap_2, - grid_opts=opts.GridOpts(pos_left="58%"), - is_control_axis_index=True, + grid = ( + Grid(init_opts=opts.InitOpts(width="1200px", height="800px")) + .add( + overlap_1, + grid_opts=opts.GridOpts(pos_right="58%"), + is_control_axis_index=True, + ) + .add( + overlap_2, + grid_opts=opts.GridOpts(pos_left="58%"), + is_control_axis_index=True, + ) ) - ) - grid.render() - _, content = fake_writer.call_args[0] - assert_in("xAxis", content) - assert_in("yAxis", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_grid_geo_map(fake_writer): - data_pair = [list(z) for z in zip(Faker.provinces, Faker.values())] - geo_chart = ( - Geo() - .add_schema(maptype="china") - .add( - "geo", - data_pair=data_pair, - type_=ChartType.SCATTER, + grid.render() + _, content = fake_writer.call_args[0] + self.assertIn("xAxis", content) + self.assertIn("yAxis", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_grid_geo_map(self, fake_writer): + data_pair = [list(z) for z in zip(Faker.provinces, Faker.values())] + geo_chart = ( + Geo() + .add_schema(maptype="china") + .add( + "geo", + data_pair=data_pair, + type_=ChartType.SCATTER, + ) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(), + ) ) - .set_global_opts( - visualmap_opts=opts.VisualMapOpts(), + map_chart = ( + Map() + .add("LCOH", data_pair=data_pair, maptype="china") + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(), + ) ) - ) - map_chart = ( - Map() - .add("LCOH", data_pair=data_pair, maptype="china") - .set_global_opts( - visualmap_opts=opts.VisualMapOpts(), + grid_chart = ( + Grid(init_opts=opts.InitOpts()) + .add(map_chart, grid_opts=opts.GridOpts()) + .add(geo_chart, grid_opts=opts.GridOpts()) ) - ) - grid_chart = ( - Grid(init_opts=opts.InitOpts()) - .add(map_chart, grid_opts=opts.GridOpts()) - .add(geo_chart, grid_opts=opts.GridOpts()) - ) - grid_chart.render() - _, content = fake_writer.call_args[0] - assert_in("geo", content) + grid_chart.render() + _, content = fake_writer.call_args[0] + self.assertIn("geo", content) diff --git a/test/test_heatmap.py b/test/test_heatmap.py index 7be76c5e9..cf34441d0 100644 --- a/test/test_heatmap.py +++ b/test/test_heatmap.py @@ -1,23 +1,23 @@ import random +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import HeatMap from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_heatmap_base(fake_writer): - value = [[i, j, random.randint(0, 50)] for i in range(24) for j in range(7)] - c = ( - HeatMap() - .add_xaxis(Faker.clock) - .add_yaxis("series0", Faker.week, value) - .set_global_opts(visualmap_opts=opts.VisualMapOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") +class TestHeatmapChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_heatmap_base(self, fake_writer): + value = [[i, j, random.randint(0, 50)] for i in range(24) for j in range(7)] + c = ( + HeatMap() + .add_xaxis(Faker.clock) + .add_yaxis("series0", Faker.week, value) + .set_global_opts(visualmap_opts=opts.VisualMapOpts()) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_image.py b/test/test_image.py index e3c87b559..268264b6e 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -1,7 +1,6 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_in - from pyecharts import options as opts from pyecharts.components import Image from pyecharts.globals import CurrentConfig, NotebookType @@ -15,32 +14,30 @@ def _gen_image() -> Image: return image -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_image_base(fake_writer): - image = _gen_image() - image.render() - _, content = fake_writer.call_args[0] - assert_in(TEST_SRC, content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_image_base_v1(fake_writer): - image = Image() - image.add(src=TEST_SRC, style_opts={"align": "center"}) - image.set_global_opts(title_opts=opts.ComponentTitleOpts(title="Image Test")) - image.render() - _, content = fake_writer.call_args[0] - assert_in(TEST_SRC, content) - - -def test_image_render_notebook(): - CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK - image = _gen_image() - content = image.render_notebook().__html__() - assert_in(TEST_SRC, content) - - -def test_images_render_embed(): - image = _gen_image() - s = image.render_embed() - assert s is not None +class TestImageComponent(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_image_base(self, fake_writer): + image = _gen_image() + image.render() + _, content = fake_writer.call_args[0] + self.assertIn(TEST_SRC, content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_image_base_v1(self, fake_writer): + image = Image() + image.add(src=TEST_SRC, style_opts={"align": "center"}) + image.set_global_opts(title_opts=opts.ComponentTitleOpts(title="Image Test")) + image.render() + _, content = fake_writer.call_args[0] + self.assertIn(TEST_SRC, content) + + def test_image_render_notebook(self): + CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK + image = _gen_image() + content = image.render_notebook().__html__() + self.assertIn(TEST_SRC, content) + + def test_images_render_embed(self): + image = _gen_image() + s = image.render_embed() + assert s is not None diff --git a/test/test_item_style_options.py b/test/test_item_style_options.py deleted file mode 100644 index bfe69bc6d..000000000 --- a/test/test_item_style_options.py +++ /dev/null @@ -1,8 +0,0 @@ -from nose.tools import assert_equal - -from pyecharts import options as opts - - -def test_area_color_in_item_styles(): - op = opts.ItemStyleOpts(area_color="red") - assert_equal(op.opts["areaColor"], "red") diff --git a/test/test_kline.py b/test/test_kline.py index cb8f632f5..193d95e0b 100644 --- a/test/test_kline.py +++ b/test/test_kline.py @@ -1,7 +1,6 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Kline @@ -19,62 +18,62 @@ ] -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_kline_base(fake_writer): - c = ( - Kline() - .add_xaxis(["2017/7/{}".format(i + 1) for i in range(10)]) - .add_yaxis("kline", data) - .set_global_opts( - yaxis_opts=opts.AxisOpts(is_scale=True), - xaxis_opts=opts.AxisOpts(is_scale=True), +class TestKlineChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_kline_base(self, fake_writer): + c = ( + Kline() + .add_xaxis(["2017/7/{}".format(i + 1) for i in range(10)]) + .add_yaxis("kline", data) + .set_global_opts( + yaxis_opts=opts.AxisOpts(is_scale=True), + xaxis_opts=opts.AxisOpts(is_scale=True), + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_kline_item_base(fake_writer): - x_axis = ["2017/7/{}".format(i + 1) for i in range(10)] - y_axis = data - kline_item = [ - opts.CandleStickItem(name=d[0], value=d[1]) for d in list(zip(x_axis, y_axis)) - ] + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_kline_item_base(self, fake_writer): + x_axis = ["2017/7/{}".format(i + 1) for i in range(10)] + y_axis = data + kline_item = [ + opts.CandleStickItem(name=d[0], value=d[1]) + for d in list(zip(x_axis, y_axis)) + ] - c = ( - Kline() - .add_xaxis(x_axis) - .add_yaxis("kline", kline_item) - .set_global_opts( - yaxis_opts=opts.AxisOpts(is_scale=True), - xaxis_opts=opts.AxisOpts(is_scale=True), + c = ( + Kline() + .add_xaxis(x_axis) + .add_yaxis("kline", kline_item) + .set_global_opts( + yaxis_opts=opts.AxisOpts(is_scale=True), + xaxis_opts=opts.AxisOpts(is_scale=True), + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_kline_axispointer_opts(fake_writer): - c = ( - Kline() - .add_xaxis(["2017/7/{}".format(i + 1) for i in range(10)]) - .add_yaxis("kline", data) - .set_global_opts( - yaxis_opts=opts.AxisOpts(is_scale=True), - xaxis_opts=opts.AxisOpts(is_scale=True), - axispointer_opts=opts.AxisPointerOpts( - is_show=True, - link=[{"xAxisIndex": "all"}], - label=opts.LabelOpts(background_color="#777"), - ), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_kline_axispointer_opts(self, fake_writer): + c = ( + Kline() + .add_xaxis(["2017/7/{}".format(i + 1) for i in range(10)]) + .add_yaxis("kline", data) + .set_global_opts( + yaxis_opts=opts.AxisOpts(is_scale=True), + xaxis_opts=opts.AxisOpts(is_scale=True), + axispointer_opts=opts.AxisPointerOpts( + is_show=True, + link=[{"xAxisIndex": "all"}], + label=opts.LabelOpts(background_color="#777"), + ), + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("axisPointer", content) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("axisPointer", content) diff --git a/test/test_line.py b/test/test_line.py index a0c07d422..b19380933 100644 --- a/test/test_line.py +++ b/test/test_line.py @@ -1,151 +1,145 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Line -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_line_base(fake_writer): - c = ( - Line() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_line_with_emphasis(fake_writer): - c = ( - Line() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4], emphasis_opts=opts.EmphasisOpts()) - .add_yaxis("series1", [2, 3, 6], emphasis_opts=opts.EmphasisOpts()) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("emphasis", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_line_item_base(fake_writer): - x_axis = ["A", "B", "C"] - y_axis_0 = [1, 2, 4] - line_item_0 = [ - opts.LineItem(name=d[0], value=d[1]) for d in list(zip(x_axis, y_axis_0)) - ] - y_axis_1 = [2, 3, 6] - line_item_1 = [ - opts.LineItem(name=d[0], value=d[1]) for d in list(zip(x_axis, y_axis_1)) - ] - - c = ( - Line() - .add_xaxis(x_axis) - .add_yaxis("series0", line_item_0) - .add_yaxis("series1", line_item_1) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_set_global_opts(fake_writer): - test_type, test_range = "test_type", [-10001, 10001] - c = ( - Line() - .set_global_opts( - xaxis_opts=opts.AxisOpts(is_inverse=True), - datazoom_opts=opts.DataZoomOpts(type_=test_type), - visualmap_opts=opts.VisualMapOpts(type_="size", range_size=test_range), +class TestLineChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_line_base(self, fake_writer): + c = ( + Line() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) ) - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in('"inverse": true', content) - assert_in("test_type", content) - assert_in("-10001", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_data_label_none_animation_opts(fake_writer): - c = ( - Line() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4], is_hover_animation=False) - .add_yaxis("series1", [2, 3, 6], is_hover_animation=False) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("hoverAnimation", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_line_opts_with_zlevel_z(fake_writer): - c = ( - Line() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6], z_level=2, z=1) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("zlevel", content) - assert_in("z", content) - - -def test_line_dataset(): - c = ( - Line() - .add_dataset( - source=[ - ["product", "2012", "2013", "2014", "2015", "2016", "2017"], - ["Milk Tea", 56.5, 82.1, 88.7, 70.1, 53.4, 85.1], - ["Matcha Latte", 51.1, 51.4, 55.1, 53.3, 73.8, 68.7], - ["Cheese Cocoa", 40.1, 62.2, 69.5, 36.4, 45.2, 32.5], - ["Walnut Brownie", 25.2, 37.1, 41.2, 18, 33.9, 49.1], - ] + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_line_with_emphasis(self, fake_writer): + c = ( + Line() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4], emphasis_opts=opts.EmphasisOpts()) + .add_yaxis("series1", [2, 3, 6], emphasis_opts=opts.EmphasisOpts()) ) - .add_yaxis( - series_name="Milk Tea", - y_axis=[], - is_smooth=True, - series_layout_by="row", + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("emphasis", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_line_item_base(self, fake_writer): + x_axis = ["A", "B", "C"] + y_axis_0 = [1, 2, 4] + line_item_0 = [ + opts.LineItem(name=d[0], value=d[1]) for d in list(zip(x_axis, y_axis_0)) + ] + y_axis_1 = [2, 3, 6] + line_item_1 = [ + opts.LineItem(name=d[0], value=d[1]) for d in list(zip(x_axis, y_axis_1)) + ] + + c = ( + Line() + .add_xaxis(x_axis) + .add_yaxis("series0", line_item_0) + .add_yaxis("series1", line_item_1) ) - .add_yaxis( - series_name="Matcha Latte", - y_axis=[], - is_smooth=True, - series_layout_by="row", + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_set_global_opts(self, fake_writer): + test_type, test_range = "test_type", [-10001, 10001] + c = ( + Line() + .set_global_opts( + xaxis_opts=opts.AxisOpts(is_inverse=True), + datazoom_opts=opts.DataZoomOpts(type_=test_type), + visualmap_opts=opts.VisualMapOpts(type_="size", range_size=test_range), + ) + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) ) - .add_yaxis( - series_name="Cheese Cocoa", - y_axis=[], - is_smooth=True, - series_layout_by="row", + c.render() + _, content = fake_writer.call_args[0] + self.assertIn('"inverse": true', content) + self.assertIn("test_type", content) + self.assertIn("-10001", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_data_label_none_animation_opts(self, fake_writer): + c = ( + Line() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4], is_hover_animation=False) + .add_yaxis("series1", [2, 3, 6], is_hover_animation=False) ) - .add_yaxis( - series_name="Walnut Brownie", - y_axis=[], - is_smooth=True, - series_layout_by="row", + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("hoverAnimation", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_line_opts_with_zlevel_z(self, fake_writer): + c = ( + Line() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6], z_level=2, z=1) ) - .set_global_opts( - title_opts=opts.TitleOpts(title="Dataset Line Example"), - xaxis_opts=opts.AxisOpts(type_="category"), + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("zlevel", content) + self.assertIn("z", content) + + def test_line_dataset(self): + c = ( + Line() + .add_dataset( + source=[ + ["product", "2012", "2013", "2014", "2015", "2016", "2017"], + ["Milk Tea", 56.5, 82.1, 88.7, 70.1, 53.4, 85.1], + ["Matcha Latte", 51.1, 51.4, 55.1, 53.3, 73.8, 68.7], + ["Cheese Cocoa", 40.1, 62.2, 69.5, 36.4, 45.2, 32.5], + ["Walnut Brownie", 25.2, 37.1, 41.2, 18, 33.9, 49.1], + ] + ) + .add_yaxis( + series_name="Milk Tea", + y_axis=[], + is_smooth=True, + series_layout_by="row", + ) + .add_yaxis( + series_name="Matcha Latte", + y_axis=[], + is_smooth=True, + series_layout_by="row", + ) + .add_yaxis( + series_name="Cheese Cocoa", + y_axis=[], + is_smooth=True, + series_layout_by="row", + ) + .add_yaxis( + series_name="Walnut Brownie", + y_axis=[], + is_smooth=True, + series_layout_by="row", + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="Dataset Line Example"), + xaxis_opts=opts.AxisOpts(type_="category"), + ) ) - ) - assert_equal(c.options.get("series")[0].get("data"), None) + self.assertEqual(c.options.get("series")[0].get("data"), None) diff --git a/test/test_line3d.py b/test/test_line3d.py index 11dc3d3cd..e841945c9 100644 --- a/test/test_line3d.py +++ b/test/test_line3d.py @@ -1,38 +1,38 @@ import math +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import Line3D from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_line3d_base(fake_writer): - data = [] - for t in range(0, 25000): - _t = t / 1000 - x = (1 + 0.25 * math.cos(75 * _t)) * math.cos(_t) - y = (1 + 0.25 * math.cos(75 * _t)) * math.sin(_t) - z = _t + 2.0 * math.sin(75 * _t) - data.append([x, y, z]) - c = ( - Line3D() - .add( - "", - data, - xaxis3d_opts=opts.Axis3DOpts(Faker.clock, type_="value"), - yaxis3d_opts=opts.Axis3DOpts(Faker.week_en, type_="value"), - grid3d_opts=opts.Grid3DOpts(width=100, height=100, depth=100), - ) - .set_global_opts( - visualmap_opts=opts.VisualMapOpts( - max_=30, min_=0, range_color=Faker.visual_color +class TestLine3DChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_line3d_base(self, fake_writer): + data = [] + for t in range(0, 25000): + _t = t / 1000 + x = (1 + 0.25 * math.cos(75 * _t)) * math.cos(_t) + y = (1 + 0.25 * math.cos(75 * _t)) * math.sin(_t) + z = _t + 2.0 * math.sin(75 * _t) + data.append([x, y, z]) + c = ( + Line3D() + .add( + "", + data, + xaxis3d_opts=opts.Axis3DOpts(Faker.clock, type_="value"), + yaxis3d_opts=opts.Axis3DOpts(Faker.week_en, type_="value"), + grid3d_opts=opts.Grid3DOpts(width=100, height=100, depth=100), + ) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts( + max_=30, min_=0, range_color=Faker.visual_color + ) ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_lines3d.py b/test/test_lines3d.py index 75e28684e..dbc18154c 100644 --- a/test/test_lines3d.py +++ b/test/test_lines3d.py @@ -1,107 +1,110 @@ import requests +import unittest from unittest.mock import patch -from nose.tools import assert_in, assert_equal - from pyecharts import options as opts from pyecharts.charts import Lines3D, Map3D from pyecharts.globals import ChartType -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_lines3d_base(fake_writer): - test_main_url: str = "https://echarts.apache.org/examples" - data_json_url = test_main_url + "/data-gl/asset/data/flights.json" - base_texture = test_main_url + "/data-gl/asset/world.topo.bathy.200401.jpg" - height_texture = test_main_url + "/data-gl/asset/bathymetry_bw_composite_4k.jpg" +class TestLines3DChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_lines3d_base(self, fake_writer): + test_main_url: str = "https://echarts.apache.org/examples" + data_json_url = test_main_url + "/data-gl/asset/data/flights.json" + base_texture = test_main_url + "/data-gl/asset/world.topo.bathy.200401.jpg" + height_texture = test_main_url + "/data-gl/asset/bathymetry_bw_composite_4k.jpg" - resp = requests.get(data_json_url).json() - json_routes = resp.get("routes") - json_airports = resp.get("airports") + resp = requests.get(data_json_url).json() + json_routes = resp.get("routes") + json_airports = resp.get("airports") - routes_data = [] - for d in json_routes: + routes_data = [] + for d in json_routes: - def _inner_func(idx): - return [json_airports[idx][3], json_airports[idx][4]] + def _inner_func(idx): + return [json_airports[idx][3], json_airports[idx][4]] - routes_data.append([_inner_func(d[1]), _inner_func(d[2])]) + routes_data.append([_inner_func(d[1]), _inner_func(d[2])]) - c = ( - Lines3D(init_opts=opts.InitOpts(bg_color="#000")) - .add_globe( - base_texture=base_texture, - height_texture=height_texture, - shading="lambert", - light_opts=opts.Map3DLightOpts(ambient_intensity=0.4, main_intensity=0.4), - ) - .add( - series_name="1", - coordinate_system="globe", - blend_mode="lighter", - linestyle_opts=opts.LineStyleOpts( - width=1, color="rgb(50, 50, 150)", opacity=0.1 - ), - data_pair=routes_data, + c = ( + Lines3D(init_opts=opts.InitOpts(bg_color="#000")) + .add_globe( + base_texture=base_texture, + height_texture=height_texture, + shading="lambert", + light_opts=opts.Map3DLightOpts( + ambient_intensity=0.4, main_intensity=0.4 + ), + ) + .add( + series_name="1", + coordinate_system="globe", + blend_mode="lighter", + linestyle_opts=opts.LineStyleOpts( + width=1, color="rgb(50, 50, 150)", opacity=0.1 + ), + data_pair=routes_data, + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("baseTexture", content) - assert_in("heightTexture", content) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("baseTexture", content) + self.assertIn("heightTexture", content) - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_lines3d_with_map3d_base(fake_writer): - example_data = [ - [[119.107078, 36.70925, 1000], [116.587245, 35.415393, 1000]], - [[117.000923, 36.675807], [120.355173, 36.082982]], - [[118.047648, 36.814939], [118.66471, 37.434564]], - [[121.391382, 37.539297], [119.107078, 36.70925]], - [[116.587245, 35.415393], [122.116394, 37.509691]], - [[119.461208, 35.428588], [118.326443, 35.065282]], - [[116.307428, 37.453968], [115.469381, 35.246531]], - ] - c = ( - Map3D() - .add_schema( - maptype="山东", - itemstyle_opts=opts.ItemStyleOpts( - color="rgb(5,101,123)", - opacity=1, - border_width=0.8, - border_color="rgb(62,215,213)", - ), - light_opts=opts.Map3DLightOpts( - main_color="#fff", - main_intensity=1.2, - is_main_shadow=False, - main_alpha=55, - main_beta=10, - ambient_intensity=0.3, - ), - view_control_opts=opts.Map3DViewControlOpts(center=[-10, 0, 10]), - post_effect_opts=opts.Map3DPostEffectOpts(is_enable=False), - ) - .add( - series_name="", - data_pair=example_data, - type_=ChartType.LINES3D, - effect=opts.Lines3DEffectOpts( - is_show=True, - period=4, - trail_width=3, - trail_length=0.5, - trail_color="#f00", - trail_opacity=1, - ), - linestyle_opts=opts.LineStyleOpts(is_show=False, color="#fff", opacity=0), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_lines3d_with_map3d_base(self, fake_writer): + example_data = [ + [[119.107078, 36.70925, 1000], [116.587245, 35.415393, 1000]], + [[117.000923, 36.675807], [120.355173, 36.082982]], + [[118.047648, 36.814939], [118.66471, 37.434564]], + [[121.391382, 37.539297], [119.107078, 36.70925]], + [[116.587245, 35.415393], [122.116394, 37.509691]], + [[119.461208, 35.428588], [118.326443, 35.065282]], + [[116.307428, 37.453968], [115.469381, 35.246531]], + ] + c = ( + Map3D() + .add_schema( + maptype="山东", + itemstyle_opts=opts.ItemStyleOpts( + color="rgb(5,101,123)", + opacity=1, + border_width=0.8, + border_color="rgb(62,215,213)", + ), + light_opts=opts.Map3DLightOpts( + main_color="#fff", + main_intensity=1.2, + is_main_shadow=False, + main_alpha=55, + main_beta=10, + ambient_intensity=0.3, + ), + view_control_opts=opts.Map3DViewControlOpts(center=[-10, 0, 10]), + post_effect_opts=opts.Map3DPostEffectOpts(is_enable=False), + ) + .add( + series_name="", + data_pair=example_data, + type_=ChartType.LINES3D, + effect=opts.Lines3DEffectOpts( + is_show=True, + period=4, + trail_width=3, + trail_length=0.5, + trail_color="#f00", + trail_opacity=1, + ), + linestyle_opts=opts.LineStyleOpts( + is_show=False, color="#fff", opacity=0 + ), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Lines3D")) ) - .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Lines3D")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_liquid.py b/test/test_liquid.py index aa5353a38..6e3216dab 100644 --- a/test/test_liquid.py +++ b/test/test_liquid.py @@ -1,47 +1,46 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Grid, Liquid from pyecharts.commons.utils import JsCode -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_liquid_base(fake_writer): - c = Liquid().add("lq", [0.6, 0.7], is_animation=False) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("animationDuration", content) - assert_in("animationDurationUpdate", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_liquid_grid(fake_writer): - l1 = ( - Liquid() - .add("lq", [0.6, 0.7], center=["60%", "50%"]) - .set_global_opts(title_opts=opts.TitleOpts(title="多个 Liquid 显示")) - ) - - l2 = Liquid().add( - "lq", - [0.3254], - center=["25%", "50%"], - label_opts=opts.LabelOpts( - font_size=50, - formatter=JsCode( - """function (param) { - return (Math.floor(param.value * 10000) / 100) + '%'; - }""" +class TestLiquidChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_liquid_base(self, fake_writer): + c = Liquid().add("lq", [0.6, 0.7], is_animation=False) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("animationDuration", content) + self.assertIn("animationDurationUpdate", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_liquid_grid(self, fake_writer): + l1 = ( + Liquid() + .add("lq", [0.6, 0.7], center=["60%", "50%"]) + .set_global_opts(title_opts=opts.TitleOpts(title="多个 Liquid 显示")) + ) + + l2 = Liquid().add( + "lq", + [0.3254], + center=["25%", "50%"], + label_opts=opts.LabelOpts( + font_size=50, + formatter=JsCode( + """function (param) { + return (Math.floor(param.value * 10000) / 100) + '%'; + }""" + ), + position="inside", ), - position="inside", - ), - ) + ) - c = Grid().add(l1, grid_opts=opts.GridOpts()).add(l2, grid_opts=opts.GridOpts()) - c.render() - _, content = fake_writer.call_args[0] - assert_in("center", content) + c = Grid().add(l1, grid_opts=opts.GridOpts()).add(l2, grid_opts=opts.GridOpts()) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("center", content) diff --git a/test/test_map.py b/test/test_map.py index 9ded99247..22e2f3636 100644 --- a/test/test_map.py +++ b/test/test_map.py @@ -1,227 +1,225 @@ +import unittest from unittest.mock import patch import simplejson as json -from nose.tools import assert_equal from pyecharts import options as opts from pyecharts.charts import Map from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map_base(fake_writer): - c = Map().add( - "商家A", [list(z) for z in zip(Faker.provinces, Faker.values())], "china" - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") +class TestMapChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map_base(self, fake_writer): + c = Map().add( + "商家A", [list(z) for z in zip(Faker.provinces, Faker.values())], "china" + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map_item_base(self, fake_writer): + location_name = ["广东"] + location_data = [[100, 200, 300, 400]] + mock_data = [ + opts.MapItem(name=d[0], value=d[1]) + for d in list(zip(location_name, location_data)) + ] -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map_item_base(fake_writer): - location_name = ["广东"] - location_data = [[100, 200, 300, 400]] - mock_data = [ - opts.MapItem(name=d[0], value=d[1]) - for d in list(zip(location_name, location_data)) - ] + c = Map().add("商家A", mock_data, "china") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") - c = Map().add("商家A", mock_data, "china") - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + def test_map_emphasis(self): + c = Map().add( + "商家A", + [list(z) for z in zip(Faker.provinces, Faker.values())], + "china", + emphasis_label_opts=opts.LabelOpts(is_show=False), + emphasis_itemstyle_opts=opts.ItemStyleOpts( + border_color="white", area_color="red" + ), + ) + options = json.loads(c.dump_options()) + expected = { + "label": {"show": False, "margin": 8}, + "itemStyle": {"borderColor": "white", "areaColor": "red"}, + } + self.assertEqual(expected, options["series"][0]["emphasis"]) + def test_map_add_geo_json(self): + geo_json = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "coordinates": [ + [ + [118.57812, 26.557402], + [118.590394, 26.553081], + [118.617087, 26.553059], + [118.616111, 26.527755], + [118.604148, 26.504904], + [118.608376, 26.497294], + [118.59623, 26.478385], + [118.578807, 26.473754], + [118.56458, 26.465373], + [118.554474, 26.445927], + [118.566533, 26.432851], + [118.54632, 26.417928], + [118.548079, 26.396854], + [118.560868, 26.378456], + [118.566104, 26.358824], + [118.589836, 26.373463], + [118.590222, 26.359802], + [118.595801, 26.347859], + [118.60369, 26.350515], + [118.621191, 26.361783], + [118.631475, 26.354788], + [118.656162, 26.340797], + [118.662964, 26.322656], + [118.653222, 26.313452], + [118.661204, 26.296272], + [118.664809, 26.270524], + [118.678076, 26.279442], + [118.68585, 26.28836], + [118.697277, 26.287725], + [118.693599, 26.305557], + [118.699533, 26.311078], + [118.724644, 26.337465], + [118.75152, 26.330542], + [118.76682, 26.327157], + [118.775794, 26.325234], + [118.797129, 26.340542], + [118.800059, 26.357136], + [118.785137, 26.365115], + [118.771085, 26.385377], + [118.750534, 26.390836], + [118.761617, 26.420202], + [118.745161, 26.427236], + [118.732826, 26.437957], + [118.734248, 26.45448], + [118.737767, 26.472405], + [118.748152, 26.481723], + [118.754353, 26.487893], + [118.748195, 26.494063], + [118.748238, 26.506403], + [118.762057, 26.508959], + [118.780881, 26.504741], + [118.784598, 26.491919], + [118.798615, 26.484013], + [118.805766, 26.474876], + [118.794544, 26.459227], + [118.809178, 26.445137], + [118.805487, 26.430479], + [118.822825, 26.420838], + [118.85278, 26.438461], + [118.878615, 26.469605], + [118.898227, 26.473649], + [118.91784, 26.472777], + [118.949511, 26.464885], + [118.951619, 26.487056], + [118.943747, 26.493838], + [118.942741, 26.507994], + [118.962064, 26.530816], + [118.997866, 26.528448], + [119.000968, 26.556946], + [119.009563, 26.560258], + [119.015767, 26.581621], + [119.032957, 26.594999], + [119.062507, 26.602235], + [119.075945, 26.587083], + [119.06329, 26.569473], + [119.076433, 26.569869], + [119.092152, 26.562702], + [119.101005, 26.572732], + [119.103604, 26.555941], + [119.119788, 26.539555], + [119.132, 26.538732], + [119.144213, 26.536681], + [119.171383, 26.548551], + [119.194922, 26.568383], + [119.212185, 26.547589], + [119.218757, 26.53412], + [119.229448, 26.526792], + [119.251174, 26.53241], + [119.253859, 26.546561], + [119.272336, 26.541055], + [119.281139, 26.534343], + [119.287711, 26.577995], + [119.301835, 26.593391], + [119.329006, 26.578699], + [119.354338, 26.599587], + [119.363191, 26.603281], + [119.376544, 26.604515], + [119.381656, 26.62171], + [119.397376, 26.627859], + [119.387272, 26.641423], + [119.375795, 26.639028], + [119.358824, 26.632951], + [119.351467, 26.648969], + [119.326158, 26.676212], + [119.261119, 26.690807], + [119.241593, 26.742684], + [119.226535, 26.728854], + [119.204611, 26.727288], + [119.182686, 26.729403], + [119.167628, 26.711891], + [119.146452, 26.731884], + [119.115663, 26.73961], + [119.047905, 26.728693], + [119.045955, 26.74669], + [119.054992, 26.764685], + [119.051092, 26.776147], + [119.042385, 26.776575], + [119.033679, 26.770872], + [119.012146, 26.761305], + [118.982497, 26.773075], + [118.945981, 26.77013], + [118.926007, 26.749652], + [118.882686, 26.738982], + [118.887478, 26.757469], + [118.903255, 26.76737], + [118.923824, 26.795751], + [118.890803, 26.813278], + [118.843328, 26.798569], + [118.827143, 26.786924], + [118.795852, 26.783859], + [118.771428, 26.805618], + [118.741511, 26.818793], + [118.728195, 26.830895], + [118.722432, 26.851573], + [118.739746, 26.856167], + [118.696095, 26.869031], + [118.664633, 26.86214], + [118.63317, 26.855248], + [118.620069, 26.839818], + [118.635807, 26.832963], + [118.638444, 26.810674], + [118.625974, 26.788073], + [118.623117, 26.769147], + [118.600382, 26.7093], + [118.582339, 26.703842], + [118.569788, 26.680592], + [118.57902, 26.673354], + [118.594415, 26.673546], + [118.594991, 26.656748], + [118.587905, 26.631739], + [118.57812, 26.557402], + ] + ], + "type": "Polygon", + }, + } + ], + } -def test_map_emphasis(): - c = Map().add( - "商家A", - [list(z) for z in zip(Faker.provinces, Faker.values())], - "china", - emphasis_label_opts=opts.LabelOpts(is_show=False), - emphasis_itemstyle_opts=opts.ItemStyleOpts( - border_color="white", area_color="red" - ), - ) - options = json.loads(c.dump_options()) - expected = { - "label": {"show": False, "margin": 8}, - "itemStyle": {"borderColor": "white", "areaColor": "red"}, - } - assert_equal(expected, options["series"][0]["emphasis"]) - - -def test_map_add_geo_json(): - geo_json = { - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": {}, - "geometry": { - "coordinates": [ - [ - [118.57812, 26.557402], - [118.590394, 26.553081], - [118.617087, 26.553059], - [118.616111, 26.527755], - [118.604148, 26.504904], - [118.608376, 26.497294], - [118.59623, 26.478385], - [118.578807, 26.473754], - [118.56458, 26.465373], - [118.554474, 26.445927], - [118.566533, 26.432851], - [118.54632, 26.417928], - [118.548079, 26.396854], - [118.560868, 26.378456], - [118.566104, 26.358824], - [118.589836, 26.373463], - [118.590222, 26.359802], - [118.595801, 26.347859], - [118.60369, 26.350515], - [118.621191, 26.361783], - [118.631475, 26.354788], - [118.656162, 26.340797], - [118.662964, 26.322656], - [118.653222, 26.313452], - [118.661204, 26.296272], - [118.664809, 26.270524], - [118.678076, 26.279442], - [118.68585, 26.28836], - [118.697277, 26.287725], - [118.693599, 26.305557], - [118.699533, 26.311078], - [118.724644, 26.337465], - [118.75152, 26.330542], - [118.76682, 26.327157], - [118.775794, 26.325234], - [118.797129, 26.340542], - [118.800059, 26.357136], - [118.785137, 26.365115], - [118.771085, 26.385377], - [118.750534, 26.390836], - [118.761617, 26.420202], - [118.745161, 26.427236], - [118.732826, 26.437957], - [118.734248, 26.45448], - [118.737767, 26.472405], - [118.748152, 26.481723], - [118.754353, 26.487893], - [118.748195, 26.494063], - [118.748238, 26.506403], - [118.762057, 26.508959], - [118.780881, 26.504741], - [118.784598, 26.491919], - [118.798615, 26.484013], - [118.805766, 26.474876], - [118.794544, 26.459227], - [118.809178, 26.445137], - [118.805487, 26.430479], - [118.822825, 26.420838], - [118.85278, 26.438461], - [118.878615, 26.469605], - [118.898227, 26.473649], - [118.91784, 26.472777], - [118.949511, 26.464885], - [118.951619, 26.487056], - [118.943747, 26.493838], - [118.942741, 26.507994], - [118.962064, 26.530816], - [118.997866, 26.528448], - [119.000968, 26.556946], - [119.009563, 26.560258], - [119.015767, 26.581621], - [119.032957, 26.594999], - [119.062507, 26.602235], - [119.075945, 26.587083], - [119.06329, 26.569473], - [119.076433, 26.569869], - [119.092152, 26.562702], - [119.101005, 26.572732], - [119.103604, 26.555941], - [119.119788, 26.539555], - [119.132, 26.538732], - [119.144213, 26.536681], - [119.171383, 26.548551], - [119.194922, 26.568383], - [119.212185, 26.547589], - [119.218757, 26.53412], - [119.229448, 26.526792], - [119.251174, 26.53241], - [119.253859, 26.546561], - [119.272336, 26.541055], - [119.281139, 26.534343], - [119.287711, 26.577995], - [119.301835, 26.593391], - [119.329006, 26.578699], - [119.354338, 26.599587], - [119.363191, 26.603281], - [119.376544, 26.604515], - [119.381656, 26.62171], - [119.397376, 26.627859], - [119.387272, 26.641423], - [119.375795, 26.639028], - [119.358824, 26.632951], - [119.351467, 26.648969], - [119.326158, 26.676212], - [119.261119, 26.690807], - [119.241593, 26.742684], - [119.226535, 26.728854], - [119.204611, 26.727288], - [119.182686, 26.729403], - [119.167628, 26.711891], - [119.146452, 26.731884], - [119.115663, 26.73961], - [119.047905, 26.728693], - [119.045955, 26.74669], - [119.054992, 26.764685], - [119.051092, 26.776147], - [119.042385, 26.776575], - [119.033679, 26.770872], - [119.012146, 26.761305], - [118.982497, 26.773075], - [118.945981, 26.77013], - [118.926007, 26.749652], - [118.882686, 26.738982], - [118.887478, 26.757469], - [118.903255, 26.76737], - [118.923824, 26.795751], - [118.890803, 26.813278], - [118.843328, 26.798569], - [118.827143, 26.786924], - [118.795852, 26.783859], - [118.771428, 26.805618], - [118.741511, 26.818793], - [118.728195, 26.830895], - [118.722432, 26.851573], - [118.739746, 26.856167], - [118.696095, 26.869031], - [118.664633, 26.86214], - [118.63317, 26.855248], - [118.620069, 26.839818], - [118.635807, 26.832963], - [118.638444, 26.810674], - [118.625974, 26.788073], - [118.623117, 26.769147], - [118.600382, 26.7093], - [118.582339, 26.703842], - [118.569788, 26.680592], - [118.57902, 26.673354], - [118.594415, 26.673546], - [118.594991, 26.656748], - [118.587905, 26.631739], - [118.57812, 26.557402], - ] - ], - "type": "Polygon", - }, - } - ], - } - - c = Map() - c.add_geo_json(geo_json=geo_json) - assert_equal(c._geo_json, geo_json) + c = Map() + c.add_geo_json(geo_json=geo_json) + self.assertEqual(c._geo_json, geo_json) diff --git a/test/test_map3d.py b/test/test_map3d.py index 178f7f962..df8869a47 100644 --- a/test/test_map3d.py +++ b/test/test_map3d.py @@ -1,7 +1,6 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_in - from pyecharts import options as opts from pyecharts.charts import Map3D from pyecharts.faker import Faker @@ -9,323 +8,323 @@ from pyecharts.commons.utils import JsCode -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map3d_base(fake_writer): - c = ( - Map3D() - .add_schema() - .add( - "商家A", - [list(z) for z in zip(Faker.provinces, Faker.values())], - maptype="china", +class TestMap3DChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map3d_base(self, fake_writer): + c = ( + Map3D() + .add_schema() + .add( + "商家A", + [list(z) for z in zip(Faker.provinces, Faker.values())], + maptype="china", + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("map3D", content) - + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("map3D", content) -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map3d_schema(fake_writer): - c = ( - Map3D() - .add_schema( - itemstyle_opts=opts.ItemStyleOpts(), - map3d_label=opts.Map3DLabelOpts(), - light_opts=opts.Map3DLightOpts(), - view_control_opts=opts.Map3DViewControlOpts(), - post_effect_opts=opts.Map3DPostEffectOpts(), - realistic_material_opts=opts.Map3DRealisticMaterialOpts(), - lambert_material_opts=opts.Map3DLambertMaterialOpts(), - color_material_opts=opts.Map3DColorMaterialOpts(), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map3d_schema(self, fake_writer): + c = ( + Map3D() + .add_schema( + itemstyle_opts=opts.ItemStyleOpts(), + map3d_label=opts.Map3DLabelOpts(), + light_opts=opts.Map3DLightOpts(), + view_control_opts=opts.Map3DViewControlOpts(), + post_effect_opts=opts.Map3DPostEffectOpts(), + realistic_material_opts=opts.Map3DRealisticMaterialOpts(), + lambert_material_opts=opts.Map3DLambertMaterialOpts(), + color_material_opts=opts.Map3DColorMaterialOpts(), + ) + .add( + series_name="商家A", + data_pair=[list(z) for z in zip(Faker.provinces, Faker.values())], + maptype="china", + type_=ChartType.LINES3D, + effect=opts.Lines3DEffectOpts(), + ) ) - .add( - series_name="商家A", - data_pair=[list(z) for z in zip(Faker.provinces, Faker.values())], - maptype="china", - type_=ChartType.LINES3D, - effect=opts.Lines3DEffectOpts(), - ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("itemStyle", content) - assert_in("label", content) - assert_in("light", content) - assert_in("viewControl", content) - assert_in("postEffect", content) - assert_in("realisticMaterial", content) - assert_in("lambertMaterial", content) - assert_in("colorMaterial", content) - + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("itemStyle", content) + self.assertIn("label", content) + self.assertIn("light", content) + self.assertIn("viewControl", content) + self.assertIn("postEffect", content) + self.assertIn("realisticMaterial", content) + self.assertIn("lambertMaterial", content) + self.assertIn("colorMaterial", content) -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map3d_with_bar3d(fake_writer): - example_data = [ - ("黑龙江", [127.9688, 45.368, 100]), - ("内蒙古", [110.3467, 41.4899, 300]), - ("吉林", [125.8154, 44.2584, 300]), - ("辽宁", [123.1238, 42.1216, 300]), - ("河北", [114.4995, 38.1006, 300]), - ("天津", [117.4219, 39.4189, 300]), - ("山西", [112.3352, 37.9413, 300]), - ("陕西", [109.1162, 34.2004, 300]), - ("甘肃", [103.5901, 36.3043, 300]), - ("宁夏", [106.3586, 38.1775, 300]), - ("青海", [101.4038, 36.8207, 300]), - ("新疆", [87.9236, 43.5883, 300]), - ("西藏", [91.11, 29.97, 300]), - ("四川", [103.9526, 30.7617, 300]), - ("重庆", [108.384366, 30.439702, 300]), - ("山东", [117.1582, 36.8701, 300]), - ("河南", [113.4668, 34.6234, 300]), - ("江苏", [118.8062, 31.9208, 300]), - ("安徽", [117.29, 32.0581, 300]), - ("湖北", [114.3896, 30.6628, 300]), - ("浙江", [119.5313, 29.8773, 300]), - ("福建", [119.4543, 25.9222, 300]), - ("江西", [116.0046, 28.6633, 300]), - ("湖南", [113.0823, 28.2568, 300]), - ("贵州", [106.6992, 26.7682, 300]), - ("广西", [108.479, 23.1152, 300]), - ("海南", [110.3893, 19.8516, 300]), - ("上海", [121.4648, 31.2891, 1300]), - ] + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map3d_with_bar3d(self, fake_writer): + example_data = [ + ("黑龙江", [127.9688, 45.368, 100]), + ("内蒙古", [110.3467, 41.4899, 300]), + ("吉林", [125.8154, 44.2584, 300]), + ("辽宁", [123.1238, 42.1216, 300]), + ("河北", [114.4995, 38.1006, 300]), + ("天津", [117.4219, 39.4189, 300]), + ("山西", [112.3352, 37.9413, 300]), + ("陕西", [109.1162, 34.2004, 300]), + ("甘肃", [103.5901, 36.3043, 300]), + ("宁夏", [106.3586, 38.1775, 300]), + ("青海", [101.4038, 36.8207, 300]), + ("新疆", [87.9236, 43.5883, 300]), + ("西藏", [91.11, 29.97, 300]), + ("四川", [103.9526, 30.7617, 300]), + ("重庆", [108.384366, 30.439702, 300]), + ("山东", [117.1582, 36.8701, 300]), + ("河南", [113.4668, 34.6234, 300]), + ("江苏", [118.8062, 31.9208, 300]), + ("安徽", [117.29, 32.0581, 300]), + ("湖北", [114.3896, 30.6628, 300]), + ("浙江", [119.5313, 29.8773, 300]), + ("福建", [119.4543, 25.9222, 300]), + ("江西", [116.0046, 28.6633, 300]), + ("湖南", [113.0823, 28.2568, 300]), + ("贵州", [106.6992, 26.7682, 300]), + ("广西", [108.479, 23.1152, 300]), + ("海南", [110.3893, 19.8516, 300]), + ("上海", [121.4648, 31.2891, 1300]), + ] - c = ( - Map3D() - .add_schema( - itemstyle_opts=opts.ItemStyleOpts( - color="rgb(5,101,123)", - opacity=1, - border_width=0.8, - border_color="rgb(62,215,213)", - ), - map3d_label=opts.Map3DLabelOpts( - is_show=False, - formatter=JsCode( - "function(data){return data.name + " " + data.value[2];}" + c = ( + Map3D() + .add_schema( + itemstyle_opts=opts.ItemStyleOpts( + color="rgb(5,101,123)", + opacity=1, + border_width=0.8, + border_color="rgb(62,215,213)", ), - ), - emphasis_label_opts=opts.LabelOpts( - is_show=False, - color="#fff", - font_size=10, - background_color="rgba(0,23,11,0)", - ), - light_opts=opts.Map3DLightOpts( - main_color="#fff", - main_intensity=1.2, - main_shadow_quality="high", - is_main_shadow=False, - main_beta=10, - ambient_intensity=0.3, - ), - ) - .add( - series_name="bar3D", - data_pair=example_data, - type_=ChartType.BAR3D, - bar_size=1, - shading="lambert", - label_opts=opts.LabelOpts( - is_show=False, - formatter=JsCode( - "function(data){return data.name + ' ' + data.value[2];}" + map3d_label=opts.Map3DLabelOpts( + is_show=False, + formatter=JsCode( + "function(data){return data.name + " " + data.value[2];}" + ), + ), + emphasis_label_opts=opts.LabelOpts( + is_show=False, + color="#fff", + font_size=10, + background_color="rgba(0,23,11,0)", + ), + light_opts=opts.Map3DLightOpts( + main_color="#fff", + main_intensity=1.2, + main_shadow_quality="high", + is_main_shadow=False, + main_beta=10, + ambient_intensity=0.3, ), - ), + ) + .add( + series_name="bar3D", + data_pair=example_data, + type_=ChartType.BAR3D, + bar_size=1, + shading="lambert", + label_opts=opts.LabelOpts( + is_show=False, + formatter=JsCode( + "function(data){return data.name + ' ' + data.value[2];}" + ), + ), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Bar3D")) ) - .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Bar3D")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("itemStyle", content) - + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("itemStyle", content) -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map3d_with_lines3d(fake_writer): - example_data = [ - [[119.107078, 36.70925, 1000], [116.587245, 35.415393, 1000]], - [[117.000923, 36.675807], [120.355173, 36.082982]], - [[118.047648, 36.814939], [118.66471, 37.434564]], - [[121.391382, 37.539297], [119.107078, 36.70925]], - [[116.587245, 35.415393], [122.116394, 37.509691]], - [[119.461208, 35.428588], [118.326443, 35.065282]], - [[116.307428, 37.453968], [115.469381, 35.246531]], - ] - c = ( - Map3D() - .add_schema( - maptype="山东", - itemstyle_opts=opts.ItemStyleOpts( - color="rgb(5,101,123)", - opacity=1, - border_width=0.8, - border_color="rgb(62,215,213)", - ), - light_opts=opts.Map3DLightOpts( - main_color="#fff", - main_intensity=1.2, - is_main_shadow=False, - main_alpha=55, - main_beta=10, - ambient_intensity=0.3, - ), - view_control_opts=opts.Map3DViewControlOpts(center=[-10, 0, 10]), - post_effect_opts=opts.Map3DPostEffectOpts(is_enable=False), - ) - .add( - series_name="", - data_pair=example_data, - type_=ChartType.LINES3D, - effect=opts.Lines3DEffectOpts( - is_show=True, - period=4, - trail_width=3, - trail_length=0.5, - trail_color="#f00", - trail_opacity=1, - ), - linestyle_opts=opts.LineStyleOpts(is_show=False, color="#fff", opacity=0), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map3d_with_lines3d(self, fake_writer): + example_data = [ + [[119.107078, 36.70925, 1000], [116.587245, 35.415393, 1000]], + [[117.000923, 36.675807], [120.355173, 36.082982]], + [[118.047648, 36.814939], [118.66471, 37.434564]], + [[121.391382, 37.539297], [119.107078, 36.70925]], + [[116.587245, 35.415393], [122.116394, 37.509691]], + [[119.461208, 35.428588], [118.326443, 35.065282]], + [[116.307428, 37.453968], [115.469381, 35.246531]], + ] + c = ( + Map3D() + .add_schema( + maptype="山东", + itemstyle_opts=opts.ItemStyleOpts( + color="rgb(5,101,123)", + opacity=1, + border_width=0.8, + border_color="rgb(62,215,213)", + ), + light_opts=opts.Map3DLightOpts( + main_color="#fff", + main_intensity=1.2, + is_main_shadow=False, + main_alpha=55, + main_beta=10, + ambient_intensity=0.3, + ), + view_control_opts=opts.Map3DViewControlOpts(center=[-10, 0, 10]), + post_effect_opts=opts.Map3DPostEffectOpts(is_enable=False), + ) + .add( + series_name="", + data_pair=example_data, + type_=ChartType.LINES3D, + effect=opts.Lines3DEffectOpts( + is_show=True, + period=4, + trail_width=3, + trail_length=0.5, + trail_color="#f00", + trail_opacity=1, + ), + linestyle_opts=opts.LineStyleOpts( + is_show=False, color="#fff", opacity=0 + ), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Lines3D")) ) - .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Lines3D")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("itemStyle", content) - + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("itemStyle", content) -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map3d_with_line3d(fake_writer): - example_data = [ - [[119.107078, 36.70925, 1000], [116.587245, 35.415393, 1000]], - [[117.000923, 36.675807], [120.355173, 36.082982]], - [[118.047648, 36.814939], [118.66471, 37.434564]], - [[121.391382, 37.539297], [119.107078, 36.70925]], - [[116.587245, 35.415393], [122.116394, 37.509691]], - [[119.461208, 35.428588], [118.326443, 35.065282]], - [[116.307428, 37.453968], [115.469381, 35.246531]], - ] - c = ( - Map3D() - .add_schema( - maptype="山东", - itemstyle_opts=opts.ItemStyleOpts( - color="rgb(5,101,123)", - opacity=1, - border_width=0.8, - border_color="rgb(62,215,213)", - ), - light_opts=opts.Map3DLightOpts( - main_color="#fff", - main_intensity=1.2, - is_main_shadow=False, - main_alpha=55, - main_beta=10, - ambient_intensity=0.3, - ), - view_control_opts=opts.Map3DViewControlOpts(center=[-10, 0, 10]), - post_effect_opts=opts.Map3DPostEffectOpts(is_enable=False), - ) - .add( - series_name="", - data_pair=example_data, - type_=ChartType.LINE3D, - effect=opts.Lines3DEffectOpts( - is_show=True, - period=4, - trail_width=3, - trail_length=0.5, - trail_color="#f00", - trail_opacity=1, - ), - linestyle_opts=opts.LineStyleOpts(is_show=False, color="#fff", opacity=0), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map3d_with_line3d(self, fake_writer): + example_data = [ + [[119.107078, 36.70925, 1000], [116.587245, 35.415393, 1000]], + [[117.000923, 36.675807], [120.355173, 36.082982]], + [[118.047648, 36.814939], [118.66471, 37.434564]], + [[121.391382, 37.539297], [119.107078, 36.70925]], + [[116.587245, 35.415393], [122.116394, 37.509691]], + [[119.461208, 35.428588], [118.326443, 35.065282]], + [[116.307428, 37.453968], [115.469381, 35.246531]], + ] + c = ( + Map3D() + .add_schema( + maptype="山东", + itemstyle_opts=opts.ItemStyleOpts( + color="rgb(5,101,123)", + opacity=1, + border_width=0.8, + border_color="rgb(62,215,213)", + ), + light_opts=opts.Map3DLightOpts( + main_color="#fff", + main_intensity=1.2, + is_main_shadow=False, + main_alpha=55, + main_beta=10, + ambient_intensity=0.3, + ), + view_control_opts=opts.Map3DViewControlOpts(center=[-10, 0, 10]), + post_effect_opts=opts.Map3DPostEffectOpts(is_enable=False), + ) + .add( + series_name="", + data_pair=example_data, + type_=ChartType.LINE3D, + effect=opts.Lines3DEffectOpts( + is_show=True, + period=4, + trail_width=3, + trail_length=0.5, + trail_color="#f00", + trail_opacity=1, + ), + linestyle_opts=opts.LineStyleOpts( + is_show=False, color="#fff", opacity=0 + ), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Line3D")) ) - .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Line3D")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("itemStyle", content) - + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("itemStyle", content) -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map3d_with_scatter3d(fake_writer): - example_data = [ - ("黑龙江", [127.9688, 45.368, 100]), - ("内蒙古", [110.3467, 41.4899, 100]), - ("吉林", [125.8154, 44.2584, 100]), - ("辽宁", [123.1238, 42.1216, 100]), - ("河北", [114.4995, 38.1006, 100]), - ("天津", [117.4219, 39.4189, 100]), - ("山西", [112.3352, 37.9413, 100]), - ("陕西", [109.1162, 34.2004, 100]), - ("甘肃", [103.5901, 36.3043, 100]), - ("宁夏", [106.3586, 38.1775, 100]), - ("青海", [101.4038, 36.8207, 100]), - ("新疆", [87.9236, 43.5883, 100]), - ("西藏", [91.11, 29.97, 100]), - ("四川", [103.9526, 30.7617, 100]), - ("重庆", [108.384366, 30.439702, 100]), - ("山东", [117.1582, 36.8701, 100]), - ("河南", [113.4668, 34.6234, 100]), - ("江苏", [118.8062, 31.9208, 100]), - ("安徽", [117.29, 32.0581, 100]), - ("湖北", [114.3896, 30.6628, 100]), - ("浙江", [119.5313, 29.8773, 100]), - ("福建", [119.4543, 25.9222, 100]), - ("江西", [116.0046, 28.6633, 100]), - ("湖南", [113.0823, 28.2568, 100]), - ("贵州", [106.6992, 26.7682, 100]), - ("广西", [108.479, 23.1152, 100]), - ("海南", [110.3893, 19.8516, 100]), - ("上海", [121.4648, 31.2891, 100]), - ] + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map3d_with_scatter3d(self, fake_writer): + example_data = [ + ("黑龙江", [127.9688, 45.368, 100]), + ("内蒙古", [110.3467, 41.4899, 100]), + ("吉林", [125.8154, 44.2584, 100]), + ("辽宁", [123.1238, 42.1216, 100]), + ("河北", [114.4995, 38.1006, 100]), + ("天津", [117.4219, 39.4189, 100]), + ("山西", [112.3352, 37.9413, 100]), + ("陕西", [109.1162, 34.2004, 100]), + ("甘肃", [103.5901, 36.3043, 100]), + ("宁夏", [106.3586, 38.1775, 100]), + ("青海", [101.4038, 36.8207, 100]), + ("新疆", [87.9236, 43.5883, 100]), + ("西藏", [91.11, 29.97, 100]), + ("四川", [103.9526, 30.7617, 100]), + ("重庆", [108.384366, 30.439702, 100]), + ("山东", [117.1582, 36.8701, 100]), + ("河南", [113.4668, 34.6234, 100]), + ("江苏", [118.8062, 31.9208, 100]), + ("安徽", [117.29, 32.0581, 100]), + ("湖北", [114.3896, 30.6628, 100]), + ("浙江", [119.5313, 29.8773, 100]), + ("福建", [119.4543, 25.9222, 100]), + ("江西", [116.0046, 28.6633, 100]), + ("湖南", [113.0823, 28.2568, 100]), + ("贵州", [106.6992, 26.7682, 100]), + ("广西", [108.479, 23.1152, 100]), + ("海南", [110.3893, 19.8516, 100]), + ("上海", [121.4648, 31.2891, 100]), + ] - c = ( - Map3D() - .add_schema( - itemstyle_opts=opts.ItemStyleOpts( - color="rgb(5,101,123)", - opacity=1, - border_width=0.8, - border_color="rgb(62,215,213)", - ), - map3d_label=opts.Map3DLabelOpts( - is_show=False, - formatter=JsCode( - "function(data){return data.name + " " + data.value[2];}" + c = ( + Map3D() + .add_schema( + itemstyle_opts=opts.ItemStyleOpts( + color="rgb(5,101,123)", + opacity=1, + border_width=0.8, + border_color="rgb(62,215,213)", ), - ), - emphasis_label_opts=opts.LabelOpts( - is_show=False, - color="#fff", - font_size=10, - background_color="rgba(0,23,11,0)", - ), - light_opts=opts.Map3DLightOpts( - main_color="#fff", - main_intensity=1.2, - main_shadow_quality="high", - is_main_shadow=False, - main_beta=10, - ambient_intensity=0.3, - ), - ) - .add( - series_name="Scatter3D", - data_pair=example_data, - type_=ChartType.SCATTER3D, - bar_size=1, - shading="lambert", - label_opts=opts.LabelOpts( - is_show=False, - formatter=JsCode( - "function(data){return data.name + ' ' + data.value[2];}" + map3d_label=opts.Map3DLabelOpts( + is_show=False, + formatter=JsCode( + "function(data){return data.name + " " + data.value[2];}" + ), + ), + emphasis_label_opts=opts.LabelOpts( + is_show=False, + color="#fff", + font_size=10, + background_color="rgba(0,23,11,0)", + ), + light_opts=opts.Map3DLightOpts( + main_color="#fff", + main_intensity=1.2, + main_shadow_quality="high", + is_main_shadow=False, + main_beta=10, + ambient_intensity=0.3, + ), + ) + .add( + series_name="Scatter3D", + data_pair=example_data, + type_=ChartType.SCATTER3D, + bar_size=1, + shading="lambert", + label_opts=opts.LabelOpts( + is_show=False, + formatter=JsCode( + "function(data){return data.name + ' ' + data.value[2];}" + ), ), - ), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Scatter3D")) ) - .set_global_opts(title_opts=opts.TitleOpts(title="Map3D-Scatter3D")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("itemStyle", content) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("itemStyle", content) diff --git a/test/test_map_globe.py b/test/test_map_globe.py index a8d2febab..f48dcf32b 100644 --- a/test/test_map_globe.py +++ b/test/test_map_globe.py @@ -1,46 +1,48 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts.charts import MapGlobe from pyecharts.faker import Faker from pyecharts.globals import CurrentConfig, NotebookType -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map_base(fake_writer): - c = MapGlobe().add( - "商家A", [list(z) for z in zip(Faker.provinces, Faker.values())], "china" - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("document.createElement('canvas')", content) - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("baseTexture", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_map_base_v2(fake_writer): - c = ( - MapGlobe() - .add("商家A", [list(z) for z in zip(Faker.provinces, Faker.values())], "china") - .add_schema(maptype="china") - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("document.createElement('canvas')", content) - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("baseTexture", content) - - -def test_map_globe_in_jupyter(): - CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK - - c = MapGlobe().add( - "商家A", [list(z) for z in zip(Faker.provinces, Faker.values())], "china" - ) - content = c.render_notebook()._repr_html_() - assert_in("document.createElement('canvas')", content) - assert_in("baseTexture", content) +class TestMapGlobeChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map_base(self, fake_writer): + c = MapGlobe().add( + "商家A", [list(z) for z in zip(Faker.provinces, Faker.values())], "china" + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("document.createElement('canvas')", content) + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("baseTexture", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_map_base_v2(self, fake_writer): + c = ( + MapGlobe() + .add( + "商家A", + [list(z) for z in zip(Faker.provinces, Faker.values())], + "china", + ) + .add_schema(maptype="china") + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("document.createElement('canvas')", content) + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("baseTexture", content) + + def test_map_globe_in_jupyter(self): + CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK + + c = MapGlobe().add( + "商家A", [list(z) for z in zip(Faker.provinces, Faker.values())], "china" + ) + content = c.render_notebook()._repr_html_() + self.assertIn("document.createElement('canvas')", content) + self.assertIn("baseTexture", content) diff --git a/test/test_mixins.py b/test/test_mixins.py index 94096f5dc..34f4270ea 100644 --- a/test/test_mixins.py +++ b/test/test_mixins.py @@ -1,8 +1,5 @@ -from unittest.mock import patch +import unittest -from nose.tools import assert_equal - -from pyecharts import options as opts from pyecharts.charts import Bar, Line from pyecharts.charts.mixins import CompositeMixin @@ -17,10 +14,11 @@ def add(self, chart, tab_name): return self -def test_composite_mixin_len(): - b_1 = Bar() - b_2 = Line() - c = CustomCompositeChart() - c.add(b_1, "bar") - c.add(b_2, "line") - assert len(c) == 2 +class TestMixins(unittest.TestCase): + def test_composite_mixin_len(self): + b_1 = Bar() + b_2 = Line() + c = CustomCompositeChart() + c.add(b_1, "bar") + c.add(b_2, "line") + assert len(c) == 2 diff --git a/test/test_page.py b/test/test_page.py index a2616735b..18ccdc3b0 100644 --- a/test/test_page.py +++ b/test/test_page.py @@ -1,14 +1,6 @@ +import unittest from typing import Iterable -from nose.tools import ( - assert_equal, - assert_in, - assert_not_equal, - assert_not_in, - assert_true, - raises, -) - from pyecharts.charts import Bar, Line, Page from pyecharts.commons.utils import OrderedSet from pyecharts.globals import ThemeType @@ -45,144 +37,136 @@ def _create_table() -> Table: return table -def test_page_layout_default(): - page = Page() - assert_equal(page.layout, "") - - -def test_page_layout_custom(): - page = Page(layout=Page.SimplePageLayout) - assert_equal(page.layout, "justify-content:center; display:flex; flex-wrap:wrap; ") - - -def test_page_jshost_default(): - bar = _create_bar() - line = _create_line() - page = Page().add(bar, line) - assert_equal(page.js_host, "https://assets.pyecharts.org/assets/v5/") - - -def test_page_jshost_custom(): - from pyecharts.globals import CurrentConfig - - default_host = CurrentConfig.ONLINE_HOST - custom_host = "http://localhost:8888/assets/" - CurrentConfig.ONLINE_HOST = custom_host - bar = _create_bar() - line = _create_line() - page = Page().add(bar, line) - assert_equal(page.js_host, custom_host) - CurrentConfig.ONLINE_HOST = default_host - - -def test_page_render_embed(): - bar = _create_bar() - line = _create_line() - content = Page().add(bar, line).render_embed() - assert_true(len(content) > 8000) - - -def test_page_render_notebook(): - page = Page() - page.add(_create_line(), _create_bar(), _create_table()) - html = page.render_notebook().__html__() - assert_in("City name", html) - - -def test_page_load_javascript(): - bar = _create_bar() - line = _create_line() - content = Page().add(bar, line).load_javascript() - assert_equal("", content.data) - assert_equal(["https://assets.pyecharts.org/assets/v5/echarts.min.js"], content.lib) - - -def _get_new_page(unique: bool = True) -> Page: - bar = _create_bar() - line = _create_line() - - if not unique: - bar.chart_id = "chenjiandongx_is_an_awesome_boy" - line.chart_id = "chenjiandongx_is_an_amazing_boy" - - p = Page(layout=Page.DraggablePageLayout) - p.add(bar, line) - return p - - -# chart_config.json content -LAYOUT_DICT = [ - { - "cid": "chenjiandongx_is_an_awesome_boy", - "width": "900px", - "height": "500px", - "top": "31px", - "left": "8px", - }, - { - "cid": "chenjiandongx_is_an_amazing_boy", - "width": "900px", - "height": "500px", - "top": "30px", - "left": "910px", - }, -] - - -def test_page_draggable_layout_unique_chart_id(): - page1 = _get_new_page() - html1 = page1.save_resize_html(source=page1.render(), cfg_dict=LAYOUT_DICT) - - page2 = _get_new_page() - html2 = page2.save_resize_html(source=page2.render(), cfg_dict=LAYOUT_DICT) - - assert_not_equal(html1, html2) - - -def test_page_draggable_layout_same_chart_id(): - page1 = _get_new_page(unique=False) - html1 = page1.save_resize_html(source=page1.render(), cfg_dict=LAYOUT_DICT) - - page2 = _get_new_page(unique=False) - html2 = page2.save_resize_html(source=page2.render(), cfg_dict=LAYOUT_DICT) - - assert_equal(html1, html2) - - -@raises(ValueError, FileNotFoundError) -def test_page_cfg_type(): - page = Page() - page.save_resize_html() - - -def test_page_iterable(): - page = Page() - assert_true(isinstance(page, Iterable)) - - -def test_page_attr(): - page = Page() - assert_true(isinstance(page.js_functions, OrderedSet)) - assert_true(isinstance(page._charts, list)) - - -def test_page_resize(): - page = Page() - content = page.save_resize_html( - cfg_dict=[{"cid": "xxx", "width": 100, "height": 100, "top": 100, "left": 100}] - ) - assert_not_in(".resizable()", content) - assert_not_in(".draggable()", content) - - -def test_page_resize_cfg(): - page = Page() - content = page.save_resize_html(cfg_file="test/fixtures/resize_cfg.json") - assert_not_in(".resizable()", content) - assert_not_in(".draggable()", content) - +class TestPageComponent(unittest.TestCase): + def test_page_layout_default(self): + page = Page() + self.assertEqual(page.layout, "") + + def test_page_layout_custom(self): + page = Page(layout=Page.SimplePageLayout) + self.assertEqual( + page.layout, "justify-content:center; display:flex; flex-wrap:wrap; " + ) + + def test_page_jshost_default(self): + bar = _create_bar() + line = _create_line() + page = Page().add(bar, line) + self.assertEqual(page.js_host, "https://assets.pyecharts.org/assets/v5/") + + def test_page_jshost_custom(self): + from pyecharts.globals import CurrentConfig + + default_host = CurrentConfig.ONLINE_HOST + custom_host = "http://localhost:8888/assets/" + CurrentConfig.ONLINE_HOST = custom_host + bar = _create_bar() + line = _create_line() + page = Page().add(bar, line) + self.assertEqual(page.js_host, custom_host) + CurrentConfig.ONLINE_HOST = default_host + + def test_page_render_embed(self): + bar = _create_bar() + line = _create_line() + content = Page().add(bar, line).render_embed() + self.assertTrue(len(content) > 8000) + + def test_page_render_notebook(self): + page = Page() + page.add(_create_line(), _create_bar(), _create_table()) + html = page.render_notebook().__html__() + self.assertIn("City name", html) + + def test_page_load_javascript(self): + bar = _create_bar() + line = _create_line() + content = Page().add(bar, line).load_javascript() + self.assertEqual("", content.data) + self.assertEqual( + ["https://assets.pyecharts.org/assets/v5/echarts.min.js"], content.lib + ) + + def _get_new_page(self, unique: bool = True) -> Page: + bar = _create_bar() + line = _create_line() + + if not unique: + bar.chart_id = "chenjiandongx_is_an_awesome_boy" + line.chart_id = "chenjiandongx_is_an_amazing_boy" + + p = Page(layout=Page.DraggablePageLayout) + p.add(bar, line) + return p + + # chart_config.json content + LAYOUT_DICT = [ + { + "cid": "chenjiandongx_is_an_awesome_boy", + "width": "900px", + "height": "500px", + "top": "31px", + "left": "8px", + }, + { + "cid": "chenjiandongx_is_an_amazing_boy", + "width": "900px", + "height": "500px", + "top": "30px", + "left": "910px", + }, + ] -@raises(ValueError) -def test_page_no_cfg_dict_or_file(): - page = Page() - page.save_resize_html() + def test_page_draggable_layout_unique_chart_id(self): + page1 = self._get_new_page() + html1 = page1.save_resize_html(source=page1.render(), cfg_dict=self.LAYOUT_DICT) + + page2 = self._get_new_page() + html2 = page2.save_resize_html(source=page2.render(), cfg_dict=self.LAYOUT_DICT) + + self.assertNotEqual(html1, html2) + + def test_page_draggable_layout_same_chart_id(self): + page1 = self._get_new_page(unique=False) + html1 = page1.save_resize_html(source=page1.render(), cfg_dict=self.LAYOUT_DICT) + + page2 = self._get_new_page(unique=False) + html2 = page2.save_resize_html(source=page2.render(), cfg_dict=self.LAYOUT_DICT) + + self.assertEqual(html1, html2) + + def test_page_cfg_type(self): + # ValueError + with self.assertRaises(FileNotFoundError): + page = Page() + page.save_resize_html() + + def test_page_iterable(self): + page = Page() + self.assertTrue(isinstance(page, Iterable)) + + def test_page_attr(self): + page = Page() + self.assertTrue(isinstance(page.js_functions, OrderedSet)) + self.assertTrue(isinstance(page._charts, list)) + + def test_page_resize(self): + page = Page() + content = page.save_resize_html( + cfg_dict=[ + {"cid": "xxx", "width": 100, "height": 100, "top": 100, "left": 100} + ] + ) + self.assertNotIn(".resizable()", content) + self.assertNotIn(".draggable()", content) + + def test_page_resize_cfg(self): + page = Page() + content = page.save_resize_html(cfg_file="test/fixtures/resize_cfg.json") + self.assertNotIn(".resizable()", content) + self.assertNotIn(".draggable()", content) + + def test_page_no_cfg_dict_or_file(self): + with self.assertRaises(ValueError): + page = Page() + page.save_resize_html() diff --git a/test/test_parallel.py b/test/test_parallel.py index e693c9b71..25c962b35 100644 --- a/test/test_parallel.py +++ b/test/test_parallel.py @@ -1,114 +1,119 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import Parallel -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_parallel_base(fake_writer): - data = [ - [1, 91, 45, 125, 0.82, 34], - [2, 65, 27, 78, 0.86, 45], - [3, 83, 60, 84, 1.09, 73], - [4, 109, 81, 121, 1.28, 68], - [5, 106, 77, 114, 1.07, 55], - [6, 109, 81, 121, 1.28, 68], - ] - c = ( - Parallel() - .add_schema( - [ - {"dim": 0, "name": "data"}, - {"dim": 1, "name": "AQI"}, - {"dim": 2, "name": "PM2.5"}, - {"dim": 3, "name": "PM10"}, - {"dim": 4, "name": "CO"}, - {"dim": 5, "name": "NO2"}, - ] +class TestParallelChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_parallel_base(self, fake_writer): + data = [ + [1, 91, 45, 125, 0.82, 34], + [2, 65, 27, 78, 0.86, 45], + [3, 83, 60, 84, 1.09, 73], + [4, 109, 81, 121, 1.28, 68], + [5, 106, 77, 114, 1.07, 55], + [6, 109, 81, 121, 1.28, 68], + ] + c = ( + Parallel() + .add_schema( + [ + {"dim": 0, "name": "data"}, + {"dim": 1, "name": "AQI"}, + {"dim": 2, "name": "PM2.5"}, + {"dim": 3, "name": "PM10"}, + {"dim": 4, "name": "CO"}, + {"dim": 5, "name": "NO2"}, + ] + ) + .add("parallel", data) ) - .add("parallel", data) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_parallel_item_base(fake_writer): - item_data = [ - opts.ParallelItem(value=[1, 91, 45, 125, 0.82, 34]), - opts.ParallelItem(value=[2, 65, 27, 78, 0.86, 45]), - opts.ParallelItem(value=[3, 83, 60, 84, 1.09, 73]), - opts.ParallelItem(value=[4, 109, 81, 121, 1.28, 68]), - opts.ParallelItem(value=[5, 106, 77, 114, 1.07, 55]), - opts.ParallelItem(value=[6, 109, 81, 121, 1.28, 68]), - ] + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_parallel_item_base(self, fake_writer): + item_data = [ + opts.ParallelItem(value=[1, 91, 45, 125, 0.82, 34]), + opts.ParallelItem(value=[2, 65, 27, 78, 0.86, 45]), + opts.ParallelItem(value=[3, 83, 60, 84, 1.09, 73]), + opts.ParallelItem(value=[4, 109, 81, 121, 1.28, 68]), + opts.ParallelItem(value=[5, 106, 77, 114, 1.07, 55]), + opts.ParallelItem(value=[6, 109, 81, 121, 1.28, 68]), + ] - c = ( - Parallel() - .add_schema( - [ - {"dim": 0, "name": "data"}, - {"dim": 1, "name": "AQI"}, - {"dim": 2, "name": "PM2.5"}, - {"dim": 3, "name": "PM10"}, - {"dim": 4, "name": "CO"}, - {"dim": 5, "name": "NO2"}, - ] + c = ( + Parallel() + .add_schema( + [ + {"dim": 0, "name": "data"}, + {"dim": 1, "name": "AQI"}, + {"dim": 2, "name": "PM2.5"}, + {"dim": 3, "name": "PM10"}, + {"dim": 4, "name": "CO"}, + {"dim": 5, "name": "NO2"}, + ] + ) + .add("parallel", item_data) ) - .add("parallel", item_data) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_parallel_base_v1(fake_writer): - data = [ - [1, 91, 45, 125, 0.82, 34, 23, "良"], - [2, 65, 27, 78, 0.86, 45, 29, "良"], - [3, 83, 60, 84, 1.09, 73, 27, "良"], - [4, 109, 81, 121, 1.28, 68, 51, "轻度污染"], - [5, 106, 77, 114, 1.07, 55, 51, "轻度污染"], - [6, 109, 81, 121, 1.28, 68, 51, "轻度污染"], - [7, 106, 77, 114, 1.07, 55, 51, "轻度污染"], - [8, 89, 65, 78, 0.86, 51, 26, "良"], - [9, 53, 33, 47, 0.64, 50, 17, "良"], - [10, 80, 55, 80, 1.01, 75, 24, "良"], - [11, 117, 81, 124, 1.03, 45, 24, "轻度污染"], - [12, 99, 71, 142, 1.1, 62, 42, "良"], - [13, 95, 69, 130, 1.28, 74, 50, "良"], - [14, 116, 87, 131, 1.47, 84, 40, "轻度污染"], - ] - c = ( - Parallel() - .add_schema( - schema=[ - opts.ParallelAxisOpts(dim=0, name="data"), - opts.ParallelAxisOpts(dim=1, name="AQI"), - opts.ParallelAxisOpts(dim=2, name="PM2.5"), - opts.ParallelAxisOpts(dim=3, name="PM10"), - opts.ParallelAxisOpts(dim=4, name="CO"), - opts.ParallelAxisOpts(dim=5, name="NO2"), - opts.ParallelAxisOpts(dim=6, name="CO2"), - opts.ParallelAxisOpts( - dim=7, - name="等级", - type_="category", - data=["优", "良", "轻度污染", "中度污染", "重度污染", "严重污染"], - ), - ], - parallel_opts=opts.ParallelOpts(), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_parallel_base_v1(self, fake_writer): + data = [ + [1, 91, 45, 125, 0.82, 34, 23, "良"], + [2, 65, 27, 78, 0.86, 45, 29, "良"], + [3, 83, 60, 84, 1.09, 73, 27, "良"], + [4, 109, 81, 121, 1.28, 68, 51, "轻度污染"], + [5, 106, 77, 114, 1.07, 55, 51, "轻度污染"], + [6, 109, 81, 121, 1.28, 68, 51, "轻度污染"], + [7, 106, 77, 114, 1.07, 55, 51, "轻度污染"], + [8, 89, 65, 78, 0.86, 51, 26, "良"], + [9, 53, 33, 47, 0.64, 50, 17, "良"], + [10, 80, 55, 80, 1.01, 75, 24, "良"], + [11, 117, 81, 124, 1.03, 45, 24, "轻度污染"], + [12, 99, 71, 142, 1.1, 62, 42, "良"], + [13, 95, 69, 130, 1.28, 74, 50, "良"], + [14, 116, 87, 131, 1.47, 84, 40, "轻度污染"], + ] + c = ( + Parallel() + .add_schema( + schema=[ + opts.ParallelAxisOpts(dim=0, name="data"), + opts.ParallelAxisOpts(dim=1, name="AQI"), + opts.ParallelAxisOpts(dim=2, name="PM2.5"), + opts.ParallelAxisOpts(dim=3, name="PM10"), + opts.ParallelAxisOpts(dim=4, name="CO"), + opts.ParallelAxisOpts(dim=5, name="NO2"), + opts.ParallelAxisOpts(dim=6, name="CO2"), + opts.ParallelAxisOpts( + dim=7, + name="等级", + type_="category", + data=[ + "优", + "良", + "轻度污染", + "中度污染", + "重度污染", + "严重污染", + ], + ), + ], + parallel_opts=opts.ParallelOpts(), + ) + .add("parallel", data) + .set_global_opts(title_opts=opts.TitleOpts(title="Parallel-Category")) ) - .add("parallel", data) - .set_global_opts(title_opts=opts.TitleOpts(title="Parallel-Category")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_pictorialbar.py b/test/test_pictorialbar.py index 66fa3bc99..3a730842a 100644 --- a/test/test_pictorialbar.py +++ b/test/test_pictorialbar.py @@ -1,26 +1,26 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts.charts import PictorialBar -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_pictorialbar_base(fake_writer): - c = ( - PictorialBar() - .add_xaxis(["山西", "四川", "西藏", "北京", "上海", "内蒙古"]) - .add_yaxis( - "", - [13, 42, 67, 81, 86, 94, 166], - symbol_size=18, - symbol_repeat="fixed", - symbol_offset=[0, 0], - is_symbol_clip=True, +class TestPictorialBarChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_pictorialbar_base(self, fake_writer): + c = ( + PictorialBar() + .add_xaxis(["山西", "四川", "西藏", "北京", "上海", "内蒙古"]) + .add_yaxis( + "", + [13, 42, 67, 81, 86, 94, 166], + symbol_size=18, + symbol_repeat="fixed", + symbol_offset=[0, 0], + is_symbol_clip=True, + ) + .reversal_axis() ) - .reversal_axis() - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_pie.py b/test/test_pie.py index 2422cbce3..999283ca5 100644 --- a/test/test_pie.py +++ b/test/test_pie.py @@ -1,92 +1,90 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Pie from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_pie_base(fake_writer): - c = ( - Pie() - .add("", [list(z) for z in zip(Faker.choose(), Faker.values())]) - .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_pie_item_base(fake_writer): - d = [ - opts.PieItem(name="河马", value=131), - opts.PieItem(name="蟒蛇", value=89), - opts.PieItem(name="老虎", value=149), - opts.PieItem(name="大象", value=178), - ] - c = ( - Pie() - .add("", d) - .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_pie_dataset(fake_writer): - c = ( - Pie() - .add_dataset( - source=[ - ["product", "2012", "2013", "2014", "2015", "2016", "2017"], - ["Matcha Latte", 41.1, 30.4, 65.1, 53.3, 83.8, 98.7], - ["Milk Tea", 86.5, 92.1, 85.7, 83.1, 73.4, 55.1], - ["Cheese Cocoa", 24.1, 67.2, 79.5, 86.4, 65.2, 82.5], - ["Walnut Brownie", 55.2, 67.1, 69.2, 72.4, 53.9, 39.1], - ] +class TestPieChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_pie_base(self, fake_writer): + c = ( + Pie() + .add("", [list(z) for z in zip(Faker.choose(), Faker.values())]) + .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) ) - .add( - series_name="Matcha Latte", - data_pair=[], - radius=60, - center=["25%", "30%"], - encode={"itemName": "product", "value": "2012"}, - ) - .add( - series_name="Milk Tea", - data_pair=[], - radius=60, - center=["75%", "30%"], - encode={"itemName": "product", "value": "2013"}, - ) - .add( - series_name="Cheese Cocoa", - data_pair=[], - radius=60, - center=["25%", "75%"], - encode={"itemName": "product", "value": "2014"}, - ) - .add( - series_name="Walnut Brownie", - data_pair=[], - radius=60, - center=["75%", "75%"], - encode={"itemName": "product", "value": "2015"}, + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_pie_item_base(self, fake_writer): + d = [ + opts.PieItem(name="河马", value=131), + opts.PieItem(name="蟒蛇", value=89), + opts.PieItem(name="老虎", value=149), + opts.PieItem(name="大象", value=178), + ] + c = ( + Pie() + .add("", d) + .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) ) - .set_global_opts( - title_opts=opts.TitleOpts(title="Dataset simple pie example"), - legend_opts=opts.LegendOpts(pos_left="30%", pos_top="2%"), + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_pie_dataset(self, fake_writer): + c = ( + Pie() + .add_dataset( + source=[ + ["product", "2012", "2013", "2014", "2015", "2016", "2017"], + ["Matcha Latte", 41.1, 30.4, 65.1, 53.3, 83.8, 98.7], + ["Milk Tea", 86.5, 92.1, 85.7, 83.1, 73.4, 55.1], + ["Cheese Cocoa", 24.1, 67.2, 79.5, 86.4, 65.2, 82.5], + ["Walnut Brownie", 55.2, 67.1, 69.2, 72.4, 53.9, 39.1], + ] + ) + .add( + series_name="Matcha Latte", + data_pair=[], + radius=60, + center=["25%", "30%"], + encode={"itemName": "product", "value": "2012"}, + ) + .add( + series_name="Milk Tea", + data_pair=[], + radius=60, + center=["75%", "30%"], + encode={"itemName": "product", "value": "2013"}, + ) + .add( + series_name="Cheese Cocoa", + data_pair=[], + radius=60, + center=["25%", "75%"], + encode={"itemName": "product", "value": "2014"}, + ) + .add( + series_name="Walnut Brownie", + data_pair=[], + radius=60, + center=["75%", "75%"], + encode={"itemName": "product", "value": "2015"}, + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="Dataset simple pie example"), + legend_opts=opts.LegendOpts(pos_left="30%", pos_top="2%"), + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("dataset", content) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("dataset", content) diff --git a/test/test_polar.py b/test/test_polar.py index 050472d7a..d3e5890e6 100644 --- a/test/test_polar.py +++ b/test/test_polar.py @@ -1,57 +1,57 @@ import random +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import Polar -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_polar_scatter(fake_writer): - data = [(i, random.randint(1, 100)) for i in range(101)] - c = Polar().add("", data, type_="scatter", label_opts=opts.LabelOpts(is_show=False)) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_polar_effect_scatter(fake_writer): - data = [(i, random.randint(1, 100)) for i in range(101)] - c = ( - Polar() - .add( - "", - data, - type_="effectScatter", - effect_opts=opts.EffectOpts(scale=10, period=5), - label_opts=opts.LabelOpts(is_show=False), +class TestPolarChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_polar_scatter(self, fake_writer): + data = [(i, random.randint(1, 100)) for i in range(101)] + c = Polar().add( + "", data, type_="scatter", label_opts=opts.LabelOpts(is_show=False) ) - .set_global_opts(title_opts=opts.TitleOpts(title="Polar-EffectScatter")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_polar_base_1(fake_writer): - data = [(i, random.randint(1, 100)) for i in range(101)] - c = ( - Polar() - .add( - "", - data, - type_="effectScatter", - effect_opts=opts.EffectOpts(scale=10, period=5), - label_opts=opts.LabelOpts(is_show=True), + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_polar_effect_scatter(self, fake_writer): + data = [(i, random.randint(1, 100)) for i in range(101)] + c = ( + Polar() + .add( + "", + data, + type_="effectScatter", + effect_opts=opts.EffectOpts(scale=10, period=5), + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Polar-EffectScatter")) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_polar_base_1(self, fake_writer): + data = [(i, random.randint(1, 100)) for i in range(101)] + c = ( + Polar() + .add( + "", + data, + type_="effectScatter", + effect_opts=opts.EffectOpts(scale=10, period=5), + label_opts=opts.LabelOpts(is_show=True), + ) + .set_global_opts(title_opts=opts.TitleOpts(title="Polar-Base-1")) ) - .set_global_opts(title_opts=opts.TitleOpts(title="Polar-Base-1")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_radar.py b/test/test_radar.py index 1adbbc85d..47d4c27cb 100644 --- a/test/test_radar.py +++ b/test/test_radar.py @@ -1,7 +1,6 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Radar @@ -9,87 +8,86 @@ v2 = [(5000, 14000, 28000, 31000, 42000, 21000)] -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_radar_base(fake_writer): - c = ( - Radar() - .add_schema( - schema=[ - opts.RadarIndicatorItem(name="销售", max_=6500), - opts.RadarIndicatorItem(name="管理", max_=16000), - opts.RadarIndicatorItem(name="信息技术", max_=30000), - opts.RadarIndicatorItem(name="客服", max_=38000), - opts.RadarIndicatorItem(name="研发", max_=52000), - opts.RadarIndicatorItem(name="市场", max_=25000), - ] +class TestRadarChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_radar_base(self, fake_writer): + c = ( + Radar() + .add_schema( + schema=[ + opts.RadarIndicatorItem(name="销售", max_=6500), + opts.RadarIndicatorItem(name="管理", max_=16000), + opts.RadarIndicatorItem(name="信息技术", max_=30000), + opts.RadarIndicatorItem(name="客服", max_=38000), + opts.RadarIndicatorItem(name="研发", max_=52000), + opts.RadarIndicatorItem(name="市场", max_=25000), + ] + ) + .add("预算分配", v1) + .add("实际开销", v2) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) ) - .add("预算分配", v1) - .add("实际开销", v2) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_radar_item_base(fake_writer): - series_names = ["预算分配", "实际开销"] - series_data = [ - [4300, 10000, 28000, 35000, 50000, 19000], - [5000, 14000, 28000, 31000, 42000, 21000], - ] - radar_item = [ - opts.RadarItem(name=d[0], value=d[1]) - for d in list(zip(series_names, series_data)) - ] + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_radar_item_base(self, fake_writer): + series_names = ["预算分配", "实际开销"] + series_data = [ + [4300, 10000, 28000, 35000, 50000, 19000], + [5000, 14000, 28000, 31000, 42000, 21000], + ] + radar_item = [ + opts.RadarItem(name=d[0], value=d[1]) + for d in list(zip(series_names, series_data)) + ] - c = ( - Radar() - .add_schema( - schema=[ - opts.RadarIndicatorItem(name="销售", max_=6500), - opts.RadarIndicatorItem(name="管理", max_=16000), - opts.RadarIndicatorItem(name="信息技术", max_=30000), - opts.RadarIndicatorItem(name="客服", max_=38000), - opts.RadarIndicatorItem(name="研发", max_=52000), - opts.RadarIndicatorItem(name="市场", max_=25000), - ] + c = ( + Radar() + .add_schema( + schema=[ + opts.RadarIndicatorItem(name="销售", max_=6500), + opts.RadarIndicatorItem(name="管理", max_=16000), + opts.RadarIndicatorItem(name="信息技术", max_=30000), + opts.RadarIndicatorItem(name="客服", max_=38000), + opts.RadarIndicatorItem(name="研发", max_=52000), + opts.RadarIndicatorItem(name="市场", max_=25000), + ] + ) + .add("", radar_item) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) + .set_global_opts(title_opts=opts.TitleOpts(title="Radar-基本示例")) ) - .add("", radar_item) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - .set_global_opts(title_opts=opts.TitleOpts(title="Radar-基本示例")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_radar_options(fake_writer): - c = ( - Radar() - .add_schema( - schema=[ - opts.RadarIndicatorItem(name="销售", max_=6500), - opts.RadarIndicatorItem(name="管理", max_=16000), - opts.RadarIndicatorItem(name="信息技术", max_=30000), - opts.RadarIndicatorItem(name="客服", max_=38000), - opts.RadarIndicatorItem(name="研发", max_=52000), - opts.RadarIndicatorItem(name="市场", max_=25000), - ], - radiusaxis_opts=opts.RadiusAxisOpts(), - angleaxis_opts=opts.AngleAxisOpts(), - polar_opts=opts.PolarOpts(), + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_radar_options(self, fake_writer): + c = ( + Radar() + .add_schema( + schema=[ + opts.RadarIndicatorItem(name="销售", max_=6500), + opts.RadarIndicatorItem(name="管理", max_=16000), + opts.RadarIndicatorItem(name="信息技术", max_=30000), + opts.RadarIndicatorItem(name="客服", max_=38000), + opts.RadarIndicatorItem(name="研发", max_=52000), + opts.RadarIndicatorItem(name="市场", max_=25000), + ], + radiusaxis_opts=opts.RadiusAxisOpts(), + angleaxis_opts=opts.AngleAxisOpts(), + polar_opts=opts.PolarOpts(), + ) + .add("预算分配", v1) + .add("实际开销", v2) + .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) ) - .add("预算分配", v1) - .add("实际开销", v2) - .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("radiusAxis", content) - assert_in("angleAxis", content) - assert_in("polar", content) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("radiusAxis", content) + self.assertIn("angleAxis", content) + self.assertIn("polar", content) diff --git a/test/test_sankey.py b/test/test_sankey.py index 278f74bca..2338ad1b8 100644 --- a/test/test_sankey.py +++ b/test/test_sankey.py @@ -1,72 +1,71 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Sankey -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_sankey_base(fake_writer): - nodes = [{"name": "category1"}, {"name": "category2"}, {"name": "category3"}] - - links = [ - {"source": "category1", "target": "category2", "value": 10}, - {"source": "category2", "target": "category3", "value": 15}, - ] - c = Sankey().add( - "sankey", - nodes, - links, - layout_iterations=16, - linestyle_opt=opts.LineStyleOpts(opacity=0.2, curve=0.5, color="source"), - label_opts=opts.LabelOpts(position="right"), - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - assert_in("layoutIteration", content) +class TestSankeyChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_sankey_base(self, fake_writer): + nodes = [{"name": "category1"}, {"name": "category2"}, {"name": "category3"}] + links = [ + {"source": "category1", "target": "category2", "value": 10}, + {"source": "category2", "target": "category3", "value": 15}, + ] + c = Sankey().add( + "sankey", + nodes, + links, + layout_iterations=16, + linestyle_opt=opts.LineStyleOpts(opacity=0.2, curve=0.5, color="source"), + label_opts=opts.LabelOpts(position="right"), + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + self.assertIn("layoutIteration", content) -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_sankey_new_opts(fake_writer): - nodes = [ - {"name": "a"}, - {"name": "b"}, - {"name": "a1"}, - {"name": "b1"}, - {"name": "c"}, - {"name": "e"}, - ] - links = [ - {"source": "a", "target": "a1", "value": 5}, - {"source": "e", "target": "b", "value": 3}, - {"source": "a", "target": "b1", "value": 3}, - {"source": "b1", "target": "a1", "value": 1}, - {"source": "b1", "target": "c", "value": 2}, - {"source": "b", "target": "c", "value": 1}, - ] - c = Sankey().add( - "sankey", - nodes, - links, - pos_bottom="10%", - orient="vertical", - levels=[ - opts.SankeyLevelsOpts( - depth=0, - itemstyle_opts=opts.ItemStyleOpts(color="#eee"), - linestyle_opts=opts.LineStyleOpts(color="source", opacity=0.6), - ) - ], - linestyle_opt=opts.LineStyleOpts(opacity=0.2, curve=0.5, color="source"), - label_opts=opts.LabelOpts(position="right"), - layout_iterations=30, - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("bottom", content) - assert_in("orient", content) - assert_in("levels", content) - assert_in("layoutIterations", content) + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_sankey_new_opts(self, fake_writer): + nodes = [ + {"name": "a"}, + {"name": "b"}, + {"name": "a1"}, + {"name": "b1"}, + {"name": "c"}, + {"name": "e"}, + ] + links = [ + {"source": "a", "target": "a1", "value": 5}, + {"source": "e", "target": "b", "value": 3}, + {"source": "a", "target": "b1", "value": 3}, + {"source": "b1", "target": "a1", "value": 1}, + {"source": "b1", "target": "c", "value": 2}, + {"source": "b", "target": "c", "value": 1}, + ] + c = Sankey().add( + "sankey", + nodes, + links, + pos_bottom="10%", + orient="vertical", + levels=[ + opts.SankeyLevelsOpts( + depth=0, + itemstyle_opts=opts.ItemStyleOpts(color="#eee"), + linestyle_opts=opts.LineStyleOpts(color="source", opacity=0.6), + ) + ], + linestyle_opt=opts.LineStyleOpts(opacity=0.2, curve=0.5, color="source"), + label_opts=opts.LabelOpts(position="right"), + layout_iterations=30, + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("bottom", content) + self.assertIn("orient", content) + self.assertIn("levels", content) + self.assertIn("layoutIterations", content) diff --git a/test/test_scatter.py b/test/test_scatter.py index a24f582fb..b139a77f6 100644 --- a/test/test_scatter.py +++ b/test/test_scatter.py @@ -1,231 +1,235 @@ import json +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Scatter, Grid from pyecharts.commons.utils import JsCode from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_scatter_base(fake_writer): - c = ( - Scatter() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_scatter_base_no_xaxis(fake_writer): - c = ( - Scatter() - .add_xaxis([]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_scatter_item_base(fake_writer): - x_axis = ["A", "B", "C"] - y_axis = [1, 2, 4] - chart_item = [ - opts.ScatterItem(name=d[0], value=d[1]) for d in list(zip(x_axis, y_axis)) - ] - - c = ( - Scatter() - .add_xaxis(x_axis) - .add_yaxis("series0", chart_item) - .set_global_opts(title_opts=opts.TitleOpts(title="Scatter-基本示例")) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_scatter_dataset(fake_writer): - with open("test/fixtures/life-expectancy-table.json", "r", encoding="utf-8") as f: - j = json.load(f) - - l1_1 = ( - Scatter() - .add_dataset( - dimensions=[ - "Income", - "Life Expectancy", - "Population", - "Country", - {"name": "Year", "type": "ordinal"}, - ], - source=j, - ) - .add_yaxis( - series_name="", - y_axis=[], - symbol_size=2.5, - xaxis_index=0, - yaxis_index=0, - encode={"x": "Income", "y": "Life Expectancy", "tooltip": [0, 1, 2, 3, 4]}, - label_opts=opts.LabelOpts(is_show=False), +class TestScatterChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_scatter_base(self, fake_writer): + c = ( + Scatter() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) ) - .set_global_opts( - xaxis_opts=opts.AxisOpts( - type_="value", - grid_index=0, - name="Income", - axislabel_opts=opts.LabelOpts(rotate=50, interval=0), - ), - yaxis_opts=opts.AxisOpts( - type_="value", grid_index=0, name="Life Expectancy" - ), - title_opts=opts.TitleOpts(title="Encode and Matrix"), + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_scatter_base_no_xaxis(self, fake_writer): + c = ( + Scatter() + .add_xaxis([]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) ) - ) - - l1_2 = ( - Scatter() - .add_dataset() - .add_yaxis( - series_name="", - y_axis=[], - symbol_size=2.5, - xaxis_index=1, - yaxis_index=1, - encode={"x": "Country", "y": "Income", "tooltip": [0, 1, 2, 3, 4]}, - label_opts=opts.LabelOpts(is_show=False), + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_scatter_item_base(self, fake_writer): + x_axis = ["A", "B", "C"] + y_axis = [1, 2, 4] + chart_item = [ + opts.ScatterItem(name=d[0], value=d[1]) for d in list(zip(x_axis, y_axis)) + ] + + c = ( + Scatter() + .add_xaxis(x_axis) + .add_yaxis("series0", chart_item) + .set_global_opts(title_opts=opts.TitleOpts(title="Scatter-基本示例")) ) - .set_global_opts( - xaxis_opts=opts.AxisOpts( - type_="category", - grid_index=1, - name="Country", - boundary_gap=False, - axislabel_opts=opts.LabelOpts(rotate=50, interval=0), - ), - yaxis_opts=opts.AxisOpts(type_="value", grid_index=1, name="Income"), + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_scatter_dataset(self, fake_writer): + with open( + "test/fixtures/life-expectancy-table.json", "r", encoding="utf-8" + ) as f: + j = json.load(f) + + l1_1 = ( + Scatter() + .add_dataset( + dimensions=[ + "Income", + "Life Expectancy", + "Population", + "Country", + {"name": "Year", "type": "ordinal"}, + ], + source=j, + ) + .add_yaxis( + series_name="", + y_axis=[], + symbol_size=2.5, + xaxis_index=0, + yaxis_index=0, + encode={ + "x": "Income", + "y": "Life Expectancy", + "tooltip": [0, 1, 2, 3, 4], + }, + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts( + type_="value", + grid_index=0, + name="Income", + axislabel_opts=opts.LabelOpts(rotate=50, interval=0), + ), + yaxis_opts=opts.AxisOpts( + type_="value", grid_index=0, name="Life Expectancy" + ), + title_opts=opts.TitleOpts(title="Encode and Matrix"), + ) ) - ) - l2_1 = ( - Scatter() - .add_dataset() - .add_yaxis( - series_name="", - y_axis=[], - symbol_size=2.5, - xaxis_index=2, - yaxis_index=2, - encode={"x": "Income", "y": "Population", "tooltip": [0, 1, 2, 3, 4]}, - label_opts=opts.LabelOpts(is_show=False), + l1_2 = ( + Scatter() + .add_dataset() + .add_yaxis( + series_name="", + y_axis=[], + symbol_size=2.5, + xaxis_index=1, + yaxis_index=1, + encode={"x": "Country", "y": "Income", "tooltip": [0, 1, 2, 3, 4]}, + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts( + type_="category", + grid_index=1, + name="Country", + boundary_gap=False, + axislabel_opts=opts.LabelOpts(rotate=50, interval=0), + ), + yaxis_opts=opts.AxisOpts(type_="value", grid_index=1, name="Income"), + ) ) - .set_global_opts( - xaxis_opts=opts.AxisOpts( - type_="value", - grid_index=2, - name="Income", - axislabel_opts=opts.LabelOpts(rotate=50, interval=0), - ), - yaxis_opts=opts.AxisOpts(type_="value", grid_index=2, name="Population"), - ) - ) - l2_2 = ( - Scatter() - .add_dataset() - .add_yaxis( - series_name="", - y_axis=[], - symbol_size=2.5, - xaxis_index=3, - yaxis_index=3, - encode={ - "x": "Life Expectancy", - "y": "Population", - "tooltip": [0, 1, 2, 3, 4], - }, - label_opts=opts.LabelOpts(is_show=False), - ) - .set_global_opts( - xaxis_opts=opts.AxisOpts( - type_="value", - grid_index=3, - name="Life Expectancy", - axislabel_opts=opts.LabelOpts(rotate=50, interval=0), - ), - yaxis_opts=opts.AxisOpts(type_="value", grid_index=3, name="Population"), + l2_1 = ( + Scatter() + .add_dataset() + .add_yaxis( + series_name="", + y_axis=[], + symbol_size=2.5, + xaxis_index=2, + yaxis_index=2, + encode={"x": "Income", "y": "Population", "tooltip": [0, 1, 2, 3, 4]}, + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts( + type_="value", + grid_index=2, + name="Income", + axislabel_opts=opts.LabelOpts(rotate=50, interval=0), + ), + yaxis_opts=opts.AxisOpts( + type_="value", grid_index=2, name="Population" + ), + ) ) - ) - grid = ( - Grid(init_opts=opts.InitOpts(width="1280px", height="960px")) - .add( - chart=l1_1, - grid_opts=opts.GridOpts(pos_right="57%", pos_bottom="57%"), - grid_index=0, - ) - .add( - chart=l1_2, - grid_opts=opts.GridOpts(pos_left="57%", pos_bottom="57%"), - grid_index=1, + l2_2 = ( + Scatter() + .add_dataset() + .add_yaxis( + series_name="", + y_axis=[], + symbol_size=2.5, + xaxis_index=3, + yaxis_index=3, + encode={ + "x": "Life Expectancy", + "y": "Population", + "tooltip": [0, 1, 2, 3, 4], + }, + label_opts=opts.LabelOpts(is_show=False), + ) + .set_global_opts( + xaxis_opts=opts.AxisOpts( + type_="value", + grid_index=3, + name="Life Expectancy", + axislabel_opts=opts.LabelOpts(rotate=50, interval=0), + ), + yaxis_opts=opts.AxisOpts( + type_="value", grid_index=3, name="Population" + ), + ) ) - .add( - chart=l2_1, - grid_opts=opts.GridOpts(pos_right="57%", pos_top="57%"), - grid_index=2, + + grid = ( + Grid(init_opts=opts.InitOpts(width="1280px", height="960px")) + .add( + chart=l1_1, + grid_opts=opts.GridOpts(pos_right="57%", pos_bottom="57%"), + grid_index=0, + ) + .add( + chart=l1_2, + grid_opts=opts.GridOpts(pos_left="57%", pos_bottom="57%"), + grid_index=1, + ) + .add( + chart=l2_1, + grid_opts=opts.GridOpts(pos_right="57%", pos_top="57%"), + grid_index=2, + ) + .add( + chart=l2_2, + grid_opts=opts.GridOpts(pos_left="57%", pos_top="57%"), + grid_index=3, + ) ) - .add( - chart=l2_2, - grid_opts=opts.GridOpts(pos_left="57%", pos_top="57%"), - grid_index=3, + grid.render() + _, content = fake_writer.call_args[0] + self.assertIn("grid", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_scatter_multi_dimension_data(self, fake_writer): + js_code_for_label_opts = JsCode( + "function(params){return params.value[1] +' : '+ params.value[2];}" ) - ) - grid.render() - _, content = fake_writer.call_args[0] - assert_in("grid", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_scatter_multi_dimension_data(fake_writer): - c = ( - Scatter() - .add_xaxis(Faker.choose()) - .add_yaxis( - "商家A", - [list(z) for z in zip(Faker.values(), Faker.choose())], - label_opts=opts.LabelOpts( - formatter=JsCode( - "function(params){return params.value[1] +' : '+ params.value[2];}" - ) - ), + js_code_for_tooltip = JsCode( + "function (params) {return params.name + ' : ' + params.value[2];}" ) - .set_global_opts( - title_opts=opts.TitleOpts(title="Scatter-多维度数据"), - tooltip_opts=opts.TooltipOpts( - formatter=JsCode( - "function (params) {return params.name + ' : ' + params.value[2];}" - ) - ), - visualmap_opts=opts.VisualMapOpts( - type_="color", max_=150, min_=20, dimension=1 - ), + c = ( + Scatter() + .add_xaxis(Faker.choose()) + .add_yaxis( + "商家A", + [list(z) for z in zip(Faker.values(), Faker.choose())], + label_opts=opts.LabelOpts(formatter=js_code_for_label_opts), + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="Scatter-多维度数据"), + tooltip_opts=opts.TooltipOpts(formatter=js_code_for_tooltip), + visualmap_opts=opts.VisualMapOpts( + type_="color", max_=150, min_=20, dimension=1 + ), + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_scatter3d.py b/test/test_scatter3d.py index 6072d08b7..df1662c1a 100644 --- a/test/test_scatter3d.py +++ b/test/test_scatter3d.py @@ -1,27 +1,27 @@ import random +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import Scatter3D from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_scatter3d_base(fake_writer): - data = [ - [random.randint(0, 100), random.randint(0, 100), random.randint(0, 100)] - for _ in range(80) - ] - c = ( - Scatter3D() - .add("", data) - .set_global_opts( - visualmap_opts=opts.VisualMapOpts(range_color=Faker.visual_color) +class TestScatter3D(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_scatter3d_base(self, fake_writer): + data = [ + [random.randint(0, 100), random.randint(0, 100), random.randint(0, 100)] + for _ in range(80) + ] + c = ( + Scatter3D() + .add("", data) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(range_color=Faker.visual_color) + ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_series_options.py b/test/test_series_options.py index 99d7e55de..f7f71b63f 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -1,4 +1,4 @@ -from nose.tools import assert_equal +import unittest from pyecharts.commons.utils import remove_key_with_none_value from pyecharts.options.series_options import ( @@ -14,145 +14,145 @@ ) -def test_label_options_defaults(): - option = LabelOpts() - expected = { - "show": True, - "position": None, - "color": None, - "distance": None, - "rotate": None, - "margin": 8, - "interval": None, - "fontSize": None, - "fontStyle": None, - "fontWeight": None, - "fontFamily": None, - "align": None, - "verticalAlign": None, - "formatter": None, - "backgroundColor": None, - "borderColor": None, - "borderWidth": None, - "borderRadius": None, - "padding": None, - "width": None, - "height": None, - "overflow": None, - "rich": None, - } - assert_equal(expected, option.opts) - - -def test_label_options_custom(): - option = LabelOpts( - background_color="red", border_color="green", border_width=1, border_radius=2 - ) - expected = { - "show": True, - "position": None, - "color": None, - "distance": None, - "rotate": None, - "margin": 8, - "interval": None, - "fontSize": None, - "fontStyle": None, - "fontWeight": None, - "fontFamily": None, - "align": None, - "verticalAlign": None, - "formatter": None, - "backgroundColor": "red", - "borderColor": "green", - "borderWidth": 1, - "borderRadius": 2, - "padding": None, - "width": None, - "height": None, - "overflow": None, - "rich": None, - } - assert_equal(expected, option.opts) - - -def test_mark_point_item_remove_none(): - item = MarkPointItem() - expected = {} - assert_equal(expected, remove_key_with_none_value(item.opts)) - - -def test_mark_line_item_remove_none(): - item = MarkLineItem() - expected = {} - assert_equal(expected, remove_key_with_none_value(item.opts)) - - -def test_mark_area_item_remove_none(): - item = MarkAreaItem() - expected = [ - { - "itemStyle": None, - "label": None, - "name": None, - "type": None, - "valueDim": None, - "valueIndex": None, - "xAxis": None, - "yAxis": None, - }, - { - "type": None, - "valueDim": None, - "valueIndex": None, - "xAxis": None, - "yAxis": None, - }, - ] - assert_equal(expected, remove_key_with_none_value(item.opts)) - - -def test_mark_area_options_remove_none(): - label_opts = LabelOpts() - option = MarkAreaOpts(label_opts=label_opts) - expected = { - "silent": False, - "label": label_opts, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_tree_map_breadcrumb_options_remove_none(): - item_opts = ItemStyleOpts() - option = TreeMapBreadcrumbOpts(item_opts=item_opts) - expected = { - "show": True, - "left": "center", - "right": "auto", - "top": "auto", - "bottom": 0, - "height": 22, - "emptyItemWidth": 25, - "itemStyle": item_opts, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_minor_tick_options_remove_none(): - option = MinorTickOpts() - expected = { - "show": False, - "splitNumber": 5, - "length": 3, - } - assert_equal(expected, remove_key_with_none_value(option.opts)) - - -def test_minor_split_line_options_remove_none(): - option = MinorSplitLineOpts() - expected = { - "show": False, - "width": 1, - "type": "solid", - } - assert_equal(expected, remove_key_with_none_value(option.opts)) +class TestSeriesOptions(unittest.TestCase): + def test_label_options_defaults(self): + option = LabelOpts() + expected = { + "show": True, + "position": None, + "color": None, + "distance": None, + "rotate": None, + "margin": 8, + "interval": None, + "fontSize": None, + "fontStyle": None, + "fontWeight": None, + "fontFamily": None, + "align": None, + "verticalAlign": None, + "formatter": None, + "backgroundColor": None, + "borderColor": None, + "borderWidth": None, + "borderRadius": None, + "padding": None, + "width": None, + "height": None, + "overflow": None, + "rich": None, + } + self.assertEqual(expected, option.opts) + + def test_label_options_custom(self): + option = LabelOpts( + background_color="red", + border_color="green", + border_width=1, + border_radius=2, + ) + expected = { + "show": True, + "position": None, + "color": None, + "distance": None, + "rotate": None, + "margin": 8, + "interval": None, + "fontSize": None, + "fontStyle": None, + "fontWeight": None, + "fontFamily": None, + "align": None, + "verticalAlign": None, + "formatter": None, + "backgroundColor": "red", + "borderColor": "green", + "borderWidth": 1, + "borderRadius": 2, + "padding": None, + "width": None, + "height": None, + "overflow": None, + "rich": None, + } + self.assertEqual(expected, option.opts) + + def test_mark_point_item_remove_none(self): + item = MarkPointItem() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(item.opts)) + + def test_mark_line_item_remove_none(self): + item = MarkLineItem() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(item.opts)) + + def test_mark_area_item_remove_none(self): + item = MarkAreaItem() + expected = [ + { + "itemStyle": None, + "label": None, + "name": None, + "type": None, + "valueDim": None, + "valueIndex": None, + "xAxis": None, + "yAxis": None, + }, + { + "type": None, + "valueDim": None, + "valueIndex": None, + "xAxis": None, + "yAxis": None, + }, + ] + self.assertEqual(expected, remove_key_with_none_value(item.opts)) + + def test_mark_area_options_remove_none(self): + label_opts = LabelOpts() + option = MarkAreaOpts(label_opts=label_opts) + expected = { + "silent": False, + "label": label_opts, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_tree_map_breadcrumb_options_remove_none(self): + item_opts = ItemStyleOpts() + option = TreeMapBreadcrumbOpts(item_opts=item_opts) + expected = { + "show": True, + "left": "center", + "right": "auto", + "top": "auto", + "bottom": 0, + "height": 22, + "emptyItemWidth": 25, + "itemStyle": item_opts, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_minor_tick_options_remove_none(self): + option = MinorTickOpts() + expected = { + "show": False, + "splitNumber": 5, + "length": 3, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_minor_split_line_options_remove_none(self): + option = MinorSplitLineOpts() + expected = { + "show": False, + "width": 1, + "type": "solid", + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_area_color_in_item_styles(self): + op = ItemStyleOpts(area_color="red") + self.assertEqual(op.opts["areaColor"], "red") diff --git a/test/test_snapshot.py b/test/test_snapshot.py index c83280edf..e1ad5289f 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -1,9 +1,7 @@ import os import unittest from io import BytesIO -from unittest.mock import patch, MagicMock - -from nose.tools import assert_equal, raises +from unittest.mock import patch from pyecharts.charts import Bar from pyecharts.render import make_snapshot @@ -26,99 +24,90 @@ def make_snapshot(self, *args, **kwargs): return Engine(content) -def test_decode_base64(): - assert decode_base64(data="abcde12") == b"i\xb7\x1d{]" - - -def test_save_as_png(): - save_as_png(image_data=b"i\xb7\x1d{]", output_name="text_png.png") - os.unlink("text_png.png") - - -def test_save_as_text(): - save_as_text(image_data="test data", output_name="test_txt.txt") - os.unlink("test_txt.txt") - - -def test_save_as(): - with open("test/fixtures/img1.jpg", "rb") as f: - image_bytes = f.read() - save_as(image_data=image_bytes, output_name="test_pdf.pdf", file_type="pdf") - os.unlink("test_pdf.pdf") - save_as(image_data=image_bytes, output_name="test_gif.gif", file_type="gif") - os.unlink("test_gif.gif") - save_as(image_data=image_bytes, output_name="test_eps.eps", file_type="eps") - os.unlink("test_eps.eps") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def _gen_bar_chart(fake_writer) -> str: - c = ( - Bar() - .add_xaxis(["A", "B", "C"]) - .add_yaxis("series0", [1, 2, 4]) - .add_yaxis("series1", [2, 3, 6]) - ) - c.render() - filename, _ = fake_writer.call_args[0] - return filename - - -@raises(OSError) -def test_make_snapshot_raise_os_error(): - eng = _gen_faker_engine("fake content") - make_snapshot(eng, _gen_bar_chart(), "make_snapshot.png") - - -@raises(TypeError) -def test_make_snapshot_raise_type_error(): - eng = _gen_faker_engine("fake content1,content2") - make_snapshot(eng, _gen_bar_chart(), "make_snapshot.pngx") - - -@patch("pyecharts.render.snapshot.save_as_png") -def test_make_snapshot_png(fake_writer): - eng = _gen_faker_engine("fake content1,content2") - make_snapshot(eng, _gen_bar_chart(), "make_snapshot.png") - _ = fake_writer.call_args[0] - assert_equal("test ok", "test ok") - - -@patch("pyecharts.render.snapshot.save_as") -def test_make_snapshot_gif(fake_writer): - eng = _gen_faker_engine("fake content1,content2") - make_snapshot(eng, _gen_bar_chart(), "make_snapshot.gif") - _ = fake_writer.call_args[0] - assert_equal("test ok", "test ok") - - -@patch("pyecharts.render.snapshot.save_as_text") -def test_make_snapshot_text(fake_writer): - eng = _gen_faker_engine("fake content1,content2") - make_snapshot(eng, _gen_bar_chart(), "make_snapshot.svg") - _ = fake_writer.call_args[0] - assert_equal("test ok", "test ok") - - -@patch("pyecharts.render.snapshot.save_as_text") -def test_make_snapshot_text_v1(fake_writer): - eng = _gen_faker_engine("fake content1,content2") - make_snapshot( - engine=eng, - file_name=_gen_bar_chart(), - output_name="make_snapshot.svg", - is_remove_html=True, - ) - _ = fake_writer.call_args[0] - assert_equal("test ok", "test ok") +class TestSnapshotComponent(unittest.TestCase): + def test_decode_base64(self): + assert decode_base64(data="abcde12") == b"i\xb7\x1d{]" + + def test_save_as_png(self): + save_as_png(image_data=b"i\xb7\x1d{]", output_name="text_png.png") + os.unlink("text_png.png") + + def test_save_as_text(self): + save_as_text(image_data="test data", output_name="test_txt.txt") + os.unlink("test_txt.txt") + + def test_save_as(self): + with open("test/fixtures/img1.jpg", "rb") as f: + image_bytes = f.read() + save_as(image_data=image_bytes, output_name="test_pdf.pdf", file_type="pdf") + os.unlink("test_pdf.pdf") + save_as(image_data=image_bytes, output_name="test_gif.gif", file_type="gif") + os.unlink("test_gif.gif") + save_as(image_data=image_bytes, output_name="test_eps.eps", file_type="eps") + os.unlink("test_eps.eps") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def _gen_bar_chart(self, fake_writer) -> str: + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + .add_yaxis("series1", [2, 3, 6]) + ) + c.render() + filename, _ = fake_writer.call_args[0] + return filename + + def test_make_snapshot_raise_os_error(self): + with self.assertRaises(OSError): + eng = _gen_faker_engine("fake content") + make_snapshot(eng, self._gen_bar_chart(), "make_snapshot.png") + + def test_make_snapshot_raise_type_error(self): + with self.assertRaises(TypeError): + eng = _gen_faker_engine("fake content1,content2") + make_snapshot(eng, self._gen_bar_chart(), "make_snapshot.pngx") + + @patch("pyecharts.render.snapshot.save_as_png") + def test_make_snapshot_png(self, fake_writer): + eng = _gen_faker_engine("fake content1,content2") + make_snapshot(eng, self._gen_bar_chart(), "make_snapshot.png") + _ = fake_writer.call_args[0] + self.assertEqual("test ok", "test ok") + + @patch("pyecharts.render.snapshot.save_as") + def test_make_snapshot_gif(self, fake_writer): + eng = _gen_faker_engine("fake content1,content2") + make_snapshot(eng, self._gen_bar_chart(), "make_snapshot.gif") + _ = fake_writer.call_args[0] + self.assertEqual("test ok", "test ok") + + @patch("pyecharts.render.snapshot.save_as_text") + def test_make_snapshot_text(self, fake_writer): + eng = _gen_faker_engine("fake content1,content2") + make_snapshot(eng, self._gen_bar_chart(), "make_snapshot.svg") + _ = fake_writer.call_args[0] + self.assertEqual("test ok", "test ok") + + @patch("pyecharts.render.snapshot.save_as_text") + def test_make_snapshot_text_v1(self, fake_writer): + eng = _gen_faker_engine("fake content1,content2") + make_snapshot( + engine=eng, + file_name=self._gen_bar_chart(), + output_name="make_snapshot.svg", + is_remove_html=True, + ) + _ = fake_writer.call_args[0] + self.assertEqual("test ok", "test ok") class TestSaveAs(unittest.TestCase): - @patch('builtins.__import__', side_effect=ModuleNotFoundError) + @patch("builtins.__import__", side_effect=ModuleNotFoundError) def test_save_as_module_not_found(self, mock_import): - image_data = b'fake_image_data' - output_name = 'output.jpg' - file_type = 'JPEG' + image_data = b"fake_image_data" + output_name = "output.jpg" + file_type = "JPEG" with self.assertRaises(Exception) as context: save_as(image_data, output_name, file_type) diff --git a/test/test_sunburst.py b/test/test_sunburst.py index 0b08bdfe1..b2093bc38 100644 --- a/test/test_sunburst.py +++ b/test/test_sunburst.py @@ -1,62 +1,61 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import Sunburst -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_sunburst_base(fake_writer): - data = [ - { - "name": "Grandpa", - "children": [ - { - "name": "Uncle Leo", - "value": 15, - "children": [ - {"name": "Cousin Jack", "value": 2}, - { - "name": "Cousin Mary", - "value": 5, - "children": [{"name": "Jackson", "value": 2}], - }, - {"name": "Cousin Ben", "value": 4}, - ], - }, - { - "name": "Father", - "value": 10, - "children": [ - {"name": "Me", "value": 5}, - {"name": "Brother Peter", "value": 1}, - ], - }, - ], - }, - { - "name": "Nancy", - "children": [ - { - "name": "Uncle Nike", - "children": [ - {"name": "Cousin Betty", "value": 1}, - {"name": "Cousin Jenny", "value": 2}, - ], - } - ], - }, - ] - - c = Sunburst().add("Sunburst 演示数据", data) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") +class TestSunburstChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_sunburst_base(self, fake_writer): + data = [ + { + "name": "Grandpa", + "children": [ + { + "name": "Uncle Leo", + "value": 15, + "children": [ + {"name": "Cousin Jack", "value": 2}, + { + "name": "Cousin Mary", + "value": 5, + "children": [{"name": "Jackson", "value": 2}], + }, + {"name": "Cousin Ben", "value": 4}, + ], + }, + { + "name": "Father", + "value": 10, + "children": [ + {"name": "Me", "value": 5}, + {"name": "Brother Peter", "value": 1}, + ], + }, + ], + }, + { + "name": "Nancy", + "children": [ + { + "name": "Uncle Nike", + "children": [ + {"name": "Cousin Betty", "value": 1}, + {"name": "Cousin Jenny", "value": 2}, + ], + } + ], + }, + ] + c = Sunburst().add("Sunburst 演示数据", data) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -def test_sunburst_dataitem(): - item_name = "test_data_item" - item = opts.SunburstItem(name=item_name) - assert_equal(item.opts.get("name"), item_name) + def test_sunburst_dataitem(self): + item_name = "test_data_item" + item = opts.SunburstItem(name=item_name) + self.assertEqual(item.opts.get("name"), item_name) diff --git a/test/test_surface3d.py b/test/test_surface3d.py index 6f7ffef4d..3feefc7ba 100644 --- a/test/test_surface3d.py +++ b/test/test_surface3d.py @@ -1,42 +1,42 @@ import math +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import Surface3D from pyecharts.faker import Faker -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_surface3d_base(fake_writer): - def surface3d_data(): - for t0 in range(-60, 60, 1): - y = t0 / 60 - for t1 in range(-60, 60, 1): - x = t1 / 60 - if math.fabs(x) < 0.1 and math.fabs(y) < 0.1: - z = "-" - else: - z = math.sin(x * math.pi) * math.sin(y * math.pi) - yield [x, y, z] +class TestSurface3DChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_surface3d_base(self, fake_writer): + def surface3d_data(): + for t0 in range(-60, 60, 1): + y = t0 / 60 + for t1 in range(-60, 60, 1): + x = t1 / 60 + if math.fabs(x) < 0.1 and math.fabs(y) < 0.1: + z = "-" + else: + z = math.sin(x * math.pi) * math.sin(y * math.pi) + yield [x, y, z] - c = ( - Surface3D() - .add( - "", - list(surface3d_data()), - xaxis3d_opts=opts.Axis3DOpts(type_="value"), - yaxis3d_opts=opts.Axis3DOpts(type_="value"), - grid3d_opts=opts.Grid3DOpts(width=100, height=100, depth=100), - ) - .set_global_opts( - visualmap_opts=opts.VisualMapOpts( - max_=3, min_=-3, range_color=Faker.visual_color + c = ( + Surface3D() + .add( + "", + list(surface3d_data()), + xaxis3d_opts=opts.Axis3DOpts(type_="value"), + yaxis3d_opts=opts.Axis3DOpts(type_="value"), + grid3d_opts=opts.Grid3DOpts(width=100, height=100, depth=100), + ) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts( + max_=3, min_=-3, range_color=Faker.visual_color + ) ) ) - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_tab.py b/test/test_tab.py index 4c44ef86b..afaa35305 100644 --- a/test/test_tab.py +++ b/test/test_tab.py @@ -1,8 +1,7 @@ +import unittest from typing import Iterable from unittest.mock import patch -from nose.tools import assert_equal, assert_in, assert_true - from pyecharts import options as opts from pyecharts.charts import Bar, Line, Tab from pyecharts.commons.utils import OrderedSet @@ -35,71 +34,65 @@ def _create_table() -> Table: return table -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_tab_base(fake_writer): - bar = _create_bar() - line = _create_line() - tab = Tab().add(bar, "bar-example").add(line, "line-example") - tab.render() - _, content = fake_writer.call_args[0] - assert_in("bar-example", content) - assert_in("line-example", content) - - -def test_tab_render_embed(): - bar = _create_bar() - line = _create_line() - content = Tab().add(bar, "bar").add(line, "line").render_embed() - assert_true(len(content) > 8000) - - -def test_tab_render_notebook(): - from pyecharts.globals import CurrentConfig, NotebookType - - CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK - - tab = Tab() - tab.add(_create_line(), "line-example") - tab.add(_create_bar(), "bar-example") - tab.add(_create_table(), "table-example") - html = tab.render_notebook().__html__() - assert_in("City name", html) - - -def test_page_jshost_default(): - bar = _create_bar() - tab = Tab().add(bar, "bar") - assert_equal(tab.js_host, "https://assets.pyecharts.org/assets/v5/") - - -def test_tab_jshost_custom(): - from pyecharts.globals import CurrentConfig - - default_host = CurrentConfig.ONLINE_HOST - custom_host = "http://localhost:8888/assets/" - CurrentConfig.ONLINE_HOST = custom_host - bar = _create_bar() - line = _create_line() - tab = Tab().add(bar, "bar").add(line, "line") - assert_equal(tab.js_host, custom_host) - CurrentConfig.ONLINE_HOST = default_host - - -def test_tab_iterable(): - tab = Tab() - assert_true(isinstance(tab, Iterable)) - - -def test_tab_attr(): - tab = Tab() - assert_true(isinstance(tab.js_functions, OrderedSet)) - assert_true(isinstance(tab._charts, list)) - - -def test_tab_with_chart_container(): - tab = Tab( - tab_css_opts=opts.TabChartGlobalOpts( - is_enable=False, tab_base_css={"overflow": "hidden"} +class TestTabComponent(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_tab_base(self, fake_writer): + bar = _create_bar() + line = _create_line() + tab = Tab().add(bar, "bar-example").add(line, "line-example") + tab.render() + _, content = fake_writer.call_args[0] + self.assertIn("bar-example", content) + self.assertIn("line-example", content) + + def test_tab_render_embed(self): + bar = _create_bar() + line = _create_line() + content = Tab().add(bar, "bar").add(line, "line").render_embed() + self.assertTrue(len(content) > 8000) + + def test_tab_render_notebook(self): + from pyecharts.globals import CurrentConfig, NotebookType + + CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK + + tab = Tab() + tab.add(_create_line(), "line-example") + tab.add(_create_bar(), "bar-example") + tab.add(_create_table(), "table-example") + html = tab.render_notebook().__html__() + self.assertIn("City name", html) + + def test_page_jshost_default(self): + bar = _create_bar() + tab = Tab().add(bar, "bar") + self.assertEqual(tab.js_host, "https://assets.pyecharts.org/assets/v5/") + + def test_tab_jshost_custom(self): + from pyecharts.globals import CurrentConfig + + default_host = CurrentConfig.ONLINE_HOST + custom_host = "http://localhost:8888/assets/" + CurrentConfig.ONLINE_HOST = custom_host + bar = _create_bar() + line = _create_line() + tab = Tab().add(bar, "bar").add(line, "line") + self.assertEqual(tab.js_host, custom_host) + CurrentConfig.ONLINE_HOST = default_host + + def test_tab_iterable(self): + tab = Tab() + self.assertTrue(isinstance(tab, Iterable)) + + def test_tab_attr(self): + tab = Tab() + self.assertTrue(isinstance(tab.js_functions, OrderedSet)) + self.assertTrue(isinstance(tab._charts, list)) + + def test_tab_with_chart_container(self): + tab = Tab( + tab_css_opts=opts.TabChartGlobalOpts( + is_enable=False, tab_base_css={"overflow": "hidden"} + ) ) - ) - assert_true(isinstance(tab._charts, list)) + self.assertTrue(isinstance(tab._charts, list)) diff --git a/test/test_table.py b/test/test_table.py index ec46091c2..2e1dfa5a3 100644 --- a/test/test_table.py +++ b/test/test_table.py @@ -1,7 +1,6 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_in - from pyecharts import options as opts from pyecharts.components import Table from pyecharts.globals import CurrentConfig, NotebookType @@ -16,40 +15,37 @@ def _gen_table() -> Table: return table -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_table_base(fake_writer): - table = _gen_table() - table.render() - _, content = fake_writer.call_args[0] - assert_in("fl-table", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_table_base_v1(fake_writer): - table = _gen_table() - table.set_global_opts(title_opts=opts.ComponentTitleOpts(title="Table Test")) - table.render() - _, content = fake_writer.call_args[0] - assert_in("fl-table", content) - - -def test_table_render_notebook(): - CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK - table = _gen_table() - html = table.render_notebook().__html__() - assert_in("City name", html) - assert_in("Brisbane", html) - - -def test_table_render_jupyter_lab(): - CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_LAB - table = _gen_table() - html = table.render_notebook().__html__() - assert_in("City name", html) - assert_in("Brisbane", html) - - -def test_table_render_embed(): - table = _gen_table() - s = table.render_embed() - assert s is not None +class TestTableComponent(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_table_base(self, fake_writer): + table = _gen_table() + table.render() + _, content = fake_writer.call_args[0] + self.assertIn("fl-table", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_table_base_v1(self, fake_writer): + table = _gen_table() + table.set_global_opts(title_opts=opts.ComponentTitleOpts(title="Table Test")) + table.render() + _, content = fake_writer.call_args[0] + self.assertIn("fl-table", content) + + def test_table_render_notebook(self): + CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK + table = _gen_table() + html = table.render_notebook().__html__() + self.assertIn("City name", html) + self.assertIn("Brisbane", html) + + def test_table_render_jupyter_lab(self): + CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_LAB + table = _gen_table() + html = table.render_notebook().__html__() + self.assertIn("City name", html) + self.assertIn("Brisbane", html) + + def test_table_render_embed(self): + table = _gen_table() + s = table.render_embed() + assert s is not None diff --git a/test/test_themeriver.py b/test/test_themeriver.py index 5275dba12..6d5693dce 100644 --- a/test/test_themeriver.py +++ b/test/test_themeriver.py @@ -1,27 +1,27 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import ThemeRiver -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_themeriver_basefake_writer(fake_writer): - data = [ - ["2015/11/08", 10, "DQ"], - ["2015/11/20", 30, "TY"], - ["2015/11/08", 21, "SS"], - ["2015/11/14", 7, "QG"], - ["2015/11/22", 4, "SY"], - ["2015/11/20", 26, "DD"], - ] - c = ThemeRiver().add( - ["DQ", "TY", "SS", "QG", "SY", "DD"], - data, - singleaxis_opts=opts.SingleAxisOpts(type_="time", pos_bottom="10%"), - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") +class TestThemeRiverChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_themeriver_basefake_writer(self, fake_writer): + data = [ + ["2015/11/08", 10, "DQ"], + ["2015/11/20", 30, "TY"], + ["2015/11/08", 21, "SS"], + ["2015/11/14", 7, "QG"], + ["2015/11/22", 4, "SY"], + ["2015/11/20", 26, "DD"], + ] + c = ThemeRiver().add( + ["DQ", "TY", "SS", "QG", "SY", "DD"], + data, + singleaxis_opts=opts.SingleAxisOpts(type_="time", pos_bottom="10%"), + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_timeline.py b/test/test_timeline.py index f43c82b96..a5cdf0858 100644 --- a/test/test_timeline.py +++ b/test/test_timeline.py @@ -1,7 +1,5 @@ import unittest -from nose.tools import assert_equal - from pyecharts import options as opts from pyecharts.charts import Bar, Timeline from pyecharts.commons.utils import JsCode @@ -22,42 +20,42 @@ def setUp(self): self.tl = Timeline().add(bar0, "year 2015") def test_default_label(self): - assert_equal( + self.assertEqual( None, self.tl.options.get("baseOption").get("timeline").get("label") ) def test_custom_label(self): custom_label_opts = {"custom": "label"} self.tl.add_schema(label_opts=custom_label_opts) - assert_equal( + self.assertEqual( custom_label_opts, self.tl.options.get("baseOption").get("timeline").get("label"), ) def test_timeline_vertical(self): self.tl.add_schema(orient="vertical") - assert_equal( + self.assertEqual( "vertical", self.tl.options.get("baseOption").get("timeline").get("orient") ) def test_timeline_inverse(self): self.tl.add_schema(is_inverse=True) - assert_equal( + self.assertEqual( True, self.tl.options.get("baseOption").get("timeline").get("inverse") ) def test_timeline_width_height(self): width, height = "20", "30" self.tl.add_schema(width=width, height=height) - assert_equal( + self.assertEqual( width, self.tl.options.get("baseOption").get("timeline").get("width") ) - assert_equal( + self.assertEqual( height, self.tl.options.get("baseOption").get("timeline").get("height") ) def test_timeline_visual_map(self): - assert_equal( + self.assertEqual( type(opts.VisualMapOpts()), type(self.tl.options.get("options")[0].get("visualMap")), ) @@ -101,7 +99,7 @@ def test_timeline_graphic(self): ) ] ) - assert_equal( + self.assertEqual( type(opts.GraphicGroup()), type(self.tl.options.get("baseOption").get("timeline").get("graphic")[0]), ) @@ -142,23 +140,22 @@ def test_timeline_graphic_v1(self): ) ] ) - assert_equal( + self.assertEqual( type(opts.GraphicGroup()), type(self.tl.options.get("baseOption").get("timeline").get("graphic")[0]), ) + def test_page_with_multi_axis(self): + tl = Timeline() + for i in range(2015, 2020): + bar = ( + Bar() + .add_xaxis(Faker.choose()) + .add_yaxis("商家A", Faker.values()) + .add_yaxis("商家B", Faker.values()) + ) + tl.add(bar, "{}年".format(i)) -def test_page_with_multi_axis(): - tl = Timeline() - for i in range(2015, 2020): - bar = ( - Bar() - .add_xaxis(Faker.choose()) - .add_yaxis("商家A", Faker.values()) - .add_yaxis("商家B", Faker.values()) - ) - tl.add(bar, "{}年".format(i)) - - for t in tl.options.get("options"): - assert "xAxis" in t - assert "color" in t + for t in tl.options.get("options"): + assert "xAxis" in t + assert "color" in t diff --git a/test/test_tree.py b/test/test_tree.py index 85952e5b3..eb287ec58 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -1,7 +1,6 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import Tree @@ -26,37 +25,36 @@ ] -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_tree_base(fake_writer): - c = Tree().add("", TEST_DATA) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_tree_collapse_interval(fake_writer): - c = Tree().add("", TEST_DATA, collapse_interval=1) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_tree_options(fake_writer): - c = Tree().add( - series_name="tree", - data=TEST_DATA, - orient="BT", - initial_tree_depth=1, - label_opts=opts.LabelOpts(), - leaves_label_opts=opts.LabelOpts(), - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("orient", content) - assert_in("initialTreeDepth", content) - assert_in("label", content) - assert_in("leaves", content) +class TestTreeChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_tree_base(self, fake_writer): + c = Tree().add("", TEST_DATA) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_tree_collapse_interval(self, fake_writer): + c = Tree().add("", TEST_DATA, collapse_interval=1) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_tree_options(self, fake_writer): + c = Tree().add( + series_name="tree", + data=TEST_DATA, + orient="BT", + initial_tree_depth=1, + label_opts=opts.LabelOpts(), + leaves_label_opts=opts.LabelOpts(), + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("orient", content) + self.assertIn("initialTreeDepth", content) + self.assertIn("label", content) + self.assertIn("leaves", content) diff --git a/test/test_treemap.py b/test/test_treemap.py index ce8c3563d..ee597cddc 100644 --- a/test/test_treemap.py +++ b/test/test_treemap.py @@ -1,7 +1,6 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in - from pyecharts import options as opts from pyecharts.charts import TreeMap @@ -26,35 +25,36 @@ ] -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_treemap_base(fake_writer): - c = TreeMap().add("演示数据", example_data) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_treemap_options(fake_writer): - c = TreeMap().add("演示数据", example_data, width="90%", height="100%", roam=False) - c.render() - _, content = fake_writer.call_args[0] - assert_in("width", content) - assert_in("height", content) - assert_in("roam", content) - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_treemap_levels_options(fake_writer): - c = TreeMap().add( - "演示数据", - example_data, - width="90%", - height="100%", - roam=False, - levels=opts.TreeMapLevelsOpts(), - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in("levels", content) +class TestTreeMapChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_treemap_base(self, fake_writer): + c = TreeMap().add("演示数据", example_data) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_treemap_options(self, fake_writer): + c = TreeMap().add( + "演示数据", example_data, width="90%", height="100%", roam=False + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("width", content) + self.assertIn("height", content) + self.assertIn("roam", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_treemap_levels_options(self, fake_writer): + c = TreeMap().add( + "演示数据", + example_data, + width="90%", + height="100%", + roam=False, + levels=opts.TreeMapLevelsOpts(), + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("levels", content) diff --git a/test/test_utils.py b/test/test_utils.py index 8c56ba960..18d935c8e 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,72 +1,73 @@ -from nose.tools import assert_equal +import unittest from pyecharts.commons import utils - - -def test_utils_produce_require_dict(): - cfg = utils.produce_require_dict(utils.OrderedSet("echarts"), "https://example.com") - assert_equal(cfg["config_items"], ["'echarts':'https://example.comecharts.min'"]) - assert_equal(cfg["libraries"], ["'echarts'"]) - - cfg_1 = utils.produce_require_dict( - utils.OrderedSet("https://api.map.baidu.com"), - "https://example.com", - ) - assert_equal( - cfg_1["config_items"], - ["'baidu_map_api25':'https://api.map.baidu.com'"], - ) - assert_equal(cfg_1["libraries"], ["'baidu_map_api25'"]) - - -def test_utils_produce_require_dict_with_extra(): - global EXTRA - EXTRA["https://api.baidu.com"] = { - "https://api.baidu.com/test.min": ["https://api.baidu.com/test.min", "css"] - } - cfg_0 = utils.produce_require_dict( - utils.OrderedSet("https://api.baidu.com/test.min"), - "https://example.com", - ) - assert_equal(cfg_0["libraries"], ["'https://api.baidu.com/test.min'"]) - - -def test_js_code(): - fn = "function() { console.log('test_js_code') }" - js_code = utils.JsCode(fn) - assert_equal(js_code.js_code, "--x_x--0_0--{}--x_x--0_0--".format(fn)) - - -def test_ordered_set(): - s = utils.OrderedSet() - s.add("a", "b", "c") - assert_equal(s.items, ["a", "b", "c"]) - - -def test_utils_remove_key_with_none_value(): - mock_data = [1, 2, 3] - list_res = utils.remove_key_with_none_value(mock_data) - assert list_res == mock_data - - mock_data_none = None - none_res = utils.remove_key_with_none_value(mock_data_none) - assert none_res == mock_data_none - - -def test_utils_remove_key_with_none_value_raise_value_error(): - import numpy as np - import pandas as pd - - mock_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] - mock_numpy_data = np.array(mock_data) - tmp_df = pd.DataFrame({"x": mock_data}) - mock_series_data = tmp_df["x"] - try: - utils.remove_key_with_none_value({"data": mock_numpy_data}) - except ValueError: - pass - - try: - utils.remove_key_with_none_value({"data": mock_series_data}) - except ValueError: - pass +from pyecharts.datasets import EXTRA + + +class TestUtils(unittest.TestCase): + def test_utils_produce_require_dict(self): + cfg = utils.produce_require_dict( + utils.OrderedSet("echarts"), "https://example.com" + ) + self.assertEqual( + cfg["config_items"], ["'echarts':'https://example.comecharts.min'"] + ) + self.assertEqual(cfg["libraries"], ["'echarts'"]) + + cfg_1 = utils.produce_require_dict( + utils.OrderedSet("https://api.map.baidu.com"), + "https://example.com", + ) + self.assertEqual( + cfg_1["config_items"], + ["'baidu_map_api25':'https://api.map.baidu.com'"], + ) + self.assertEqual(cfg_1["libraries"], ["'baidu_map_api25'"]) + + def test_utils_produce_require_dict_with_extra(self): + global EXTRA + EXTRA["https://api.baidu.com"] = { + "https://api.baidu.com/test.min": ["https://api.baidu.com/test.min", "css"] + } + cfg_0 = utils.produce_require_dict( + utils.OrderedSet("https://api.baidu.com/test.min"), + "https://example.com", + ) + self.assertEqual(cfg_0["libraries"], ["'https://api.baidu.com/test.min'"]) + + def test_js_code(self): + fn = "function() { console.log('test_js_code') }" + js_code = utils.JsCode(fn) + self.assertEqual(js_code.js_code, "--x_x--0_0--{}--x_x--0_0--".format(fn)) + + def test_ordered_set(self): + s = utils.OrderedSet() + s.add("a", "b", "c") + self.assertEqual(s.items, ["a", "b", "c"]) + + def test_utils_remove_key_with_none_value(self): + mock_data = [1, 2, 3] + list_res = utils.remove_key_with_none_value(mock_data) + assert list_res == mock_data + + mock_data_none = None + none_res = utils.remove_key_with_none_value(mock_data_none) + assert none_res == mock_data_none + + def test_utils_remove_key_with_none_value_raise_value_error(self): + import numpy as np + import pandas as pd + + mock_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + mock_numpy_data = np.array(mock_data) + tmp_df = pd.DataFrame({"x": mock_data}) + mock_series_data = tmp_df["x"] + try: + utils.remove_key_with_none_value({"data": mock_numpy_data}) + except ValueError: + pass + + try: + utils.remove_key_with_none_value({"data": mock_series_data}) + except ValueError: + pass diff --git a/test/test_wordcloud.py b/test/test_wordcloud.py index 824693dc1..cbb1ba7f8 100644 --- a/test/test_wordcloud.py +++ b/test/test_wordcloud.py @@ -1,7 +1,6 @@ +import unittest from unittest.mock import patch -from nose.tools import assert_equal, assert_in, assert_not_in - from pyecharts.charts import WordCloud from pyecharts.exceptions import WordCloudMaskImageException @@ -17,61 +16,58 @@ ] -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_wordcloud_base(fake_writer): - c = WordCloud().add("", words, word_size_range=[20, 100]) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - +class TestWordcloudChart(unittest.TestCase): + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_wordcloud_base(self, fake_writer): + c = WordCloud().add("", words, word_size_range=[20, 100]) + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_wordcloud_shapes(fake_writer): - c = WordCloud().add("", words, word_size_range=[20, 100], shape="cardioid") - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_wordcloud_shapes(self, fake_writer): + c = WordCloud().add("", words, word_size_range=[20, 100], shape="cardioid") + c.render() + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") + def test_wordcloud_error_url(self): + try: + c = WordCloud().add( + "", words, word_size_range=[20, 100], mask_image="error images_url" + ) + c.render() + except WordCloudMaskImageException as err: + self.assertEqual(type(err), WordCloudMaskImageException) + assert err.__str__() != "" -def test_wordcloud_error_url(): - try: + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_wordcloud_mask_image(self, fake_writer): c = WordCloud().add( - "", words, word_size_range=[20, 100], mask_image="error images_url" + "", + words, + word_size_range=[20, 100], + shape="cardioid", + mask_image="test/fixtures/img.png", ) c.render() - except WordCloudMaskImageException as err: - assert_equal(type(err), WordCloudMaskImageException) - assert err.__str__() != "" - + _, content = fake_writer.call_args[0] + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_wordcloud_mask_image(fake_writer): - c = WordCloud().add( - "", - words, - word_size_range=[20, 100], - shape="cardioid", - mask_image="test/fixtures/img.png", - ) - c.render() - _, content = fake_writer.call_args[0] - assert_equal(c.theme, "white") - assert_equal(c.renderer, "canvas") - - -@patch("pyecharts.render.engine.write_utf8_html_file") -def test_wordcloud_encode_image_to_base64_os_error(fake_writer): - error_path = "A" * 1000 - c = WordCloud().add( - "", - words, - word_size_range=[20, 100], - shape="cardioid", - mask_image=f"{error_path}" - ) - c.render() - _, content = fake_writer.call_args[0] - assert_in(error_path, content) - assert_not_in("data:image/", content) + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_wordcloud_encode_image_to_base64_os_error(self, fake_writer): + error_path = "A" * 1000 + c = WordCloud().add( + "", + words, + word_size_range=[20, 100], + shape="cardioid", + mask_image=f"{error_path}", + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn(error_path, content) + self.assertNotIn("data:image/", content) From c78e224640b3f8d9167f6f3075aaa4b85df816fd Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 7 Jun 2024 18:28:53 +0800 Subject: [PATCH 118/150] update test_page.py --- test/test_page.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_page.py b/test/test_page.py index 18ccdc3b0..e17d6e4bc 100644 --- a/test/test_page.py +++ b/test/test_page.py @@ -136,10 +136,12 @@ def test_page_draggable_layout_same_chart_id(self): self.assertEqual(html1, html2) def test_page_cfg_type(self): - # ValueError with self.assertRaises(FileNotFoundError): page = Page() page.save_resize_html() + with self.assertRaises(ValueError): + page = Page() + page.save_resize_html() def test_page_iterable(self): page = Page() From 0e41cfa2f8417816693d1b181f4b606df4ea0191 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 7 Jun 2024 18:39:06 +0800 Subject: [PATCH 119/150] update python-app.yml --- .github/workflows/python-app.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 190957a7c..88e49a762 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] exclude: # Python < v3.8 does not support Apple Silicon ARM64. - python-version: "3.7" os: macos-latest @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From 05c18d095c756cee1ad9407d6cd9eb01ef07df3f Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 7 Jun 2024 18:44:19 +0800 Subject: [PATCH 120/150] update python-app.yml for 3.13-dev --- .github/workflows/python-app.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 88e49a762..9ec07a4ec 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -7,13 +7,15 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev"] exclude: # Python < v3.8 does not support Apple Silicon ARM64. - python-version: "3.7" os: macos-latest include: # So run those legacy versions on Intel CPUs. - python-version: "3.7" os: macos-13 + - python-version: "3.13-dev" + allowed_failure: true runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 From a46a63d3497a7e45efc89ba83bf2b4126f03cd62 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 7 Jun 2024 18:50:19 +0800 Subject: [PATCH 121/150] update python-app.yml --- .github/workflows/python-app.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 9ec07a4ec..91defae7b 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -5,17 +5,17 @@ on: [push, pull_request] jobs: build: strategy: + # Allows for matrix sub-jobs to fail without canceling the rest + fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] exclude: # Python < v3.8 does not support Apple Silicon ARM64. - python-version: "3.7" os: macos-latest include: # So run those legacy versions on Intel CPUs. - python-version: "3.7" os: macos-13 - - python-version: "3.13-dev" - allowed_failure: true runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 From dd9a9cf9a29ee809cf998814e96672a83e928224 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 14 Jun 2024 14:17:46 +0800 Subject: [PATCH 122/150] update test_page.py and test_snapshot.py --- test/test_page.py | 11 ++++++----- test/test_snapshot.py | 17 +++++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/test/test_page.py b/test/test_page.py index e17d6e4bc..7923158a3 100644 --- a/test/test_page.py +++ b/test/test_page.py @@ -1,4 +1,5 @@ import unittest +from contextlib import ExitStack from typing import Iterable from pyecharts.charts import Bar, Line, Page @@ -136,11 +137,11 @@ def test_page_draggable_layout_same_chart_id(self): self.assertEqual(html1, html2) def test_page_cfg_type(self): - with self.assertRaises(FileNotFoundError): - page = Page() - page.save_resize_html() - with self.assertRaises(ValueError): - page = Page() + page = Page() + + with ExitStack() as stack: + stack.enter_context(self.assertRaises(FileNotFoundError)) + stack.enter_context(self.assertRaises(ValueError)) page.save_resize_html() def test_page_iterable(self): diff --git a/test/test_snapshot.py b/test/test_snapshot.py index e1ad5289f..e766c0801 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -37,7 +37,7 @@ def test_save_as_text(self): os.unlink("test_txt.txt") def test_save_as(self): - with open("test/fixtures/img1.jpg", "rb") as f: + with open("fixtures/img1.jpg", "rb") as f: image_bytes = f.read() save_as(image_data=image_bytes, output_name="test_pdf.pdf", file_type="pdf") os.unlink("test_pdf.pdf") @@ -92,13 +92,14 @@ def test_make_snapshot_text(self, fake_writer): @patch("pyecharts.render.snapshot.save_as_text") def test_make_snapshot_text_v1(self, fake_writer): eng = _gen_faker_engine("fake content1,content2") - make_snapshot( - engine=eng, - file_name=self._gen_bar_chart(), - output_name="make_snapshot.svg", - is_remove_html=True, - ) - _ = fake_writer.call_args[0] + with self.assertRaises(FileNotFoundError): + make_snapshot( + engine=eng, + file_name=self._gen_bar_chart(), + output_name="make_snapshot.svg", + is_remove_html=True, + ) + _ = fake_writer.call_args[0] self.assertEqual("test ok", "test ok") From 189c7fa595dc4cc02cfa21b72c6f9a6d4edb51bf Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 14 Jun 2024 14:19:19 +0800 Subject: [PATCH 123/150] update test_snapshot.py --- test/test_snapshot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_snapshot.py b/test/test_snapshot.py index e766c0801..1e9bf80db 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -37,7 +37,7 @@ def test_save_as_text(self): os.unlink("test_txt.txt") def test_save_as(self): - with open("fixtures/img1.jpg", "rb") as f: + with open("test/fixtures/img1.jpg", "rb") as f: image_bytes = f.read() save_as(image_data=image_bytes, output_name="test_pdf.pdf", file_type="pdf") os.unlink("test_pdf.pdf") From 66171db16c11f435e0c8ca8b67b931fb8380674b Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 14 Jun 2024 14:27:44 +0800 Subject: [PATCH 124/150] update test_snapshot.py --- test/test_snapshot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_snapshot.py b/test/test_snapshot.py index 1e9bf80db..e766c0801 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -37,7 +37,7 @@ def test_save_as_text(self): os.unlink("test_txt.txt") def test_save_as(self): - with open("test/fixtures/img1.jpg", "rb") as f: + with open("fixtures/img1.jpg", "rb") as f: image_bytes = f.read() save_as(image_data=image_bytes, output_name="test_pdf.pdf", file_type="pdf") os.unlink("test_pdf.pdf") From 7b95ab1463c65a5bc87c75d6b9232cdf9c5087c4 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 14 Jun 2024 14:29:08 +0800 Subject: [PATCH 125/150] update test_snapshot.py --- test/test_snapshot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_snapshot.py b/test/test_snapshot.py index e766c0801..642fb2a65 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -37,7 +37,7 @@ def test_save_as_text(self): os.unlink("test_txt.txt") def test_save_as(self): - with open("fixtures/img1.jpg", "rb") as f: + with open("test/fixtures/img1.jpg", "rb") as f: image_bytes = f.read() save_as(image_data=image_bytes, output_name="test_pdf.pdf", file_type="pdf") os.unlink("test_pdf.pdf") @@ -115,5 +115,5 @@ def test_save_as_module_not_found(self, mock_import): # 检查异常消息是否包含期望的提示信息 self.assertTrue( - f"Please install PIL for {file_type} image type" in str(context.exception), + f"Please install PIL for {file_type} image type." in str(context.exception), ) From b9ecac6d6c62f8cf1742cf40e261661bb8305c41 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 14 Jun 2024 14:41:32 +0800 Subject: [PATCH 126/150] update test_snapshot.py --- test/test_snapshot.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/test_snapshot.py b/test/test_snapshot.py index 642fb2a65..9252057f1 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -92,14 +92,13 @@ def test_make_snapshot_text(self, fake_writer): @patch("pyecharts.render.snapshot.save_as_text") def test_make_snapshot_text_v1(self, fake_writer): eng = _gen_faker_engine("fake content1,content2") - with self.assertRaises(FileNotFoundError): - make_snapshot( - engine=eng, - file_name=self._gen_bar_chart(), - output_name="make_snapshot.svg", - is_remove_html=True, - ) - _ = fake_writer.call_args[0] + make_snapshot( + engine=eng, + file_name=self._gen_bar_chart(), + output_name="make_snapshot.svg", + is_remove_html=True, + ) + _ = fake_writer.call_args[0] self.assertEqual("test ok", "test ok") From 550d29e56201567113e9b22f7ae91b033f6928ab Mon Sep 17 00:00:00 2001 From: sunhailin Date: Mon, 17 Jun 2024 15:08:43 +0800 Subject: [PATCH 127/150] update function for add_js_events; update macro --- pyecharts/charts/base.py | 1 + pyecharts/charts/mixins.py | 5 +++++ pyecharts/render/templates/macro | 10 ++++++++++ test/test_base.py | 6 ++++++ test/test_snapshot.py | 10 +++++----- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/pyecharts/charts/base.py b/pyecharts/charts/base.py index a1cc49a2c..8661998bb 100644 --- a/pyecharts/charts/base.py +++ b/pyecharts/charts/base.py @@ -52,6 +52,7 @@ def __init__( 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)) diff --git a/pyecharts/charts/mixins.py b/pyecharts/charts/mixins.py index 10bf45129..a45b59d89 100644 --- a/pyecharts/charts/mixins.py +++ b/pyecharts/charts/mixins.py @@ -7,6 +7,11 @@ def add_js_funcs(self, *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) diff --git a/pyecharts/render/templates/macro b/pyecharts/render/templates/macro index e2ae45c8c..b2326a786 100644 --- a/pyecharts/render/templates/macro +++ b/pyecharts/render/templates/macro @@ -55,6 +55,11 @@ chart_{{ c.chart_id }}.resize(); }) {% endif %} + {% if c.js_events %} + {% for fn in c.js_events.items %} + {{ fn }} + {% endfor %} + {% endif %} {%- endmacro %} @@ -74,6 +79,11 @@ var bmap = chart_{{ c.chart_id }}.getModel().getComponent('bmap').getBMap(); bmap.addControl(new BMap.MapTypeControl()); {% endif %} + {% if c.js_events %} + {% for fn in c.js_events.items %} + {{ fn }} + {% endfor %} + {% endif %} {% endif %} {% endfor %} }); diff --git a/test/test_base.py b/test/test_base.py index c2a5c7a3a..f760db6ce 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -17,6 +17,12 @@ def test_base_add_functions(self): self.assertEqual(1, len(c.js_functions.items)) self.assertEqual(["console.log('hello')"], c.js_functions.items) + def test_base_add_events(self): + c = Base() + c.add_js_events("console.log('hello')", "console.log('hello')") + self.assertEqual(1, len(c.js_events.items)) + self.assertEqual(["console.log('hello')"], c.js_events.items) + def test_base_init_funcs(self): c0 = Base({"width": "100px", "height": "200px"}) self.assertEqual(c0.width, "100px") diff --git a/test/test_snapshot.py b/test/test_snapshot.py index 9252057f1..09ff8539d 100644 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -111,8 +111,8 @@ def test_save_as_module_not_found(self, mock_import): with self.assertRaises(Exception) as context: save_as(image_data, output_name, file_type) - - # 检查异常消息是否包含期望的提示信息 - self.assertTrue( - f"Please install PIL for {file_type} image type." in str(context.exception), - ) + # 检查异常消息是否包含期望的提示信息 + self.assertTrue( + f"Please install PIL for {file_type} image type." in + str(context.exception), + ) From 0bb75c8e6fd35e57dc385813c63f31d07cd48e9d Mon Sep 17 00:00:00 2001 From: Han-Teng Liao Date: Mon, 17 Jun 2024 19:56:14 +0800 Subject: [PATCH 128/150] merge two commits: one on series_options.py and another on test_series_options.py merge two commits: one on series_options.py and another on test_series_options.py --- pyecharts/options/series_options.py | 12 ++++++++++++ test/test_series_options.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/pyecharts/options/series_options.py b/pyecharts/options/series_options.py index a0e6105a0..5fbea160b 100644 --- a/pyecharts/options/series_options.py +++ b/pyecharts/options/series_options.py @@ -110,6 +110,12 @@ def __init__( 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, ): @@ -135,6 +141,12 @@ def __init__( "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, } diff --git a/test/test_series_options.py b/test/test_series_options.py index f7f71b63f..f9b53cc37 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -50,6 +50,12 @@ def test_label_options_custom(self): border_color="green", border_width=1, border_radius=2, + text_border_color="black", + text_border_width=3, + text_shadow_color="whitesmoke", + text_shadow_blur=.1, + text_shadow_offset_x=.2, + text_shadow_offset_y=.3, ) expected = { "show": True, @@ -73,6 +79,12 @@ def test_label_options_custom(self): "padding": None, "width": None, "height": None, + "textBorderColor": "black", + "textBorderWidth": 3, + "textShadowColor": "whitesmoke", + "textShadowBlur": .1, + "textShadowOffsetX": .2, + "textShadowOffsetY": .3, "overflow": None, "rich": None, } From fb3b5b5f8c4f19e3571cfbeeee77ae060eb8f7a2 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Tue, 18 Jun 2024 16:25:29 +0800 Subject: [PATCH 129/150] update boxplot, custom, effectscatter, funnel, kline, pie, radar charts configuration. --- pyecharts/charts/basic_charts/boxplot.py | 22 +++++++++- pyecharts/charts/basic_charts/custom.py | 2 +- .../charts/basic_charts/effectscatter.py | 29 +++++++++++- pyecharts/charts/basic_charts/funnel.py | 44 +++++++++++++++++++ pyecharts/charts/basic_charts/kline.py | 24 ++++++++++ pyecharts/charts/basic_charts/pie.py | 8 +++- pyecharts/charts/basic_charts/radar.py | 8 ++++ 7 files changed, 133 insertions(+), 4 deletions(-) diff --git a/pyecharts/charts/basic_charts/boxplot.py b/pyecharts/charts/basic_charts/boxplot.py index ff09e95f7..df92f5a80 100644 --- a/pyecharts/charts/basic_charts/boxplot.py +++ b/pyecharts/charts/basic_charts/boxplot.py @@ -24,14 +24,24 @@ def add_yaxis( 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, + encode: types.Union[types.JSFunc, dict, None] = None, ): if box_width is None: box_width = [7, 50] @@ -44,15 +54,25 @@ def add_yaxis( "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, - "selected_mode": selected_mode, + "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, + "encode": encode, } ) return self diff --git a/pyecharts/charts/basic_charts/custom.py b/pyecharts/charts/basic_charts/custom.py index 5a10f0345..d23fa7d72 100644 --- a/pyecharts/charts/basic_charts/custom.py +++ b/pyecharts/charts/basic_charts/custom.py @@ -19,7 +19,7 @@ def add( *, color_by: str = "series", is_legend_hover_link: bool = True, - coordinate_system: types.JSFunc = "cartesian2d", + coordinate_system: str = "cartesian2d", x_axis_index: types.Numeric = 0, y_axis_index: types.Numeric = 0, polar_index: types.Numeric = 0, diff --git a/pyecharts/charts/basic_charts/effectscatter.py b/pyecharts/charts/basic_charts/effectscatter.py index 90266242b..3ce1015ad 100644 --- a/pyecharts/charts/basic_charts/effectscatter.py +++ b/pyecharts/charts/basic_charts/effectscatter.py @@ -18,15 +18,29 @@ def add_yaxis( *, 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) @@ -43,16 +57,29 @@ def add_yaxis( "name": series_name, "xAxisIndex": xaxis_index, "yAxisIndex": yaxis_index, - "showEffectOn": "render", + "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, } ) diff --git a/pyecharts/charts/basic_charts/funnel.py b/pyecharts/charts/basic_charts/funnel.py index 31659eab2..ad82cc6ab 100644 --- a/pyecharts/charts/basic_charts/funnel.py +++ b/pyecharts/charts/basic_charts/funnel.py @@ -21,12 +21,34 @@ def add( 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]): @@ -46,12 +68,34 @@ def add( "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 diff --git a/pyecharts/charts/basic_charts/kline.py b/pyecharts/charts/basic_charts/kline.py index 76dcbf1b9..0750f3e4e 100644 --- a/pyecharts/charts/basic_charts/kline.py +++ b/pyecharts/charts/basic_charts/kline.py @@ -30,33 +30,57 @@ def add_yaxis( 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 diff --git a/pyecharts/charts/basic_charts/pie.py b/pyecharts/charts/basic_charts/pie.py index 5b60033a4..d36354b0e 100644 --- a/pyecharts/charts/basic_charts/pie.py +++ b/pyecharts/charts/basic_charts/pie.py @@ -25,7 +25,7 @@ def add( selected_offset: types.Numeric = 10, radius: types.Optional[types.Sequence] = None, center: types.Optional[types.Sequence] = None, - rosetype: types.Optional[str] = None, + rosetype: types.Union[str, bool] = None, is_clockwise: bool = True, start_angle: types.Numeric = 90, min_angle: types.Numeric = 0, @@ -41,6 +41,9 @@ def add( 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 @@ -93,6 +96,9 @@ def add( "itemStyle": itemstyle_opts, "emphasis": emphasis_opts, "encode": encode, + "markpoint": markpoint_opts, + "markline": markline_opts, + "markarea": markarea_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/radar.py b/pyecharts/charts/basic_charts/radar.py index 1386993b3..a671387db 100644 --- a/pyecharts/charts/basic_charts/radar.py +++ b/pyecharts/charts/basic_charts/radar.py @@ -67,10 +67,14 @@ def add( 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, @@ -86,9 +90,13 @@ def add( "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, From 88164717911014541814888a75be89dad3feeca7 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Tue, 18 Jun 2024 18:18:39 +0800 Subject: [PATCH 130/150] update bar, boxplot, funnel, heatmap, parallel, pie, tree charts configuration and add selectOpts, TreeLeavesOpts --- pyecharts/charts/basic_charts/bar.py | 2 ++ pyecharts/charts/basic_charts/boxplot.py | 2 ++ pyecharts/charts/basic_charts/funnel.py | 6 ++-- pyecharts/charts/basic_charts/heatmap.py | 26 ++++++++++++++++ pyecharts/charts/basic_charts/parallel.py | 14 +++++++++ pyecharts/charts/basic_charts/pie.py | 6 ++-- pyecharts/charts/basic_charts/tree.py | 26 ++++++++++++---- pyecharts/options/__init__.py | 2 ++ pyecharts/options/global_options.py | 36 +++++++++++++++++++++++ pyecharts/types.py | 3 ++ test/test_global_options.py | 6 ++++ 11 files changed, 117 insertions(+), 12 deletions(-) diff --git a/pyecharts/charts/basic_charts/bar.py b/pyecharts/charts/basic_charts/bar.py index 0a658eac9..8ceb44b40 100644 --- a/pyecharts/charts/basic_charts/bar.py +++ b/pyecharts/charts/basic_charts/bar.py @@ -56,6 +56,7 @@ def add_yaxis( 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) @@ -107,6 +108,7 @@ def add_yaxis( "itemStyle": itemstyle_opts, "emphasis": emphasis_opts, "blur": blur_opts, + "select": select_opts, "encode": encode, } ) diff --git a/pyecharts/charts/basic_charts/boxplot.py b/pyecharts/charts/basic_charts/boxplot.py index df92f5a80..f199ed74b 100644 --- a/pyecharts/charts/basic_charts/boxplot.py +++ b/pyecharts/charts/basic_charts/boxplot.py @@ -41,6 +41,7 @@ def add_yaxis( 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: @@ -72,6 +73,7 @@ def add_yaxis( "itemStyle": itemstyle_opts, "emphasis": emphasis_opts, "blur": blur_opts, + "select": select_opts, "encode": encode, } ) diff --git a/pyecharts/charts/basic_charts/funnel.py b/pyecharts/charts/basic_charts/funnel.py index ad82cc6ab..7dbf81cb5 100644 --- a/pyecharts/charts/basic_charts/funnel.py +++ b/pyecharts/charts/basic_charts/funnel.py @@ -93,9 +93,9 @@ def add( "height": height, "datasetIndex": dataset_index, "encode": encode, - "markpoint": markpoint_opts, - "markline": markline_opts, - "markarea": markarea_opts, + "markPoint": markpoint_opts, + "markLine": markline_opts, + "markArea": markarea_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/heatmap.py b/pyecharts/charts/basic_charts/heatmap.py index e91b1e86c..ec68c19ea 100644 --- a/pyecharts/charts/basic_charts/heatmap.py +++ b/pyecharts/charts/basic_charts/heatmap.py @@ -27,14 +27,27 @@ def add_yaxis( 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) @@ -42,15 +55,28 @@ def add_yaxis( { "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 diff --git a/pyecharts/charts/basic_charts/parallel.py b/pyecharts/charts/basic_charts/parallel.py index 2130700d0..8450c8a44 100644 --- a/pyecharts/charts/basic_charts/parallel.py +++ b/pyecharts/charts/basic_charts/parallel.py @@ -43,7 +43,14 @@ def add( 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, @@ -54,6 +61,13 @@ def add( { "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, diff --git a/pyecharts/charts/basic_charts/pie.py b/pyecharts/charts/basic_charts/pie.py index d36354b0e..f39f73caf 100644 --- a/pyecharts/charts/basic_charts/pie.py +++ b/pyecharts/charts/basic_charts/pie.py @@ -96,9 +96,9 @@ def add( "itemStyle": itemstyle_opts, "emphasis": emphasis_opts, "encode": encode, - "markpoint": markpoint_opts, - "markline": markline_opts, - "markarea": markarea_opts, + "markPoint": markpoint_opts, + "markLine": markline_opts, + "markArea": markarea_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/tree.py b/pyecharts/charts/basic_charts/tree.py index 01c314bde..787546632 100644 --- a/pyecharts/charts/basic_charts/tree.py +++ b/pyecharts/charts/basic_charts/tree.py @@ -37,14 +37,18 @@ def add( 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.Optional[str] = None, - pos_left: types.Optional[str] = None, - pos_bottom: types.Optional[str] = None, - pos_right: types.Optional[str] = None, + 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%", @@ -52,10 +56,13 @@ def add( is_expand_and_collapse: bool = True, initial_tree_depth: types.Optional[types.Numeric] = None, label_opts: types.Label = opts.LabelOpts(), - leaves_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( @@ -67,6 +74,10 @@ def add( "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, @@ -77,10 +88,13 @@ def add( "layout": layout, "orient": orient, "label": label_opts, - "leaves": {"label": leaves_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 diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index deb62a14d..464b73303 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -97,6 +97,7 @@ RadarIndicatorItem, RadiusAxisItem, RadiusAxisOpts, + SelectOpts, SingleAxisOpts, TitleOpts, ToolBoxFeatureBrushOpts, @@ -108,6 +109,7 @@ ToolBoxFeatureSaveAsImageOpts, ToolboxOpts, TooltipOpts, + TreeLeavesOpts, VisualMapOpts, ) from .series_options import ( diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index d4ab6ced6..6afac297e 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -1540,15 +1540,51 @@ 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, + } diff --git a/pyecharts/types.py b/pyecharts/types.py index aaeb2be36..a78ad8510 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -68,6 +68,7 @@ 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] @@ -102,6 +103,8 @@ _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] diff --git a/test/test_global_options.py b/test/test_global_options.py index fc730221b..3eb01ddc1 100644 --- a/test/test_global_options.py +++ b/test/test_global_options.py @@ -16,6 +16,7 @@ ParallelAxisOpts, RadiusAxisItem, RadiusAxisOpts, + SelectOpts, ToolBoxFeatureBrushOpts, ToolBoxFeatureDataViewOpts, ToolBoxFeatureDataZoomOpts, @@ -366,3 +367,8 @@ def test_blur_options_remove_none(self): option = BlurOpts() expected = {"labelLine": {"show": False}} self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_select_opts_remove_none(self): + option = SelectOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) From 98b45b7aa72ccc1fc82f684d555b5b9debe2ec58 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Tue, 18 Jun 2024 18:33:29 +0800 Subject: [PATCH 131/150] update README and _version.py --- README.en.md | 4 ++-- README.md | 6 +++--- pyecharts/_version.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.en.md b/README.en.md index 43665e517..de367f8b1 100644 --- a/README.en.md +++ b/README.en.md @@ -57,13 +57,13 @@ At the discretion of the development team, version 0.5.x will no longer be maint ### V1 -> Python 3.6+ only +> Python 3.7+ only The new version series will start with v1.0.0, documented at [pyecharts.org](https://pyecharts.org); examples at [gallery.pyecharts.org](https://gallery.pyecharts.org) ### V2 -> Python 3.6+ only +> Python 3.7+ only The new version is based on Echarts 5.4.1+ for rendering, and the documentation and examples are in the same location as V1. diff --git a/README.md b/README.md index 523d10a5e..2ac6184bc 100644 --- a/README.md +++ b/README.md @@ -53,19 +53,19 @@ v0.5.x 和 V1 间不兼容,V1 是一个全新的版本,详见 [ISSUE#892](ht ### V0.5.x -> 支持 Python2.7,3.4+ +> 支持 Python 2.7,3.4+ 经开发团队决定,0.5.x 版本将不再进行维护,0.5.x 版本代码位于 *05x* 分支,文档位于 [05x-docs.pyecharts.org](http://05x-docs.pyecharts.org)。 ### V1 -> 仅支持 Python3.6+ +> 仅支持 Python 3.7+ 新版本系列将从 v1.0.0 开始,文档位于 [pyecharts.org](https://pyecharts.org);示例位于 [gallery.pyecharts.org](https://gallery.pyecharts.org) ### V2 -> 仅支持 Python3.6+ +> 仅支持 Python 3.7+ 新版本基于 Echarts 5.4.1+ 进行渲染, 文档和示例位置与 V1 相同 diff --git a/pyecharts/_version.py b/pyecharts/_version.py index 205f89717..ed6b4bfdd 100644 --- a/pyecharts/_version.py +++ b/pyecharts/_version.py @@ -1,2 +1,2 @@ -__version__ = "2.0.5" +__version__ = "2.0.6" __author__ = "chenjiandongx" From 229b903e77826e6c06dc2e2e7b5620a3285e2dc4 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Tue, 18 Jun 2024 18:59:10 +0800 Subject: [PATCH 132/150] update timeline for 3d charts --- pyecharts/charts/composite_charts/timeline.py | 4 +++ test/test_timeline.py | 34 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/pyecharts/charts/composite_charts/timeline.py b/pyecharts/charts/composite_charts/timeline.py index e641da80b..d0ef9332d 100644 --- a/pyecharts/charts/composite_charts/timeline.py +++ b/pyecharts/charts/composite_charts/timeline.py @@ -107,6 +107,10 @@ def add(self, chart: Base, time_point: str): "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) diff --git a/test/test_timeline.py b/test/test_timeline.py index a5cdf0858..ebc561ad1 100644 --- a/test/test_timeline.py +++ b/test/test_timeline.py @@ -1,11 +1,32 @@ +import random import unittest from pyecharts import options as opts -from pyecharts.charts import Bar, Timeline +from pyecharts.charts import Bar, Bar3D, Timeline from pyecharts.commons.utils import JsCode from pyecharts.faker import Faker +def get_bar_3d_chart(i: int): + data = [(i, j, random.randint(0, 12)) for i in range(6) for j in range(24)] + c = ( + Bar3D() + .add( + "", + [[d[1], d[0], d[2]] for d in data], + xaxis3d_opts=opts.Axis3DOpts(Faker.clock, type_="category"), + yaxis3d_opts=opts.Axis3DOpts(Faker.week_en, type_="category"), + zaxis3d_opts=opts.Axis3DOpts(type_="value"), + ) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(max_=20), + title_opts=opts.TitleOpts(title=f"Bar3D-{i}"), + ) + ) + + return c + + class TestTimeLine(unittest.TestCase): def setUp(self): bar0 = ( @@ -159,3 +180,14 @@ def test_page_with_multi_axis(self): for t in tl.options.get("options"): assert "xAxis" in t assert "color" in t + + def test_timeline_with_3d_chart(self): + tl = Timeline() + for i in range(2, 5): + bar_3d = get_bar_3d_chart(i=i) + tl.add(bar_3d, "{}".format(i)) + + for t in tl.options.get("options"): + assert "xAxis3D" in t + assert "yAxis3D" in t + assert "zAxis3D" in t \ No newline at end of file From 8051e2459bdfe2f0251551439bbcb33400893ceb Mon Sep 17 00:00:00 2001 From: sunhailin Date: Tue, 18 Jun 2024 19:06:17 +0800 Subject: [PATCH 133/150] update test_timeline.py and test_tree.py --- test/test_timeline.py | 2 +- test/test_tree.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_timeline.py b/test/test_timeline.py index ebc561ad1..4ce98349a 100644 --- a/test/test_timeline.py +++ b/test/test_timeline.py @@ -190,4 +190,4 @@ def test_timeline_with_3d_chart(self): for t in tl.options.get("options"): assert "xAxis3D" in t assert "yAxis3D" in t - assert "zAxis3D" in t \ No newline at end of file + assert "zAxis3D" in t diff --git a/test/test_tree.py b/test/test_tree.py index eb287ec58..97f66867b 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -50,7 +50,7 @@ def test_tree_options(self, fake_writer): orient="BT", initial_tree_depth=1, label_opts=opts.LabelOpts(), - leaves_label_opts=opts.LabelOpts(), + leaves_opts=opts.TreeLeavesOpts(), ) c.render() _, content = fake_writer.call_args[0] From aa65e13f86397011a7938d8134709eb8bbe43b83 Mon Sep 17 00:00:00 2001 From: Han-Teng Liao Date: Wed, 19 Jun 2024 16:25:01 +0800 Subject: [PATCH 134/150] Update test_series_options.py In response to the request by @sunhailin-Leo https://github.com/pyecharts/pyecharts/pull/2345#issuecomment-2174721155 --- test/test_series_options.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_series_options.py b/test/test_series_options.py index f9b53cc37..f58fdd0fd 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -39,6 +39,12 @@ def test_label_options_defaults(self): "padding": None, "width": None, "height": None, + "textBorderColor": None, + "textBorderWidth": None, + "textShadowColor": None, + "textShadowBlur": None, + "textShadowOffsetX": None, + "textShadowOffsetY": None, "overflow": None, "rich": None, } From 4eafa58ec552643f33d594d2d3a1395bd200f498 Mon Sep 17 00:00:00 2001 From: LeoSun <379978424@qq.com> Date: Wed, 19 Jun 2024 17:16:36 +0800 Subject: [PATCH 135/150] Update test_series_options.py remove unnecessary space --- test/test_series_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_series_options.py b/test/test_series_options.py index f58fdd0fd..7dbfe0dd3 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -90,7 +90,7 @@ def test_label_options_custom(self): "textShadowColor": "whitesmoke", "textShadowBlur": .1, "textShadowOffsetX": .2, - "textShadowOffsetY": .3, + "textShadowOffsetY": .3, "overflow": None, "rich": None, } From a07f46a1e1a79069c6b27c7a7c2b1b2ceaa7a345 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Thu, 20 Jun 2024 18:23:09 +0800 Subject: [PATCH 136/150] update test_datasets.py --- test/test_datasets.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_datasets.py b/test/test_datasets.py index ca65070cd..3bd5a7780 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -72,3 +72,19 @@ def test_register_files(self): def test_register_coords(self): register_coords(coords={"深圳": [113, 23]}) + + def test_type_error_with_non_string_key(self): + fd = FuzzyDict() + fd[1] = "one" + fd[2] = "two" + + result = fd._search("1") + self.assertFalse(result[0]) # Ensure no match found + + def test_type_error_with_non_string_lookfor(self): + fd = FuzzyDict() + fd["one"] = 1 + fd["two"] = 2 + + with self.assertRaises(KeyError): + _ = fd[1] From 94784adeef551efe36cc336752f98416274e9122 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Wed, 10 Jul 2024 10:15:58 +0800 Subject: [PATCH 137/150] update AnimationOpts, MarkPointOpts, MarkLineOpts, MarkAreaOpts --- pyecharts/charts/base.py | 3 +-- pyecharts/options/global_options.py | 25 +------------------- pyecharts/options/series_options.py | 36 +++++++++++++++++++++++++++++ test/test_base.py | 2 +- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/pyecharts/charts/base.py b/pyecharts/charts/base.py index 8661998bb..06545651e 100644 --- a/pyecharts/charts/base.py +++ b/pyecharts/charts/base.py @@ -7,8 +7,7 @@ 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 ..options.series_options import BasicOpts, AnimationOpts from ..render import engine from ..types import Optional, Sequence, Union from .mixins import ChartMixin diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 6afac297e..8b96cacff 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -1,6 +1,7 @@ from ..globals import CurrentConfig, RenderType, ThemeType from ..options.series_options import ( BasicOpts, + AnimationOpts, AreaStyleOpts, ItemStyleOpts, JSFunc, @@ -18,30 +19,6 @@ ) -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, diff --git a/pyecharts/options/series_options.py b/pyecharts/options/series_options.py index 5fbea160b..e1a7df52a 100644 --- a/pyecharts/options/series_options.py +++ b/pyecharts/options/series_options.py @@ -16,6 +16,30 @@ def get(self, key: str) -> Any: return self.opts.get(key) +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 ItemStyleOpts(BasicOpts): def __init__( self, @@ -216,6 +240,7 @@ def __init__( symbol: Optional[str] = None, symbol_size: Union[None, Numeric] = None, label_opts: LabelOpts = LabelOpts(position="inside", color="#fff"), + animation_opts: Union[AnimationOpts, dict, None] = None, ): self.opts: dict = { "symbol": symbol, @@ -224,6 +249,9 @@ def __init__( "data": data, } + if animation_opts: + self.opts.update(**animation_opts.opts) + class MarkLineItem(BasicOpts): def __init__( @@ -267,6 +295,7 @@ def __init__( precision: int = 2, label_opts: LabelOpts = LabelOpts(), linestyle_opts: Union[LineStyleOpts, dict, None] = None, + animation_opts: Union[AnimationOpts, dict, None] = None, ): self.opts: dict = { "silent": is_silent, @@ -278,6 +307,9 @@ def __init__( "data": data, } + if animation_opts: + self.opts.update(**animation_opts.opts) + class MarkAreaItem(BasicOpts): def __init__( @@ -319,6 +351,7 @@ def __init__( label_opts: LabelOpts = LabelOpts(), data: Sequence[Union[MarkAreaItem, Sequence, dict]] = None, itemstyle_opts: ItemStyleOpts = None, + animation_opts: Union[AnimationOpts, dict, None] = None, ): self.opts: dict = { "silent": is_silent, @@ -327,6 +360,9 @@ def __init__( "itemStyle": itemstyle_opts, } + if animation_opts: + self.opts.update(**animation_opts.opts) + class EffectOpts(BasicOpts): def __init__( diff --git a/test/test_base.py b/test/test_base.py index f760db6ce..3e6763c7a 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -6,7 +6,7 @@ from pyecharts.options import InitOpts, RenderOpts from pyecharts.globals import CurrentConfig from pyecharts.charts.base import Base, default -from pyecharts.options.global_options import AnimationOpts +from pyecharts.options.series_options import AnimationOpts class TestBaseClass(unittest.TestCase): From ad6d44d716e9ceb5bd49de17abcd3315764bd522 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Thu, 26 Sep 2024 17:55:27 +0800 Subject: [PATCH 138/150] fix timeline bug --- pyecharts/charts/composite_charts/timeline.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pyecharts/charts/composite_charts/timeline.py b/pyecharts/charts/composite_charts/timeline.py index d0ef9332d..0434122c4 100644 --- a/pyecharts/charts/composite_charts/timeline.py +++ b/pyecharts/charts/composite_charts/timeline.py @@ -94,6 +94,7 @@ def add(self, chart: Base, time_point: str): self.options.get("options").append( { "backgroundColor": chart.options.get("backgroundColor"), + "legend": chart.options.get("legend"), "series": chart.options.get("series"), "xAxis": chart.options.get("xAxis"), "yAxis": chart.options.get("yAxis"), @@ -136,9 +137,4 @@ def __check_components(self, chart: Base): 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}) + self.options.get("baseOption").update({component: c}) From 777e94a1febe1e2973fdffee80436c6547d5763d Mon Sep 17 00:00:00 2001 From: Myles Scolnick Date: Mon, 14 Oct 2024 21:17:09 -0400 Subject: [PATCH 139/150] docs: add marimo support --- README.en.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.en.md b/README.en.md index de367f8b1..74e09b3b2 100644 --- a/README.en.md +++ b/README.en.md @@ -39,7 +39,7 @@ * Simple API, Sleek and method chaining * Support 30 + popular charts -* Suppot data science tools: Jupyter Notebook, JupyterLab, nteract +* Support data science tools: Jupyter Notebook, JupyterLab, nteract, [marimo](https://github.com/marimo-team/marimo) * Integrate with Flask,Django at ease * Easy to use and highly configurable * Detailed documentation and examples. diff --git a/README.md b/README.md index 2ac6184bc..a7ac21974 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ * 简洁的 API 设计,使用如丝滑般流畅,支持链式调用 * 囊括了 30+ 种常见图表,应有尽有 -* 支持主流 Notebook 环境,Jupyter Notebook 和 JupyterLab +* 支持主流 Notebook 环境,Jupyter Notebook、JupyterLab 和 [marimo](https://github.com/marimo-team/marimo) * 可轻松集成至 Flask,Sanic,Django 等主流 Web 框架 * 高度灵活的配置项,可轻松搭配出精美的图表 * 详细的文档和示例,帮助开发者更快的上手项目 From ab168aba374c643a792a0f477e196de47a0d51ce Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 18 Oct 2024 15:33:12 +0800 Subject: [PATCH 140/150] Page component support is_embed_js param. --- pyecharts/charts/composite_charts/page.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyecharts/charts/composite_charts/page.py b/pyecharts/charts/composite_charts/page.py index ad470af6b..65e18a354 100644 --- a/pyecharts/charts/composite_charts/page.py +++ b/pyecharts/charts/composite_charts/page.py @@ -55,6 +55,7 @@ def __init__( js_host: str = "", interval: int = 1, is_remove_br: bool = False, + is_embed_js: bool = False, page_border_color: str = "", layout: types.Union[PageLayoutOpts, dict] = PageLayoutOpts(), ): @@ -69,6 +70,9 @@ def __init__( self.download_button: bool = False self._charts: list = [] + self.render_options: dict = {"embed_js": is_embed_js} + self._render_cache: dict = dict() + def add(self, *charts): for c in charts: self._charts.append(c) @@ -128,6 +132,12 @@ def _prepare_render(self): self.css_libs = [self.js_host + link for link in ("jquery-ui.css",)] self.layout = "" + self._render_cache.clear() + if self.render_options.get("embed_js"): + self._render_cache[ + "javascript" + ] = self.load_javascript().load_javascript_contents() + def render( self, path: str = "render.html", From f37241a76803e165b669fe89ad669b80d134a34b Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 18 Oct 2024 15:46:21 +0800 Subject: [PATCH 141/150] update test code --- test/test_page.py | 10 +++++++++- test/test_series_options.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/test/test_page.py b/test/test_page.py index 7923158a3..e916603b6 100644 --- a/test/test_page.py +++ b/test/test_page.py @@ -4,7 +4,7 @@ from pyecharts.charts import Bar, Line, Page from pyecharts.commons.utils import OrderedSet -from pyecharts.globals import ThemeType +from pyecharts.globals import ThemeType, CurrentConfig from pyecharts.components import Table from pyecharts import options as opts from pyecharts.faker import Faker @@ -173,3 +173,11 @@ def test_page_no_cfg_dict_or_file(self): with self.assertRaises(ValueError): page = Page() page.save_resize_html() + + def test_page_with_is_embed_js(self): + page = Page(layout=Page.SimplePageLayout, is_embed_js=True) + # Embedded JavaScript + content = page.render_embed() + self.assertNotIn( + CurrentConfig.ONLINE_HOST, content, "Embedding JavaScript fails" + ) diff --git a/test/test_series_options.py b/test/test_series_options.py index 7dbfe0dd3..88c77bfb2 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -2,12 +2,15 @@ from pyecharts.commons.utils import remove_key_with_none_value from pyecharts.options.series_options import ( + AnimationOpts, LabelOpts, ItemStyleOpts, MarkPointItem, MarkLineItem, + MarkLineOpts, MarkAreaItem, MarkAreaOpts, + MarkPointOpts, MinorTickOpts, MinorSplitLineOpts, TreeMapBreadcrumbOpts, @@ -174,3 +177,15 @@ def test_minor_split_line_options_remove_none(self): def test_area_color_in_item_styles(self): op = ItemStyleOpts(area_color="red") self.assertEqual(op.opts["areaColor"], "red") + + def test_mark_point_opts_with_animation(self): + op = MarkPointOpts(animation_opts=AnimationOpts(animation=True)) + self.assertEqual(op.opts["animation"], True) + + def test_mark_line_opts_with_animation(self): + op = MarkLineOpts(animation_opts=AnimationOpts(animation=True)) + self.assertEqual(op.opts["animation"], True) + + def test_mark_area_opts_with_animation(self): + op = MarkAreaOpts(animation_opts=AnimationOpts(animation=True)) + self.assertEqual(op.opts["animation"], True) From 86369543f6482bf2e0fb7d3b448dd703b2ab8170 Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 18 Oct 2024 15:52:29 +0800 Subject: [PATCH 142/150] update test code and workflow --- .github/workflows/python-app.yml | 2 +- test/test_engine.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 91defae7b..921f8da0a 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] exclude: # Python < v3.8 does not support Apple Silicon ARM64. - python-version: "3.7" os: macos-latest diff --git a/test/test_engine.py b/test/test_engine.py index 579c7411e..5dca14a9a 100644 --- a/test/test_engine.py +++ b/test/test_engine.py @@ -55,3 +55,16 @@ def test_import_iterable_old_location(self): # importlib.reload(collections.abc) # 重新加载模块以应用补丁 assert collections.abc.Iterable.__module__ == "collections.abc" + + def test_iterable_import(self): + # 尝试从 collections.abc 导入 Iterable + try: + from collections.abc import Iterable + self.assertTrue(True) # 如果成功导入,测试通过 + except ImportError: + # 如果从 collections.abc 导入失败,则尝试从 collections 导入 + try: + from collections import Iterable + self.assertTrue(True) # 如果这里成功导入,测试通过 + except ImportError: + self.fail("Failed to import Iterable from both collections.abc and collections") # 如果两者都失败,则测试不通过 From 910fdb8f83e7363691808a5c03874b28dd08262a Mon Sep 17 00:00:00 2001 From: sunhailin Date: Fri, 18 Oct 2024 15:55:31 +0800 Subject: [PATCH 143/150] update test_engine.py --- test/test_engine.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_engine.py b/test/test_engine.py index 5dca14a9a..de8d937df 100644 --- a/test/test_engine.py +++ b/test/test_engine.py @@ -67,4 +67,5 @@ def test_iterable_import(self): from collections import Iterable self.assertTrue(True) # 如果这里成功导入,测试通过 except ImportError: - self.fail("Failed to import Iterable from both collections.abc and collections") # 如果两者都失败,则测试不通过 + # 如果两者都失败,则测试不通过 + self.fail("Failed to import Iterable") From 14e079e5de489bef3fd1e868fadb2385cc86f439 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Sat, 19 Oct 2024 18:45:00 +0800 Subject: [PATCH 144/150] add animation_opts in AxisOpts; fix AnimationOpts wrong type-hints; add is_value_animation(valueAnimation) in LabelOpts; update test code --- pyecharts/options/global_options.py | 4 ++++ pyecharts/options/series_options.py | 4 +++- test/test_map.py | 2 +- test/test_series_options.py | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 8b96cacff..02782b47d 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -862,6 +862,7 @@ def __init__( 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, + animation_opts: Union[AnimationOpts, dict] = AnimationOpts(), ): self.opts: dict = { "type": type_, @@ -893,6 +894,9 @@ def __init__( "minorSplitLine": minor_split_line_opts, } + if animation_opts: + self.opts.update(**animation_opts.opts) + class GridOpts(BasicOpts): def __init__( diff --git a/pyecharts/options/series_options.py b/pyecharts/options/series_options.py index e1a7df52a..5a8cd8961 100644 --- a/pyecharts/options/series_options.py +++ b/pyecharts/options/series_options.py @@ -25,7 +25,7 @@ def __init__( 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_easing_update: Union[str] = "cubicOut", animation_delay_update: Union[Numeric, JSFunc] = 0, ): self.opts: dict = { @@ -142,6 +142,7 @@ def __init__( text_shadow_offset_y: Optional[Numeric] = None, overflow: Optional[str] = None, rich: Optional[dict] = None, + is_value_animation: bool = False, ): self.opts: dict = { "show": is_show, @@ -173,6 +174,7 @@ def __init__( "textShadowOffsetY": text_shadow_offset_y, "overflow": overflow, "rich": rich, + "valueAnimation": is_value_animation, } diff --git a/test/test_map.py b/test/test_map.py index 22e2f3636..2c89716a4 100644 --- a/test/test_map.py +++ b/test/test_map.py @@ -46,7 +46,7 @@ def test_map_emphasis(self): ) options = json.loads(c.dump_options()) expected = { - "label": {"show": False, "margin": 8}, + "label": {"show": False, "margin": 8, "valueAnimation": False}, "itemStyle": {"borderColor": "white", "areaColor": "red"}, } self.assertEqual(expected, options["series"][0]["emphasis"]) diff --git a/test/test_series_options.py b/test/test_series_options.py index 88c77bfb2..836a32fcc 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -50,6 +50,7 @@ def test_label_options_defaults(self): "textShadowOffsetY": None, "overflow": None, "rich": None, + "valueAnimation": False, } self.assertEqual(expected, option.opts) @@ -96,6 +97,7 @@ def test_label_options_custom(self): "textShadowOffsetY": .3, "overflow": None, "rich": None, + "valueAnimation": False, } self.assertEqual(expected, option.opts) From d50cbb7ccb01972bb7cd40789f64c3983715dc57 Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Sat, 2 Nov 2024 10:12:50 +0800 Subject: [PATCH 145/150] add AMap support like BMap (Baidu Map) --- pyecharts/charts/__init__.py | 1 + pyecharts/charts/basic_charts/amap.py | 91 +++++++++++++++++++++++++++ pyecharts/commons/utils.py | 3 + pyecharts/datasets/map_filename.json | 1 + pyecharts/render/engine.py | 2 + pyecharts/render/templates/macro | 48 +++++++++++--- test/test_amap.py | 89 ++++++++++++++++++++++++++ test/test_exception.py | 16 ++++- test/test_utils.py | 10 +++ 9 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 pyecharts/charts/basic_charts/amap.py create mode 100644 test/test_amap.py diff --git a/pyecharts/charts/__init__.py b/pyecharts/charts/__init__.py index 14f682295..394bc23a2 100644 --- a/pyecharts/charts/__init__.py +++ b/pyecharts/charts/__init__.py @@ -1,4 +1,5 @@ # basic Charts +from ..charts.basic_charts.amap import AMap from ..charts.basic_charts.bar import Bar from ..charts.basic_charts.bmap import BMap from ..charts.basic_charts.boxplot import Boxplot diff --git a/pyecharts/charts/basic_charts/amap.py b/pyecharts/charts/basic_charts/amap.py new file mode 100644 index 000000000..e51efdcbc --- /dev/null +++ b/pyecharts/charts/basic_charts/amap.py @@ -0,0 +1,91 @@ +from ... import options as opts +from ... import types +from ...charts.basic_charts.geo import GeoChartBase +from ...commons.utils import OrderedSet, JsCode +from ...exceptions import NonexistentCoordinatesException +from ...globals import ChartType + +AMAP_API = "https://webapi.amap.com/maps?v=2.0&key={}&plugin=AMap.Scale,AMap.ToolBar" + + +class AMap(GeoChartBase): + """ + <<< AMap(GaoDe) 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("amap") + self._is_geo_chart = True + self._coordinate_system: types.Optional[str] = "amap" + self.amap_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, + amap_ak: str, + center: types.Sequence, + zoom: types.Union[types.Numeric, str] = None, + is_enable_resize: bool = True, + map_style: types.Optional[dict] = None, + is_render_on_map: bool = True, + is_layer_interactive: bool = True, + is_large: bool = False, + ): + self.js_dependencies.add(AMAP_API.format(amap_ak)) + self.options.update( + amap={ + "center": center, + "viewMode": "3D", # default + "zoom": zoom, + "resizeEnable": is_enable_resize, + "mapStyle": map_style, + "renderOnMoving": is_render_on_map, + "echartsLayerInteractive": is_layer_interactive, + "largeMode": is_large, + } + ) + if is_layer_interactive: + self.amap_js_functions.add( + "amapComponent.setEChartsLayerInteractive(true);" + ) + + return self + + def add_control_panel( + self, + is_add_satellite_layer: bool = False, + is_add_road_net_layer: bool = False, + ): + if is_add_satellite_layer: + self.amap_js_functions.add( + "amap.add(new AMap.TileLayer.Satellite());", + ) + if is_add_road_net_layer: + self.amap_js_functions.add( + "amap.add(new AMap.TileLayer.RoadNet());", + ) + + return self diff --git a/pyecharts/commons/utils.py b/pyecharts/commons/utils.py index 2af574005..fa4e4951e 100644 --- a/pyecharts/commons/utils.py +++ b/pyecharts/commons/utils.py @@ -32,6 +32,9 @@ def produce_require_dict(js_dependencies, js_host) -> dict: 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.startswith("https://webapi.amap.com"): + confs.append("'amap_map_api{}':'{}'".format(len(name), name)) + libraries.append("'amap_map_api{}'".format(len(name))) if name in FILENAMES: f, _ = FILENAMES[name] confs.append("'{}':'{}{}'".format(name, js_host, f)) diff --git a/pyecharts/datasets/map_filename.json b/pyecharts/datasets/map_filename.json index e6b23443e..43e77656b 100644 --- a/pyecharts/datasets/map_filename.json +++ b/pyecharts/datasets/map_filename.json @@ -1,4 +1,5 @@ { + "amap": ["echarts-extension-amap.min", "js"], "bulma": ["bulma.min", "css"], "jquery": ["jquery.min", "js"], "jquery-ui": ["jquery-ui.min", "js"], diff --git a/pyecharts/render/engine.py b/pyecharts/render/engine.py index 63b834972..582f0dc3e 100644 --- a/pyecharts/render/engine.py +++ b/pyecharts/render/engine.py @@ -35,6 +35,8 @@ def generate_js_link(chart: Any) -> Any: # TODO: if? if dep.startswith("https://api.map.baidu.com"): links.append(dep) + if dep.startswith("https://webapi.amap.com"): + links.append(dep) if dep in FILENAMES: f, ext = FILENAMES[dep] links.append("{}{}.{}".format(chart.js_host, f, ext)) diff --git a/pyecharts/render/templates/macro b/pyecharts/render/templates/macro index b2326a786..e873a95a5 100644 --- a/pyecharts/render/templates/macro +++ b/pyecharts/render/templates/macro @@ -43,11 +43,26 @@ var option_{{ c.chart_id }} = {{ c.json_contents }}; chart_{{ c.chart_id }}.setOption(option_{{ c.chart_id }}); {% if c._is_geo_chart %} - var bmap = chart_{{ c.chart_id }}.getModel().getComponent('bmap').getBMap(); - {% if c.bmap_js_functions %} - {% for fn in c.bmap_js_functions.items %} - {{ fn }} - {% endfor %} + {% if c._coordinate_system == 'bmap' %} + var bmap = chart_{{ c.chart_id }}.getModel().getComponent('bmap').getBMap(); + {% if c.bmap_js_functions %} + {% for fn in c.bmap_js_functions.items %} + {{ fn }} + {% endfor %} + {% endif %} + {% elif c._coordinate_system == 'amap' %} + // Get AMap extension component + var amapComponent = chart_{{ c.chart_id }}.getModel().getComponent('amap'); + // Get the instance of AMap + var amap = amapComponent.getAMap(); + // Add some controls provided by AMap. + amap.addControl(new AMap.Scale()); + amap.addControl(new AMap.ToolBar()); + {% if c.amap_js_functions %} + {% for fn in c.amap_js_functions.items %} + {{ fn }} + {% endfor %} + {% endif %} {% endif %} {% endif %} {% if c.width.endswith('%') %} @@ -76,8 +91,27 @@ var option_{{ c.chart_id }} = {{ c.json_contents }}; chart_{{ c.chart_id }}.setOption(option_{{ c.chart_id }}); {% if c._is_geo_chart %} - var bmap = chart_{{ c.chart_id }}.getModel().getComponent('bmap').getBMap(); - bmap.addControl(new BMap.MapTypeControl()); + {% if c._coordinate_system == 'bmap' %} + var bmap = chart_{{ c.chart_id }}.getModel().getComponent('bmap').getBMap(); + {% if c.bmap_js_functions %} + {% for fn in c.bmap_js_functions.items %} + {{ fn }} + {% endfor %} + {% endif %} + {% elif c._coordinate_system == 'amap' %} + // Get AMap extension component + var amapComponent = chart_{{ c.chart_id }}.getModel().getComponent('amap'); + // Get the instance of AMap + var amap = amapComponent.getAMap(); + // Add some controls provided by AMap. + amap.addControl(new AMap.Scale()); + amap.addControl(new AMap.ToolBar()); + {% if c.amap_js_functions %} + {% for fn in c.amap_js_functions.items %} + {{ fn }} + {% endfor %} + {% endif %} + {% endif %} {% endif %} {% if c.js_events %} {% for fn in c.js_events.items %} diff --git a/test/test_amap.py b/test/test_amap.py new file mode 100644 index 000000000..4636738c4 --- /dev/null +++ b/test/test_amap.py @@ -0,0 +1,89 @@ +import unittest +from unittest.mock import patch + +from pyecharts import options as opts +from pyecharts.charts import AMap +from pyecharts.globals import ChartType + +FAKE_API_KEY = "fake_application_key" +AMAP_API_PREFIX = "https://webapi.amap.com/maps?v=2.0" + +TEST_LOCATION = ["London"] +TEST_VALUE = [1] + + +class TestAMapChart(unittest.TestCase): + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_amap(self, fake_writer): + amap = ( + AMap() + .add_schema(amap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "amap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + amap.render() + content = fake_writer.call_args[0][1] + self.assertIn(f'src="{AMAP_API_PREFIX}&key={FAKE_API_KEY}', content) + self.assertIn('"coordinateSystem": "amap"', content, "non amap found") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_amap_map_control_panel(self, fake_writer): + amap = ( + AMap() + .add_schema(amap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "amap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + .add_control_panel( + is_add_satellite_layer=True, + is_add_road_net_layer=True, + ) + ) + amap.render() + content = fake_writer.call_args[0][1] + self.assertIn("new AMap.TileLayer.Satellite", content) + self.assertIn("new AMap.TileLayer.RoadNet", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_amap_polyline_and_large(self, fake_writer): + amap = ( + AMap() + .add_schema(amap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "amap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + is_polyline=True, + is_large=True, + type_=ChartType.LINES, + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + amap.render() + content = fake_writer.call_args[0][1] + self.assertIn('"polyline": true', content, "polyline parameter is error") + self.assertIn('"large": true', content, "large parameter is error") + + def test_amap_heatmap(self): + amap = ( + AMap() + .add_schema(amap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "amap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + data = amap.options.get("series")[0]["data"] + for item in data: + self.assertIn("name", item) + self.assertIn("value", item) diff --git a/test/test_exception.py b/test/test_exception.py index 664f863f3..ec54d132d 100644 --- a/test/test_exception.py +++ b/test/test_exception.py @@ -1,7 +1,7 @@ import unittest from pyecharts import options as opts -from pyecharts.charts import Geo, BMap +from pyecharts.charts import Geo, AMap, BMap from pyecharts.exceptions import NonexistentCoordinatesException BAIDU_MAP_API_PREFIX = "https://api.map.baidu.com/api?v=2.0" @@ -46,6 +46,20 @@ def test_bmap_catch_nonexistent_coord_exception(self): except NonexistentCoordinatesException as err: self.assertEqual(type(err), NonexistentCoordinatesException) + def test_amap_catch_nonexistent_coord_exception(self): + try: + ( + AMap() + .add_schema(amap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add( + "amap", + [["NonexistentLocation", 123]], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + except NonexistentCoordinatesException as err: + self.assertEqual(type(err), NonexistentCoordinatesException) + def test_bmap_ignore_nonexistent_coord_exception(self): ( BMap(is_ignore_nonexistent_coord=True) diff --git a/test/test_utils.py b/test/test_utils.py index 18d935c8e..503b88067 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -24,6 +24,16 @@ def test_utils_produce_require_dict(self): ) self.assertEqual(cfg_1["libraries"], ["'baidu_map_api25'"]) + cfg_2 = utils.produce_require_dict( + utils.OrderedSet("https://webapi.amap.com"), + "https://example.com", + ) + self.assertEqual( + cfg_2["config_items"], + ["'amap_map_api23':'https://webapi.amap.com'"], + ) + self.assertEqual(cfg_2["libraries"], ["'amap_map_api23'"]) + def test_utils_produce_require_dict_with_extra(self): global EXTRA EXTRA["https://api.baidu.com"] = { From bde68fd845681ba41419b46e8d221304fdaa539d Mon Sep 17 00:00:00 2001 From: sunhailinLeo <379978424@qq.com> Date: Sun, 3 Nov 2024 14:33:08 +0800 Subject: [PATCH 146/150] update version and setup --- pyecharts/_version.py | 2 +- setup.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyecharts/_version.py b/pyecharts/_version.py index ed6b4bfdd..a5607247c 100644 --- a/pyecharts/_version.py +++ b/pyecharts/_version.py @@ -1,2 +1,2 @@ -__version__ = "2.0.6" +__version__ = "2.0.7" __author__ = "chenjiandongx" diff --git a/setup.py b/setup.py index 176a7ef30..0fc4cebba 100644 --- a/setup.py +++ b/setup.py @@ -95,6 +95,8 @@ def run(self): "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries", ], cmdclass={"upload": UploadCommand}, From 0fbbcf94700277efdac865c64a90ea9323bce960 Mon Sep 17 00:00:00 2001 From: LeoSun <379978424@qq.com> Date: Fri, 24 Jan 2025 10:54:19 +0800 Subject: [PATCH 147/150] Prepare for version 2.0.8 (#2393) version 2.0.8 --- .github/workflows/python-app.yml | 4 + pyecharts/charts/__init__.py | 2 + pyecharts/charts/base.py | 4 + pyecharts/charts/basic_charts/calendar.py | 7 +- pyecharts/charts/basic_charts/gmap.py | 77 ++++++++++++++++++ pyecharts/charts/basic_charts/line.py | 2 + pyecharts/charts/basic_charts/lmap.py | 66 +++++++++++++++ pyecharts/datasets/map_filename.json | 5 ++ pyecharts/render/engine.py | 16 +++- pyecharts/render/templates/macro | 38 ++++++++- pyecharts/render/templates/simple_chart.html | 1 + test/test_base.py | 4 + test/test_calendar.py | 61 ++++++++++++++ test/test_engine.py | 11 ++- test/test_exception.py | 30 ++++++- test/test_gmap.py | 85 ++++++++++++++++++++ test/test_lmap.py | 64 +++++++++++++++ 17 files changed, 470 insertions(+), 7 deletions(-) create mode 100644 pyecharts/charts/basic_charts/gmap.py create mode 100644 pyecharts/charts/basic_charts/lmap.py create mode 100644 test/test_gmap.py create mode 100644 test/test_lmap.py diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 921f8da0a..72f57d4b6 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -13,9 +13,13 @@ jobs: exclude: # Python < v3.8 does not support Apple Silicon ARM64. - python-version: "3.7" os: macos-latest + - python-version: "3.7" + os: ubuntu-latest include: # So run those legacy versions on Intel CPUs. - python-version: "3.7" os: macos-13 + - python-version: "3.7" + os: ubuntu-22.04 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/pyecharts/charts/__init__.py b/pyecharts/charts/__init__.py index 394bc23a2..36426a85d 100644 --- a/pyecharts/charts/__init__.py +++ b/pyecharts/charts/__init__.py @@ -9,11 +9,13 @@ 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.gmap import GMap 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.lmap import LMap from ..charts.basic_charts.map import Map from ..charts.basic_charts.parallel import Parallel from ..charts.basic_charts.pictorialbar import PictorialBar diff --git a/pyecharts/charts/base.py b/pyecharts/charts/base.py index 06545651e..b19faab48 100644 --- a/pyecharts/charts/base.py +++ b/pyecharts/charts/base.py @@ -66,6 +66,10 @@ def __init__( self.render_options.update(embed_js=bool(_render_opts.get("embed_js"))) self._render_cache: dict = dict() + def use_echarts_stat(self): + self.js_dependencies.add("echarts-stat") + return self + def get_chart_id(self) -> str: return self.chart_id diff --git a/pyecharts/charts/basic_charts/calendar.py b/pyecharts/charts/basic_charts/calendar.py index 86f131194..ed565b87c 100644 --- a/pyecharts/charts/basic_charts/calendar.py +++ b/pyecharts/charts/basic_charts/calendar.py @@ -19,7 +19,6 @@ def __init__( 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, @@ -37,7 +36,11 @@ def add( **other_calendar_opts, ): if calendar_opts: - self.options.update(calendar=calendar_opts) + if self.options.get("calendar"): + self.options.get("calendar").append(calendar_opts) + else: + self.options.update(calendar=[calendar_opts]) + if visualmap_opts: self.options.update(visualMap=visualmap_opts) diff --git a/pyecharts/charts/basic_charts/gmap.py b/pyecharts/charts/basic_charts/gmap.py new file mode 100644 index 000000000..1f42de106 --- /dev/null +++ b/pyecharts/charts/basic_charts/gmap.py @@ -0,0 +1,77 @@ +from ... import options as opts +from ... import types +from ...charts.basic_charts.geo import GeoChartBase +from ...commons.utils import OrderedSet, JsCode +from ...exceptions import NonexistentCoordinatesException +from ...globals import ChartType + +GMAP_API = "https://maps.googleapis.com/maps/api/js?key={}" + + +class GMap(GeoChartBase): + """ + <<< GMap(Google Map) 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("gmap") + self._is_geo_chart = True + self._coordinate_system: types.Optional[str] = "gmap" + self.gmap_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, + gmap_ak: str, + center: types.Sequence, + zoom: types.Union[types.Numeric, str] = None, + is_render_on_map: bool = True, + z_index: types.Optional[int] = None, + is_roam: bool = True, + ): + self.js_dependencies.add(GMAP_API.format(gmap_ak)) + self.options.update( + gmap={ + "center": center, + "zoom": zoom, + "renderOnMoving": is_render_on_map, + "echartsLayerZIndex": z_index, + "roam": is_roam + } + ) + return self + + def add_control_panel( + self, + is_add_traffic_layer: bool = False, + ): + if is_add_traffic_layer: + self.gmap_js_functions.add( + "var trafficLayer = new google.maps.TrafficLayer(); " + "trafficLayer.setMap(gmap);" + ) + + return self diff --git a/pyecharts/charts/basic_charts/line.py b/pyecharts/charts/basic_charts/line.py index 13c45aa00..7f23acfb1 100644 --- a/pyecharts/charts/basic_charts/line.py +++ b/pyecharts/charts/basic_charts/line.py @@ -21,6 +21,7 @@ def add_yaxis( xaxis_index: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, polar_index: types.Optional[types.Numeric] = None, + dataset_index: types.Optional[types.Numeric] = None, coordinate_system: types.Optional[str] = None, color_by: types.Optional[str] = None, color: types.Optional[str] = None, @@ -78,6 +79,7 @@ def add_yaxis( "xAxisIndex": xaxis_index, "yAxisIndex": yaxis_index, "polarIndex": polar_index, + "datasetIndex": dataset_index, "coordinateSystem": coordinate_system, "colorBy": color_by, "symbol": symbol, diff --git a/pyecharts/charts/basic_charts/lmap.py b/pyecharts/charts/basic_charts/lmap.py new file mode 100644 index 000000000..68a4062e3 --- /dev/null +++ b/pyecharts/charts/basic_charts/lmap.py @@ -0,0 +1,66 @@ +from ... import options as opts +from ... import types +from ...charts.basic_charts.geo import GeoChartBase +from ...commons.utils import OrderedSet, JsCode +from ...exceptions import NonexistentCoordinatesException +from ...globals import ChartType + + +class LMap(GeoChartBase): + """ + <<< LMap(leaflet) 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("lmap-css") + self.js_dependencies.add("lmap-src") + self.js_dependencies.add("lmap") + self._is_geo_chart = True + self._coordinate_system: types.Optional[str] = "lmap" + self.lmap_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, + center: types.Sequence, + zoom: types.Union[types.Numeric, str] = None, + is_enable_resize: bool = True, + is_render_on_map: bool = True, + is_layer_interactive: bool = True, + is_large: bool = False, + ): + self.options.update( + lmap={ + "center": center, + "zoom": zoom, + "resizeEnable": is_enable_resize, + "renderOnMoving": is_render_on_map, + "echartsLayerInteractive": is_layer_interactive, + "largeMode": is_large, + } + ) + + return self diff --git a/pyecharts/datasets/map_filename.json b/pyecharts/datasets/map_filename.json index 43e77656b..0237c8882 100644 --- a/pyecharts/datasets/map_filename.json +++ b/pyecharts/datasets/map_filename.json @@ -1,5 +1,9 @@ { "amap": ["echarts-extension-amap.min", "js"], + "lmap": ["echarts-extension-leaflet.min", "js"], + "lmap-src": ["leaflet/leaflet", "js"], + "lmap-css": ["leaflet/leaflet", "css"], + "gmap": ["echarts-extension-gmap.min", "js"], "bulma": ["bulma.min", "css"], "jquery": ["jquery.min", "js"], "jquery-ui": ["jquery-ui.min", "js"], @@ -8,6 +12,7 @@ "echarts-gl": ["echarts-gl.min", "js"], "echarts-liquidfill": ["echarts-liquidfill.min", "js"], "echarts-wordcloud": ["echarts-wordcloud.min", "js"], + "echarts-stat": ["ecStat.min", "js"], "bmap": ["bmap.min", "js"], "chalk": ["themes/chalk", "js"], "essos": ["themes/essos", "js"], diff --git a/pyecharts/render/engine.py b/pyecharts/render/engine.py index 582f0dc3e..271596e1c 100644 --- a/pyecharts/render/engine.py +++ b/pyecharts/render/engine.py @@ -31,22 +31,34 @@ def generate_js_link(chart: Any) -> Any: if not chart.js_host: chart.js_host = CurrentConfig.ONLINE_HOST links = [] + css_links = [] for dep in chart.js_dependencies.items: # TODO: if? if dep.startswith("https://api.map.baidu.com"): links.append(dep) if dep.startswith("https://webapi.amap.com"): links.append(dep) + if dep.startswith("https://maps.googleapis.com"): + links.append(dep) if dep in FILENAMES: f, ext = FILENAMES[dep] - links.append("{}{}.{}".format(chart.js_host, f, ext)) + _link = "{}{}.{}".format(chart.js_host, f, ext) + if ext == "css": + css_links.append(_link) + else: + links.append(_link) else: for url, files in EXTRA.items(): if dep in files: f, ext = files[dep] - links.append("{}{}.{}".format(url, f, ext)) + _link = "{}{}.{}".format(url, f, ext) + if ext == "css": + css_links.append(_link) + else: + links.append(_link) break chart.dependencies = links + chart.css_libs = css_links return chart def render_chart_to_file(self, template_name: str, chart: Any, path: str, **kwargs): diff --git a/pyecharts/render/templates/macro b/pyecharts/render/templates/macro index e873a95a5..0f8325650 100644 --- a/pyecharts/render/templates/macro +++ b/pyecharts/render/templates/macro @@ -63,6 +63,24 @@ {{ fn }} {% endfor %} {% endif %} + {% elif c._coordinate_system == 'lmap' %} + var lmapComponent = chart_{{ c.chart_id }}.getModel().getComponent('lmap'); + var lmap = lmapComponent.getLeaflet(); + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(lmap); + {% elif c._coordinate_system == 'gmap' %} + // Get the instance of Google Map + var gmap = chart_{{ c.chart_id }}.getModel().getComponent('gmap').getGoogleMap(); + // Add some markers to map + var marker = new google.maps.Marker({ position: gmap.getCenter() }); + marker.setMap(gmap); + // Add TrafficLayer to map + {% if c.gmap_js_functions %} + {% for fn in c.gmap_js_functions.items %} + {{ fn }} + {% endfor %} + {% endif %} {% endif %} {% endif %} {% if c.width.endswith('%') %} @@ -111,6 +129,24 @@ {{ fn }} {% endfor %} {% endif %} + {% elif c._coordinate_system == 'lmap' %} + var lmapComponent = chart_{{ c.chart_id }}.getModel().getComponent('lmap'); + var lmap = lmapComponent.getLeaflet(); + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(lmap); + {% elif c._coordinate_system == 'gmap' %} + // Get the instance of Google Map + var gmap = chart_{{ c.chart_id }}.getModel().getComponent('gmap').getGoogleMap(); + // Add some markers to map + var marker = new google.maps.Marker({ position: gmap.getCenter() }); + marker.setMap(gmap); + // Add TrafficLayer to map + {% if c.gmap_js_functions %} + {% for fn in c.gmap_js_functions.items %} + {{ fn }} + {% endfor %} + {% endif %} {% endif %} {% endif %} {% if c.js_events %} @@ -141,7 +177,7 @@ {%- macro render_chart_css(c) -%} {% for dep in c.css_libs %} - + {% endfor %} {%- endmacro %} diff --git a/pyecharts/render/templates/simple_chart.html b/pyecharts/render/templates/simple_chart.html index 75585090c..268516a75 100644 --- a/pyecharts/render/templates/simple_chart.html +++ b/pyecharts/render/templates/simple_chart.html @@ -5,6 +5,7 @@ {{ chart.page_title }} {{ macro.render_chart_dependencies(chart) }} + {{ macro.render_chart_css(chart) }} {{ macro.render_chart_content(chart) }} diff --git a/test/test_base.py b/test/test_base.py index 3e6763c7a..1dafc6885 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -90,3 +90,7 @@ def test_base_chart_id(self): c1 = Base(init_opts=InitOpts(chart_id="1234567")) self.assertEqual(c1.get_chart_id(), "1234567") + + def test_use_echarts_stat(self): + c0 = Base().use_echarts_stat() + self.assertEqual(c0.js_dependencies.items, ["echarts", "echarts-stat"]) diff --git a/test/test_calendar.py b/test/test_calendar.py index a8064f51e..402e3c918 100644 --- a/test/test_calendar.py +++ b/test/test_calendar.py @@ -71,3 +71,64 @@ def test_calendar_setting(self, fake_writer): self.assertIn("dayLabel", content) self.assertIn("monthLabel", content) self.assertIn("visualMap", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_multi_calendar(self, fake_writer): + + def get_calendar_data(year: int): + begin = datetime.date(year, 1, 1) + end = datetime.date(year, 12, 31) + return [ + [str(begin + datetime.timedelta(days=i)), random.randint(1000, 25000)] + for i in range((end - begin).days + 1) + ] + + c = ( + Calendar(init_opts=opts.InitOpts(width="1300px", height="600px")) + .add( + series_name="2015", + yaxis_data=get_calendar_data(year=2015), + calendar_opts=opts.CalendarOpts( + range_="2015", + cell_size=["auto", 20], + pos_left="100", + ), + calendar_index=0, + ) + .add( + series_name="2016", + yaxis_data=get_calendar_data(year=2016), + calendar_opts=opts.CalendarOpts( + range_="2016", + pos_top="260", + cell_size=["auto", 20], + pos_left="100", + ), + calendar_index=1, + ) + .add( + series_name="2017", + yaxis_data=get_calendar_data(year=2017), + calendar_opts=opts.CalendarOpts( + range_="2017", + pos_top="450", + cell_size=["auto", 20], + pos_left="100", + ), + calendar_index=2, + ) + .set_global_opts( + title_opts=opts.TitleOpts(title="2015~2017 年微信步数情况"), + visualmap_opts=opts.VisualMapOpts( + max_=20000, + min_=500, + orient="vertical", + is_calculable=True, + ), + ) + ) + + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("cellSize", content) + self.assertIn("visualMap", content) diff --git a/test/test_engine.py b/test/test_engine.py index de8d937df..027950d82 100644 --- a/test/test_engine.py +++ b/test/test_engine.py @@ -15,7 +15,10 @@ def test_generate_js_link(self): { "https://extra_host.com": { "dep": ("extra_file_path", "extra_ext"), - } + }, + "https://extra_host.com/css": { + "dep1": ("extra_file_path", "css"), + }, } ) @@ -26,6 +29,7 @@ def test_generate_js_link(self): "https://api.map.baidu.com/test", "existing_dep", "dep", + "dep1", ] RenderEngine.generate_js_link(chart) @@ -39,6 +43,11 @@ def test_generate_js_link(self): ] assert chart.dependencies == expected_links + expected_css_links = [ + "https://extra_host.com/cssextra_file_path.css", + ] + assert chart.css_libs == expected_css_links + def test_import_iterable_new_location(self): # 在collections.abc不可用的情况下,尝试从collections导入 with patch("collections.abc", side_effect=ImportError): diff --git a/test/test_exception.py b/test/test_exception.py index ec54d132d..c8ef5ca74 100644 --- a/test/test_exception.py +++ b/test/test_exception.py @@ -1,7 +1,7 @@ import unittest from pyecharts import options as opts -from pyecharts.charts import Geo, AMap, BMap +from pyecharts.charts import Geo, AMap, BMap, LMap, GMap from pyecharts.exceptions import NonexistentCoordinatesException BAIDU_MAP_API_PREFIX = "https://api.map.baidu.com/api?v=2.0" @@ -60,6 +60,34 @@ def test_amap_catch_nonexistent_coord_exception(self): except NonexistentCoordinatesException as err: self.assertEqual(type(err), NonexistentCoordinatesException) + def test_lmap_catch_nonexistent_coord_exception(self): + try: + ( + LMap() + .add_schema(center=[-0.118092, 51.509865]) + .add( + "lmap", + [["NonexistentLocation", 123]], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + except NonexistentCoordinatesException as err: + self.assertEqual(type(err), NonexistentCoordinatesException) + + def test_gmap_catch_nonexistent_coord_exception(self): + try: + ( + GMap() + .add_schema(gmap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add( + "gmap", + [["NonexistentLocation", 123]], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + except NonexistentCoordinatesException as err: + self.assertEqual(type(err), NonexistentCoordinatesException) + def test_bmap_ignore_nonexistent_coord_exception(self): ( BMap(is_ignore_nonexistent_coord=True) diff --git a/test/test_gmap.py b/test/test_gmap.py new file mode 100644 index 000000000..7644ed18e --- /dev/null +++ b/test/test_gmap.py @@ -0,0 +1,85 @@ +import unittest +from unittest.mock import patch + +from pyecharts import options as opts +from pyecharts.charts import GMap +from pyecharts.globals import ChartType + +FAKE_API_KEY = "fake_application_key" +GMAP_API_PREFIX = "https://maps.googleapis.com/maps/api/js" + +TEST_LOCATION = ["London"] +TEST_VALUE = [1] + + +class TestGMapChart(unittest.TestCase): + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_gmap(self, fake_writer): + gmap = ( + GMap() + .add_schema(gmap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "gmap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + gmap.render() + content = fake_writer.call_args[0][1] + self.assertIn(f'src="{GMAP_API_PREFIX}?key={FAKE_API_KEY}', content) + self.assertIn('"coordinateSystem": "gmap"', content, "non gmap found") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_gmap_map_control_panel(self, fake_writer): + gmap = ( + GMap() + .add_schema(gmap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "amap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + .add_control_panel(is_add_traffic_layer=True) + ) + gmap.render() + content = fake_writer.call_args[0][1] + self.assertIn("new google.maps.TrafficLayer", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_gmap_polyline_and_large(self, fake_writer): + gmap = ( + GMap() + .add_schema(gmap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "amap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + is_polyline=True, + is_large=True, + type_=ChartType.LINES, + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + gmap.render() + content = fake_writer.call_args[0][1] + self.assertIn('"polyline": true', content, "polyline parameter is error") + self.assertIn('"large": true', content, "large parameter is error") + + def test_gmap_heatmap(self): + gmap = ( + GMap() + .add_schema(gmap_ak=FAKE_API_KEY, center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "amap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + data = gmap.options.get("series")[0]["data"] + for item in data: + self.assertIn("name", item) + self.assertIn("value", item) diff --git a/test/test_lmap.py b/test/test_lmap.py new file mode 100644 index 000000000..e7adbf537 --- /dev/null +++ b/test/test_lmap.py @@ -0,0 +1,64 @@ +import unittest +from unittest.mock import patch + +from pyecharts import options as opts +from pyecharts.charts import LMap +from pyecharts.globals import ChartType + +TEST_LOCATION = ["London"] +TEST_VALUE = [1] + + +class TestLMapChart(unittest.TestCase): + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_amap(self, fake_writer): + lmap = ( + LMap() + .add_schema(center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "lmap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + lmap.render() + content = fake_writer.call_args[0][1] + self.assertIn('"coordinateSystem": "lmap"', content, "non lmap found") + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_amap_polyline_and_large(self, fake_writer): + lmap = ( + LMap() + .add_schema(center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "lmap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + is_polyline=True, + is_large=True, + type_=ChartType.LINES, + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + lmap.render() + content = fake_writer.call_args[0][1] + self.assertIn('"polyline": true', content, "polyline parameter is error") + self.assertIn('"large": true', content, "large parameter is error") + + def test_amap_heatmap(self): + lmap = ( + LMap() + .add_schema(center=[-0.118092, 51.509865]) + .add_coordinate("London", -0.118092, 51.509865) + .add( + "lmap", + [list(z) for z in zip(TEST_LOCATION, TEST_VALUE)], + label_opts=opts.LabelOpts(formatter="{b}"), + ) + ) + data = lmap.options.get("series")[0]["data"] + for item in data: + self.assertIn("name", item) + self.assertIn("value", item) From 5228492426e7f7bd6d701af8ae1b130d5c14145c Mon Sep 17 00:00:00 2001 From: LeoSun <379978424@qq.com> Date: Sun, 26 Jan 2025 15:01:34 +0800 Subject: [PATCH 148/150] update _version.py --- pyecharts/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyecharts/_version.py b/pyecharts/_version.py index a5607247c..a57042679 100644 --- a/pyecharts/_version.py +++ b/pyecharts/_version.py @@ -1,2 +1,2 @@ -__version__ = "2.0.7" +__version__ = "2.0.8" __author__ = "chenjiandongx" From 64ba867342da59c86a51d705a666e4575b8dc31a Mon Sep 17 00:00:00 2001 From: LeoSun <379978424@qq.com> Date: Fri, 10 Oct 2025 15:24:18 +0800 Subject: [PATCH 149/150] =?UTF-8?q?=F0=9F=9A=80=20Prepare=20for=20version?= =?UTF-8?q?=202.0.9=E3=80=90Last=20support=20version=20for=20Echarts=205.x?= =?UTF-8?q?=E3=80=91=20(#2420)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support with google map(GMap) * add support with leaflet map(LMap); * fix blank * add api in class Base for echarts stats extension * support multiple calendar (#2414) * support multiple calendar * Fix PrettyTable class not expose parameters (#2416) * Fix/grid and table (#2419) fix grid chart_id not working and format table.py * update version to 2.0.9 * fix: issue#2421 (#2422) fix: issue#2421 * Fix grid index (#2426) fix some bugs in Grid component * 更新 MarkPointOpts、MarkPointItemOpts 以及 LabelOpts (#2427) * update README.md * [hotfix]fix page missing css_libs * fix workflows * fix workflows * update engine.py * FIX enable changing locale (#2442) * FIX enable changing locale * FIX add locale two more templates * FIX add tests and defaultLocale * FIX add tests and defaultLocale * FIX add locale render in HTML test --------- Co-authored-by: Feiko Ritsema * update line.py and update workflows * update python-app.yml --------- Co-authored-by: sunhailin Co-authored-by: jiangyang Co-authored-by: GokoRuri <1249736473@qq.com> Co-authored-by: Feiko Co-authored-by: Feiko Ritsema --- .github/workflows/python-app.yml | 25 ++- README.en.md | 4 + README.md | 6 +- pyecharts/_version.py | 2 +- pyecharts/charts/base.py | 7 +- pyecharts/charts/basic_charts/line.py | 2 + pyecharts/charts/composite_charts/grid.py | 53 ++++-- pyecharts/components/table.py | 10 +- pyecharts/globals.py | 8 + pyecharts/options/global_options.py | 38 ++-- pyecharts/options/series_options.py | 21 ++- pyecharts/render/engine.py | 5 +- pyecharts/render/templates/macro | 4 +- .../render/templates/nb_jupyter_globe.html | 2 +- pyecharts/render/templates/simple_globe.html | 2 +- setup.py | 1 + test/test_base.py | 31 +++- test/test_grid.py | 171 +++++++++++++++++- 18 files changed, 329 insertions(+), 63 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 72f57d4b6..4049b4f93 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -1,6 +1,12 @@ name: pyecharts CI -on: [push, pull_request] +on: + push: + paths-ignore: + - '**/*.md' + pull_request: + paths-ignore: + - '**/*.md' jobs: build: @@ -8,8 +14,19 @@ jobs: # Allows for matrix sub-jobs to fail without canceling the rest fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + os: + - ubuntu-latest + - macos-latest + - windows-latest + python-version: + - "3.14" + - "3.13" + - "3.12" + - "3.11" + - "3.10" + - "3.9" + - "3.8" + - "3.7" exclude: # Python < v3.8 does not support Apple Silicon ARM64. - python-version: "3.7" os: macos-latest @@ -34,7 +51,7 @@ jobs: pip install -r test/requirements.txt - name: Run unit test run: | - python setup.py install + pip install . python test.py - name: Codecov run: | diff --git a/README.en.md b/README.en.md index 74e09b3b2..9fe2836d6 100644 --- a/README.en.md +++ b/README.en.md @@ -35,6 +35,10 @@ [Apache ECharts](https://github.com/apache/echarts) is easy-to-use, highly interactive and highly performant javascript visualization library under Apache license. Since its first public release in 2013, it now dominates over 74% of Chinese web front-end market. Yet Python is an expressive language and is loved by data science community. Combining the strength of both technologies, [pyecharts](https://github.com/pyecharts/pyecharts) is born. +* **`pyecharts like` visualization projects** + * [py-vchart](https://github.com/VisActor/py-vchart) + * [py-antv](https://github.com/sunhailin-Leo/pyantv) + ## ✨ Feature highlights * Simple API, Sleek and method chaining diff --git a/README.md b/README.md index a7ac21974..55d9aefa9 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ PyPI - Format - + Contributions welcome @@ -37,6 +37,10 @@ [Apache ECharts](https://github.com/apache/echarts) 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时,[pyecharts](https://github.com/pyecharts/pyecharts) 诞生了。 +* **`pyecharts like` 的可视化项目** + * [py-vchart](https://github.com/VisActor/py-vchart) + * [py-antv](https://github.com/sunhailin-Leo/pyantv) + ## ✨ 特性 * 简洁的 API 设计,使用如丝滑般流畅,支持链式调用 diff --git a/pyecharts/_version.py b/pyecharts/_version.py index a57042679..b44c8e1e6 100644 --- a/pyecharts/_version.py +++ b/pyecharts/_version.py @@ -1,2 +1,2 @@ -__version__ = "2.0.8" +__version__ = "2.0.9" __author__ = "chenjiandongx" diff --git a/pyecharts/charts/base.py b/pyecharts/charts/base.py index b19faab48..f75ebfa06 100644 --- a/pyecharts/charts/base.py +++ b/pyecharts/charts/base.py @@ -5,7 +5,7 @@ from jinja2 import Environment from ..commons import utils -from ..globals import CurrentConfig, RenderType, ThemeType +from ..globals import CurrentConfig, Locale, RenderType, ThemeType, DefaultLocale from ..options import InitOpts, RenderOpts from ..options.series_options import BasicOpts, AnimationOpts from ..render import engine @@ -40,6 +40,11 @@ def __init__( else "" ) self.renderer = _opts.get("renderer", RenderType.CANVAS) + self.locale = ( + CurrentConfig.LOCALE if + getattr(Locale, CurrentConfig.LOCALE, False) + else DefaultLocale + ) 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 diff --git a/pyecharts/charts/basic_charts/line.py b/pyecharts/charts/basic_charts/line.py index 7f23acfb1..e36abc715 100644 --- a/pyecharts/charts/basic_charts/line.py +++ b/pyecharts/charts/basic_charts/line.py @@ -46,6 +46,7 @@ def add_yaxis( tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, label_opts: types.Label = opts.LabelOpts(), + end_label_opts: types.Label = None, linestyle_opts: types.LineStyle = opts.LineStyleOpts(), areastyle_opts: types.AreaStyle = opts.AreaStyleOpts(), emphasis_opts: types.Emphasis = None, @@ -93,6 +94,7 @@ def add_yaxis( "data": data, "hoverAnimation": is_hover_animation, "label": label_opts, + "endLabel": end_label_opts, "logBase": log_base, "sampling": sampling, "dimensions": dimensions, diff --git a/pyecharts/charts/composite_charts/grid.py b/pyecharts/charts/composite_charts/grid.py index 0c22f6a7d..9f80cc653 100644 --- a/pyecharts/charts/composite_charts/grid.py +++ b/pyecharts/charts/composite_charts/grid.py @@ -1,4 +1,5 @@ import copy +from typing import Optional from ... import options as opts from ... import types @@ -30,12 +31,11 @@ def add( chart: Chart, grid_opts: types.Union[opts.GridOpts, dict], *, - grid_index: int = 0, + grid_index: Optional[int] = None, 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=[]) @@ -43,6 +43,9 @@ def add( # Priority Order: Grid > Other Chart self.options.update(backgroundColor=self.bg_color) + # 如果是第一个添加的图表,则初始化 dataZoom 和 visualMap 配置 + self.options.update({"dataZoom": None, "visualMap": None}) + if not is_control_axis_index: for s in self.options.get("series"): s.update(xAxisIndex=self._axis_index, yAxisIndex=self._axis_index) @@ -50,24 +53,30 @@ def add( # 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")]) + if self.options.get("visualMap") is None: + self.options.update( + visualMap=[visual_map] if not isinstance(visual_map, list) + else visual_map + ) else: - if self.options.get("visualMap") is None: - self.options.update(visualMap=[visual_map]) - else: - self.options.get("visualMap").extend([visual_map]) + self.options.get("visualMap").extend( + [visual_map] if not isinstance(visual_map, list) + else 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")]) + if self.options.get("dataZoom") is None: + self.options.update( + dataZoom=[data_zoom] if not isinstance(data_zoom, list) + else data_zoom + ) else: - if self.options.get("dataZoom") is None: - self.options.update(dataZoom=[data_zoom]) - else: - self.options.get("dataZoom").extend([data_zoom]) + self.options.get("dataZoom").extend( + [data_zoom] if not isinstance(data_zoom, list) + else data_zoom + ) # title 配置添加 title = chart.options.get("title", opts.TitleOpts().opts) @@ -88,13 +97,19 @@ def add( self.options.update(geo=chart.options.get("geo")) if isinstance(chart, RectChart): - if grid_index == 0: + if grid_index is None: 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) + if self._grow_grid_index == 0: + for x in self.options.get("xAxis"): + x.update(gridIndex=grid_index) if x.get("gridIndex") is None else ... + for y in self.options.get("yAxis"): + y.update(gridIndex=grid_index) if y.get("gridIndex") is None else ... + else: + for x in chart.options.get("xAxis"): + x.update(gridIndex=grid_index) if x.get("gridIndex") is None else ... + for y in chart.options.get("yAxis"): + y.update(gridIndex=grid_index) if y.get("gridIndex") is None else ... self._grow_grid_index += 1 if self._axis_index > 0: diff --git a/pyecharts/components/table.py b/pyecharts/components/table.py index 3b8141b26..fe85f5cc8 100644 --- a/pyecharts/components/table.py +++ b/pyecharts/components/table.py @@ -22,9 +22,15 @@ def __init__(self, page_title: str = CurrentConfig.PAGE_TITLE, js_host: str = "" self._component_type: str = "table" self.chart_id: str = uuid.uuid4().hex - def add(self, headers: Sequence, rows: Sequence, attributes: Optional[dict] = None): + def add( + self, + headers: Sequence, + rows: Sequence, + attributes: Optional[dict] = None, + **kwargs, + ): attributes = attributes or {"class": "fl-table"} - table = PrettyTable(headers, attributes=attributes) + table = PrettyTable(headers, attributes=attributes, **kwargs) for r in rows: table.add_row(r) self.html_content = table.get_html_string() diff --git a/pyecharts/globals.py b/pyecharts/globals.py index 16d86405d..52e41aa86 100644 --- a/pyecharts/globals.py +++ b/pyecharts/globals.py @@ -10,6 +10,11 @@ class _RenderType: SVG: str = "svg" +class _Locale: + EN: str = "EN" + ZH: str = "ZH" + + class _FileType: SVG: str = "svg" PNG: str = "png" @@ -137,6 +142,7 @@ class _RenderSepType: RenderType = _RenderType() +Locale = _Locale() FileType = _FileType() SymbolType = _SymbolType() ChartType = _ChartType @@ -146,12 +152,14 @@ class _RenderSepType: NotebookType = _NotebookType() OnlineHostType = _OnlineHost() RenderSepType = _RenderSepType() +DefaultLocale = Locale.ZH class _CurrentConfig: PAGE_TITLE = "Awesome-pyecharts" ONLINE_HOST = OnlineHostType.DEFAULT_HOST NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK + LOCALE = DefaultLocale GLOBAL_ENV = Environment( keep_trailing_newline=True, trim_blocks=True, diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 02782b47d..3984cede2 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -175,7 +175,7 @@ def __init__( connected_background_color: str = "#fff", exclude_components: Optional[Sequence[str]] = None, is_show: bool = True, - title: str = "保存为图片", + title: Optional[str] = None, icon: Optional[JSFunc] = None, pixel_ratio: Numeric = 1, ): @@ -194,7 +194,10 @@ def __init__( class ToolBoxFeatureRestoreOpts(BasicOpts): def __init__( - self, is_show: bool = True, title: str = "还原", icon: Optional[JSFunc] = None + self, + is_show: bool = True, + title: Optional[str] = None, + icon: Optional[JSFunc] = None ): self.opts: dict = {"show": is_show, "title": title, "icon": icon} @@ -203,7 +206,7 @@ class ToolBoxFeatureDataViewOpts(BasicOpts): def __init__( self, is_show: bool = True, - title: str = "数据视图", + title: Optional[str] = None, icon: Optional[JSFunc] = None, is_read_only: bool = False, option_to_content: Optional[JSFunc] = None, @@ -216,9 +219,6 @@ def __init__( button_color: str = "#c23531", button_text_color: str = "#fff", ): - if lang is None: - lang = ["数据视图", "关闭", "刷新"] - self.opts: dict = { "show": is_show, "title": title, @@ -240,8 +240,8 @@ class ToolBoxFeatureDataZoomOpts(BasicOpts): def __init__( self, is_show: bool = True, - zoom_title: str = "区域缩放", - back_title: str = "区域缩放还原", + zoom_title: Optional[str] = None, + back_title: Optional[str] = None, zoom_icon: Optional[JSFunc] = None, back_icon: Optional[JSFunc] = None, xaxis_index: Union[Numeric, Sequence, bool] = None, @@ -263,10 +263,10 @@ def __init__( self, is_show: bool = True, type_: Optional[Sequence] = None, - line_title: str = "切换为折线图", - bar_title: str = "切换为柱状图", - stack_title: str = "切换为堆叠", - tiled_title: str = "切换为平铺", + line_title: Optional[str] = None, + bar_title: Optional[str] = None, + stack_title: Optional[str] = None, + tiled_title: Optional[str] = None, line_icon: Optional[JSFunc] = None, bar_icon: Optional[JSFunc] = None, stack_icon: Optional[JSFunc] = None, @@ -303,12 +303,12 @@ def __init__( 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 = "清除选择", + rect_title: Optional[str] = None, + polygon_title: Optional[str] = None, + line_x_title: Optional[str] = None, + line_y_title: Optional[str] = None, + keep_title: Optional[str] = None, + clear_title: Optional[str] = None, ): self.opts: dict = { "type": type_, @@ -844,7 +844,7 @@ def __init__( name_gap: Numeric = 15, name_rotate: Optional[Numeric] = None, interval: Optional[Numeric] = None, - grid_index: Numeric = 0, + grid_index: Optional[Numeric] = None, position: Optional[str] = None, offset: Numeric = 0, split_number: Numeric = 5, diff --git a/pyecharts/options/series_options.py b/pyecharts/options/series_options.py index 5a8cd8961..8117811c5 100644 --- a/pyecharts/options/series_options.py +++ b/pyecharts/options/series_options.py @@ -140,6 +140,7 @@ def __init__( text_shadow_blur: Optional[Numeric] = None, text_shadow_offset_x: Optional[Numeric] = None, text_shadow_offset_y: Optional[Numeric] = None, + offset: Optional[Sequence[Numeric]] = None, overflow: Optional[str] = None, rich: Optional[dict] = None, is_value_animation: bool = False, @@ -218,7 +219,11 @@ def __init__( value: Optional[Numeric] = None, symbol: Optional[str] = None, symbol_size: Union[Numeric, Sequence, None] = None, + symbol_rotate: Optional[Numeric] = None, + symbol_keep_aspect: bool = None, + symbol_offset: Optional[Sequence] = None, itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, + label_opts: Optional[LabelOpts] = None, ): self.opts: dict = { "name": name, @@ -231,7 +236,11 @@ def __init__( "value": value, "symbol": symbol, "symbolSize": symbol_size, + "symbolRotate": symbol_rotate, + "symbolKeepAspect": symbol_keep_aspect, + "symbolOffset": symbol_offset, "itemStyle": itemstyle_opts, + "label": label_opts, } @@ -241,13 +250,23 @@ def __init__( data: Sequence[Union[MarkPointItem, dict]] = None, symbol: Optional[str] = None, symbol_size: Union[None, Numeric] = None, - label_opts: LabelOpts = LabelOpts(position="inside", color="#fff"), + symbol_rotate: Optional[Numeric] = None, + symbol_keep_aspect: bool = None, + symbol_offset: Optional[Sequence] = None, + silent: bool = None, + label_opts: Optional[LabelOpts] = None, + itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, animation_opts: Union[AnimationOpts, dict, None] = None, ): self.opts: dict = { "symbol": symbol, "symbolSize": symbol_size, + "symbolRotate": symbol_rotate, + "symbolKeepAspect": symbol_keep_aspect, + "symbolOffset": symbol_offset, + "silent": silent, "label": label_opts, + "itemStyle": itemstyle_opts, "data": data, } diff --git a/pyecharts/render/engine.py b/pyecharts/render/engine.py index 271596e1c..715286376 100644 --- a/pyecharts/render/engine.py +++ b/pyecharts/render/engine.py @@ -58,7 +58,10 @@ def generate_js_link(chart: Any) -> Any: links.append(_link) break chart.dependencies = links - chart.css_libs = css_links + if hasattr(chart, "css_libs"): + chart.css_libs = chart.css_libs + css_links + else: + chart.css_libs = css_links return chart def render_chart_to_file(self, template_name: str, chart: Any, path: str, **kwargs): diff --git a/pyecharts/render/templates/macro b/pyecharts/render/templates/macro index 0f8325650..71c203b23 100644 --- a/pyecharts/render/templates/macro +++ b/pyecharts/render/templates/macro @@ -36,7 +36,7 @@ document.getElementById('{{ c.chart_id }}').style.width = document.getElementById('{{ c.chart_id }}').parentNode.clientWidth + 'px'; {% endif %} var chart_{{ c.chart_id }} = echarts.init( - document.getElementById('{{ c.chart_id }}'), '{{ c.theme }}', {renderer: '{{ c.renderer }}'}); + document.getElementById('{{ c.chart_id }}'), '{{ c.theme }}', {renderer: '{{ c.renderer }}', locale: '{{ c.locale }}'}); {% for js in c.js_functions.items %} {{ js }} {% endfor %} @@ -102,7 +102,7 @@ {% for c in charts %} {% if c._component_type not in ("table", "image") %} var chart_{{ c.chart_id }} = echarts.init( - document.getElementById('{{ c.chart_id }}'), '{{ c.theme }}', {renderer: '{{ c.renderer }}'}); + document.getElementById('{{ c.chart_id }}'), '{{ c.theme }}', {renderer: '{{ c.renderer }}', locale: '{{ c.locale }}'}); {% for js in c.js_functions.items %} {{ js }} {% endfor %} diff --git a/pyecharts/render/templates/nb_jupyter_globe.html b/pyecharts/render/templates/nb_jupyter_globe.html index e9b4d442a..88c3a0c2e 100644 --- a/pyecharts/render/templates/nb_jupyter_globe.html +++ b/pyecharts/render/templates/nb_jupyter_globe.html @@ -20,7 +20,7 @@ var mapOption_{{ c.chart_id }} = {{ c.json_contents }}; mapChart_{{ c.chart_id }}.setOption(mapOption_{{ c.chart_id }}); var chart_{{ c.chart_id }} = echarts.init( - document.getElementById('{{ c.chart_id }}'), '{{ c.theme }}', {renderer: '{{ c.renderer }}'}); + document.getElementById('{{ c.chart_id }}'), '{{ c.theme }}', {renderer: '{{ c.renderer }}', locale: '{{ c.locale }}'}); {% for js in c.js_functions.items %} {{ js }} {% endfor %} diff --git a/pyecharts/render/templates/simple_globe.html b/pyecharts/render/templates/simple_globe.html index ad37793bc..b9e4bbe4f 100644 --- a/pyecharts/render/templates/simple_globe.html +++ b/pyecharts/render/templates/simple_globe.html @@ -19,7 +19,7 @@ mapChart_{{ chart.chart_id }}.setOption(mapOption_{{ chart.chart_id }}); var chart_{{ chart.chart_id }} = echarts.init( - document.getElementById('{{ chart.chart_id }}'), '{{ chart.theme }}', {renderer: '{{ chart.renderer }}'}); + document.getElementById('{{ chart.chart_id }}'), '{{ chart.theme }}', {renderer: '{{ chart.renderer }}', locale: '{{ chart.locale }}'}); var options_{{ chart.chart_id }} = { "globe": { "show": true, diff --git a/setup.py b/setup.py index 0fc4cebba..c73d6df2e 100644 --- a/setup.py +++ b/setup.py @@ -97,6 +97,7 @@ def run(self): "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries", ], cmdclass={"upload": UploadCommand}, diff --git a/test/test_base.py b/test/test_base.py index 1dafc6885..498e0cf44 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -4,7 +4,7 @@ from pyecharts.charts import Bar from pyecharts.options import InitOpts, RenderOpts -from pyecharts.globals import CurrentConfig +from pyecharts.globals import CurrentConfig, Locale from pyecharts.charts.base import Base, default from pyecharts.options.series_options import AnimationOpts @@ -94,3 +94,32 @@ def test_base_chart_id(self): def test_use_echarts_stat(self): c0 = Base().use_echarts_stat() self.assertEqual(c0.js_dependencies.items, ["echarts", "echarts-stat"]) + + def test_default_locale_zh(self): + # Default locale should be Chinese + c0 = Base() + self.assertEqual(c0.locale, Locale.ZH) + + def test_set_locale_en(self): + # Set to English locale + CurrentConfig.LOCALE = Locale.EN + c0 = Base() + self.assertEqual(c0.locale, Locale.EN) + + def test_set_locale_wrong(self): + # Set to a wrong locale, should fallback to default + CurrentConfig.LOCALE = "wrong_locale" + c0 = Base() + self.assertEqual(c0.locale, Locale.ZH) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_locale_renders_in_html(self, fake_writer): + CurrentConfig.LOCALE = Locale.ZH + c = ( + Bar() + .add_xaxis(["A", "B", "C"]) + .add_yaxis("series0", [1, 2, 4]) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("locale: 'ZH'", content) diff --git a/test/test_grid.py b/test/test_grid.py index 5c0dab215..44a53eda0 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -8,6 +8,16 @@ class TestGridComponent(unittest.TestCase): + def _verify_instance_for_datazoom_and_visualmap(self, chart, key): + return isinstance( + chart.options.get(key), list + ) and all( + isinstance( + item, + (opts.DataZoomOpts if key == "dataZoom" else opts.VisualMapOpts, dict) + ) for item in chart.options.get(key) + ) + def _chart_for_grid(self) -> Bar: x_data = ["{}月".format(i) for i in range(1, 13)] bar = ( @@ -49,7 +59,19 @@ def _chart_for_grid(self) -> Bar: bar.overlap(line) return bar - def _chart_for_grid_with_datazoom(self) -> Bar: + def _chart_for_set_grid_index(self, gird_index=None) -> Bar: + bar = ( + Bar() + .add_xaxis(Faker.choose()) + .add_yaxis("商家A", Faker.values()) + .set_global_opts( + xaxis_opts=opts.AxisOpts(grid_index=gird_index), + yaxis_opts=opts.AxisOpts(grid_index=gird_index) + ) + ) + return bar + + def _chart_for_grid_with_datazoom_and_visualmap(self) -> Bar: bar_1 = ( Bar() .add_xaxis(Faker.days_attrs) @@ -57,6 +79,7 @@ def _chart_for_grid_with_datazoom(self) -> Bar: .set_global_opts( title_opts=opts.TitleOpts(title="Bar-DataZoom"), datazoom_opts=opts.DataZoomOpts(), + visualmap_opts=opts.VisualMapOpts(), toolbox_opts=opts.ToolboxOpts( feature=opts.ToolBoxFeatureOpts( save_as_image=opts.ToolBoxFeatureSaveAsImageOpts( @@ -69,6 +92,95 @@ def _chart_for_grid_with_datazoom(self) -> Bar: ) return bar_1 + def _chart_for_grid_with_multiple_datazoom_and_visualmap(self) -> Bar: + bar_1 = ( + Bar() + .add_xaxis(Faker.days_attrs) + .add_yaxis("商家", Faker.days_values) + .set_global_opts( + title_opts=opts.TitleOpts(title="Bar-DataZoom"), + datazoom_opts=[opts.DataZoomOpts(), opts.DataZoomOpts()], + visualmap_opts=[opts.VisualMapOpts(), opts.VisualMapOpts()], + toolbox_opts=opts.ToolboxOpts( + feature=opts.ToolBoxFeatureOpts( + save_as_image=opts.ToolBoxFeatureSaveAsImageOpts( + exclude_components=["dataZoom", "toolbox"], + ) + ), + ), + legend_opts=opts.LegendOpts(), + ) + ) + return bar_1 + + def test_grid_set_grid_index(self): + bar_1 = self._chart_for_set_grid_index() + bar_2 = self._chart_for_set_grid_index() + bar_3 = self._chart_for_set_grid_index() + gc = ( + Grid() + .add(chart=bar_1, grid_index=1, grid_opts=opts.GridOpts()) + .add(chart=bar_2, grid_index=0, grid_opts=opts.GridOpts()) + .add(chart=bar_3, grid_index=2, grid_opts=opts.GridOpts()) + ) + expected_idx = (1, 0, 2) + for idx, series in enumerate(gc.options.get("xAxis")): + self.assertEqual(series.get("gridIndex"), expected_idx[idx]) + + bar_1 = self._chart_for_set_grid_index() + bar_2 = self._chart_for_set_grid_index() + bar_3 = self._chart_for_set_grid_index() + gc = ( + Grid() + .add(chart=bar_1, grid_index=0, grid_opts=opts.GridOpts()) + .add(chart=bar_2, grid_index=2, grid_opts=opts.GridOpts()) + .add(chart=bar_3, grid_index=1, grid_opts=opts.GridOpts()) + ) + expected_idx = (0, 2, 1) + for idx, series in enumerate(gc.options.get("xAxis")): + self.assertEqual(series.get("gridIndex"), expected_idx[idx]) + + def test_grid_set_grid_index_priority(self): + bar_1 = self._chart_for_set_grid_index(gird_index=2) + bar_2 = self._chart_for_set_grid_index(gird_index=0) + bar_3 = self._chart_for_set_grid_index(gird_index=1) + gc = ( + Grid() + .add(chart=bar_1, grid_index=1, grid_opts=opts.GridOpts()) + .add(chart=bar_2, grid_index=2, grid_opts=opts.GridOpts()) + .add(chart=bar_3, grid_index=0, grid_opts=opts.GridOpts()) + ) + expected_idx = (2, 0, 1) + for idx, series in enumerate(gc.options.get("xAxis")): + self.assertEqual(series.get("gridIndex"), expected_idx[idx]) + + bar_1 = self._chart_for_set_grid_index(gird_index=0) + bar_2 = self._chart_for_set_grid_index(gird_index=2) + bar_3 = self._chart_for_set_grid_index(gird_index=1) + gc = ( + Grid() + .add(chart=bar_1, grid_index=2, grid_opts=opts.GridOpts()) + .add(chart=bar_2, grid_index=1, grid_opts=opts.GridOpts()) + .add(chart=bar_3, grid_index=0, grid_opts=opts.GridOpts()) + ) + expected_idx = (0, 2, 1) + for idx, series in enumerate(gc.options.get("xAxis")): + self.assertEqual(series.get("gridIndex"), expected_idx[idx]) + + def test_grid_set_grid_out_of_order(self): + bar_1 = self._chart_for_set_grid_index(gird_index=2) + bar_2 = self._chart_for_set_grid_index(gird_index=0) + bar_3 = self._chart_for_set_grid_index(gird_index=1) + gc = ( + Grid() + .add(chart=bar_2, grid_index=1, grid_opts=opts.GridOpts()) + .add(chart=bar_3, grid_index=2, grid_opts=opts.GridOpts()) + .add(chart=bar_1, grid_index=0, grid_opts=opts.GridOpts()) + ) + expected_idx = (0, 1, 2) + for idx, series in enumerate(gc.options.get("xAxis")): + self.assertEqual(series.get("gridIndex"), expected_idx[idx]) + def test_grid_control_axis_index(self): bar = self._chart_for_grid() gc = Grid().add( @@ -87,29 +199,70 @@ def test_grid_do_not_control_axis_index(self): for idx, series in enumerate(gc.options.get("series")): self.assertEqual(series.get("yAxisIndex"), expected_idx[idx]) - def test_grid_with_more_datazoom_opts(self): - bar_1 = self._chart_for_grid_with_datazoom() - bar_2 = self._chart_for_grid_with_datazoom() + def test_grid_with_more_datazoom_and_visualmap_opts(self): + bar_1 = self._chart_for_grid_with_datazoom_and_visualmap() + bar_2 = self._chart_for_grid_with_datazoom_and_visualmap() grid_1 = ( Grid() .add(chart=bar_1, grid_opts=opts.GridOpts()) .add(chart=bar_2, grid_opts=opts.GridOpts()) ) - expected_datazoom_opts_len = 2 + expected_opts_len = 2 + self.assertEqual( + len(grid_1.options.get("dataZoom")), expected_opts_len + ) + self.assertEqual( + len(grid_1.options.get("visualMap")), expected_opts_len + ) + self.assertEqual( + self._verify_instance_for_datazoom_and_visualmap(grid_1, "dataZoom"), True + ) self.assertEqual( - len(grid_1.options.get("dataZoom")), expected_datazoom_opts_len + self._verify_instance_for_datazoom_and_visualmap(grid_1, "visualMap"), True ) bar_3 = self._chart_for_grid() - bar_4 = self._chart_for_grid_with_datazoom() + bar_4 = self._chart_for_grid_with_datazoom_and_visualmap() grid_2 = ( Grid() .add(chart=bar_3, grid_opts=opts.GridOpts()) .add(chart=bar_4, grid_opts=opts.GridOpts()) ) - expected_datazoom_opts_len = 1 + expected_opts_len = 1 + self.assertEqual( + len(grid_2.options.get("dataZoom")), expected_opts_len + ) + self.assertEqual( + len(grid_2.options.get("visualMap")), expected_opts_len + ) + self.assertEqual( + self._verify_instance_for_datazoom_and_visualmap(grid_2, "dataZoom"), True + ) + self.assertEqual( + self._verify_instance_for_datazoom_and_visualmap(grid_2, "visualMap"), True + ) + + bar_5 = self._chart_for_grid_with_multiple_datazoom_and_visualmap() + bar_6 = self._chart_for_grid_with_datazoom_and_visualmap() + bar_7 = self._chart_for_grid() + grid_3 = ( + Grid() + .add(chart=bar_5, grid_opts=opts.GridOpts()) + .add(chart=bar_6, grid_opts=opts.GridOpts()) + .add(chart=bar_7, grid_opts=opts.GridOpts()) + ) + expected_opts_len = 3 + self.assertEqual( + len(grid_3.options.get("dataZoom")), expected_opts_len + ) + self.assertEqual( + len(grid_3.options.get("visualMap")), expected_opts_len + ) + self.assertEqual( + self._verify_instance_for_datazoom_and_visualmap(grid_3, "dataZoom"), True + ) self.assertEqual( - len(grid_2.options.get("dataZoom")), expected_datazoom_opts_len + self._verify_instance_for_datazoom_and_visualmap(grid_3, "visualMap"), True ) @patch("pyecharts.render.engine.write_utf8_html_file") From c50da2aa877bb4ce576b15b5be3b2ad0c8aa33c6 Mon Sep 17 00:00:00 2001 From: LeoSun Date: Tue, 10 Feb 2026 19:45:03 +0800 Subject: [PATCH 150/150] Ready to version 2.1.0 (#2459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support with google map(GMap) * add support with leaflet map(LMap); * fix blank * add api in class Base for echarts stats extension * support multiple calendar (#2414) * support multiple calendar * Fix PrettyTable class not expose parameters (#2416) * Fix/grid and table (#2419) fix grid chart_id not working and format table.py * update version to 2.0.9 * fix: issue#2421 (#2422) fix: issue#2421 * Fix grid index (#2426) fix some bugs in Grid component * 更新 MarkPointOpts、MarkPointItemOpts 以及 LabelOpts (#2427) * update README.md * [hotfix]fix page missing css_libs * fix workflows * fix workflows * update engine.py * FIX enable changing locale (#2442) * FIX enable changing locale * FIX add locale two more templates * FIX add tests and defaultLocale * FIX add tests and defaultLocale * FIX add locale render in HTML test --------- Co-authored-by: Feiko Ritsema * update line.py and update workflows * update python-app.yml * support Echarts 6.X and no longer fix Echarts 5.X issues. (#2451) * support Echarts 6.X and no longer fix Echarts 5.X issues. * add AGENTS.md and Makefile * delete unused import and update workflows * update workflow * update version to 2.1.0; support uv and add pyproject.toml; add Makefile; * update workflow * fix pyproject.toml * fix pyproject.toml and test/requirements.txt * fix damn actions config * fix ut and pyproject.toml in old version python * fix ci config; * fix conflict and merge master; --------- Co-authored-by: sunhailin Co-authored-by: jiangyang Co-authored-by: GokoRuri <1249736473@qq.com> Co-authored-by: Feiko Co-authored-by: Feiko Ritsema --- .github/workflows/python-app.yml | 7 +- AGENTS.md | 294 ++ Makefile | 28 + pyecharts/_version.py | 2 +- pyecharts/charts/__init__.py | 1 + pyecharts/charts/basic_charts/bar.py | 8 + pyecharts/charts/basic_charts/boxplot.py | 10 + pyecharts/charts/basic_charts/chord.py | 74 + pyecharts/charts/basic_charts/custom.py | 71 +- .../charts/basic_charts/effectscatter.py | 18 + pyecharts/charts/basic_charts/funnel.py | 14 + pyecharts/charts/basic_charts/gauge.py | 14 + pyecharts/charts/basic_charts/geo.py | 32 + pyecharts/charts/basic_charts/graph.py | 44 + pyecharts/charts/basic_charts/heatmap.py | 16 + pyecharts/charts/basic_charts/kline.py | 8 + pyecharts/charts/basic_charts/line.py | 12 + pyecharts/charts/basic_charts/map.py | 16 + pyecharts/charts/basic_charts/parallel.py | 9 +- pyecharts/charts/basic_charts/pictorialbar.py | 10 + pyecharts/charts/basic_charts/pie.py | 20 + pyecharts/charts/basic_charts/polar.py | 24 +- pyecharts/charts/basic_charts/radar.py | 18 +- pyecharts/charts/basic_charts/sankey.py | 26 + pyecharts/charts/basic_charts/scatter.py | 34 + pyecharts/charts/basic_charts/sunburst.py | 14 + pyecharts/charts/basic_charts/themeriver.py | 26 + pyecharts/charts/basic_charts/tree.py | 16 + pyecharts/charts/basic_charts/treemap.py | 14 + pyecharts/charts/chart.py | 31 +- pyecharts/charts/composite_charts/grid.py | 6 +- pyecharts/charts/composite_charts/timeline.py | 16 + pyecharts/datasets/map_filename.json | 7 + pyecharts/globals.py | 10 +- pyecharts/options/__init__.py | 21 + pyecharts/options/charts_options.py | 112 +- pyecharts/options/global_options.py | 673 ++- pyecharts/options/series_options.py | 44 +- pyecharts/types.py | 17 + pyproject.toml | 85 + test/requirements.txt | 1 + test/test_bar.py | 6 +- test/test_chart.py | 13 +- test/test_chart_options.py | 31 + test/test_chord.py | 48 + test/test_custom.py | 30 + test/test_global_options.py | 135 +- test/test_grid.py | 32 +- test/test_map.py | 12 +- test/test_page.py | 4 +- test/test_series_options.py | 12 + test/test_tab.py | 4 +- test/test_wordcloud.py | 43 +- uv.lock | 3866 +++++++++++++++++ 54 files changed, 5993 insertions(+), 146 deletions(-) create mode 100644 AGENTS.md create mode 100644 Makefile create mode 100644 pyecharts/charts/basic_charts/chord.py create mode 100644 pyproject.toml create mode 100644 test/test_chord.py create mode 100644 uv.lock diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 4049b4f93..7b4923a45 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -33,8 +33,9 @@ jobs: - python-version: "3.7" os: ubuntu-latest include: # So run those legacy versions on Intel CPUs. - - python-version: "3.7" - os: macos-13 + # No ci env to test macos 13 with python3.7 + #- python-version: "3.7" + # os: macos-13 - python-version: "3.7" os: ubuntu-22.04 runs-on: ${{ matrix.os }} @@ -52,7 +53,7 @@ jobs: - name: Run unit test run: | pip install . - python test.py + pytest -v --cov-config=pyproject.toml --cov=./ test/ - name: Codecov run: | codecov diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..0f153c65d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,294 @@ +# AGENTS.md + +This file contains guidelines for agents working on the pyecharts project. + +## Build/Lint/Test Commands + +### Running Tests + +The project uses multiple testing frameworks. Here are the commands to run tests: + +1. **Run all tests**: + ```bash + make test + # or + python test.py + # or + nose2 --with-coverage --coverage pyecharts --coverage-config .coveragerc -s test + # or + pytest -cov-config=.coveragerc --cov=./ test/ + ``` + +2. **Run a single test file**: + ```bash + # Using pytest (recommended) + pytest test/test_chart.py + + # Using nose2 + nose2 --with-coverage --coverage pyecharts --coverage-config .coveragerc test.test_chart + ``` + +3. **Run a specific test method**: + ```bash + # Using pytest + pytest test/test_chart.py::TestChartClass::test_chart_dark_mode + + # Using nose2 + nose2 --with-coverage --coverage pyecharts --coverage-config .coveragerc test.test_chart.TestChartClass.test_chart_dark_mode + ``` + +4. **Run tests with verbose output**: + ```bash + pytest -v test/test_chart.py + ``` + +5. **Run tests with coverage reporting**: + ```bash + pytest -v --cov=./ --cov-report=html test/test_chart.py + ``` + +### Linting + +1. **Run linter**: + ```bash + make lint + # or + flake8 --exclude=build,images,example,examples --max-line-length=89 --ignore=F401 + ``` + +2. **Lint a specific file**: + ```bash + flake8 --exclude=build,images,example,examples --max-line-length=89 --ignore=F401 test/test_chart.py + ``` + +### Formatting + +The project uses black and isort for code formatting: + +1. **Run black formatter**: + ```bash + black . + ``` + +2. **Run isort for imports**: + ```bash + isort . + ``` + +### Building + +1. **Build the package**: + ```bash + python setup.py sdist bdist_wheel + ``` + +2. **Upload to PyPI**: + ```bash + make upload + # This runs the UploadCommand defined in setup.py + ``` + +## Code Style Guidelines + +### Imports + +1. **Import organization**: + - Standard library imports + - Related third party imports + - Local application/library specific imports + +2. **Import style**: + ```python + # Good + from pyecharts import options as opts + from pyecharts.charts import Line, Bar + + # Avoid + from pyecharts.options import * + import pyecharts.charts.Line + ``` + +3. **Use aliases** for commonly used modules: + ```python + import pyecharts.options as opts + import pyecharts.globals as globals + ``` + +### Formatting + +1. **Line length**: 89 characters max (as specified in flake8 command) + +2. **Indentation**: 4 spaces (no tabs) + +3. **Quotes**: Use double quotes " for strings, single quotes ' for char literals + +4. **Blank lines**: + - Top-level functions and classes: 2 blank lines + - Methods within a class: 1 blank line + +5. **Use black formatter** to ensure consistent code style + +### Naming Conventions + +1. **Variables and functions**: snake_case + ```python + def add_yaxis(self, series_name, y_axis, *args, **kwargs): + pass + ``` + +2. **Classes**: PascalCase + ```python + class TestChartClass(unittest.TestCase): + pass + ``` + +3. **Constants**: UPPER_CASE + ```python + MAX_RETRIES = 3 + ``` + +4. **Private members**: prefix with _ + ```python + def _internal_method(self): + pass + ``` + +### Error Handling + +1. **Use exceptions** for error conditions, not return codes + +2. **Be specific** with exception types: + ```python + try: + result = do_something() + except ValueError as e: + logger.error(f"Invalid value: {e}") + raise + except RuntimeError as e: + logger.error(f"Runtime error: {e}") + raise + ``` + +3. **Don't catch all exceptions** indiscriminately: + ```python + # Avoid + try: + do_something() + except: + pass + + # Prefer + try: + do_something() + except SpecificError: + handle_specific_error() + ``` + +### Testing Guidelines + +1. **Test structure**: + - Use unittest framework with TestCase classes + - Group related tests in classes + - Use descriptive test method names + +2. **Test naming**: + ```python + def test_chart_dark_mode(self): + # Tests a specific feature + + def test_chart_line_style_opts(self): + # Tests a specific option + ``` + +3. **Use mocking** for external dependencies: + ```python + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chart_dark_mode(self, fake_writer): + # Test code here + ``` + +4. **Test coverage**: + - Aim for high test coverage + - Test both success and failure cases + - Test edge cases + +### Documentation + +1. **Docstrings**: + - Use Google-style docstrings + - Include Args, Returns, and Raises sections when applicable + + ```python + def add_yaxis(self, series_name, y_axis, color=None): + """Add a series to the chart. + + Args: + series_name (str): Name of the series. + y_axis (list): Data values. + color (str, optional): Color of the series. Defaults to None. + + Returns: + Chart: The chart instance for method chaining. + """ + ``` + +2. **Comments**: + - Use comments to explain "why", not "what" + - Keep comments up-to-date with code changes + +### Best Practices + +1. **Method chaining**: The library supports method chaining, so return self in methods that modify the object: + ```python + def add_xaxis(self, xaxis_data): + self.options["xAxis"][0]["data"] = xaxis_data + return self # Enable method chaining + ``` + +2. **Type hints**: Use type hints for better code documentation and IDE support: + ```python + def add_yaxis(self, series_name: str, y_axis: list) -> "Base": + # method implementation + return self + ``` + +3. **Immutable options**: When adding options, don't modify the original option objects: + ```python + # Good - create a copy + self.options = deepcopy(self.options) + + # Or use dict constructor + new_opts = dict(old_opts) + ``` + +4. **Performance**: Be mindful of performance when processing large datasets. + +5. **Backward compatibility**: When making changes, consider backward compatibility for existing users. + +## Development Workflow + +1. **Make changes** to the code +2. **Run tests** to ensure nothing is broken: + ```bash + make test + ``` +3. **Lint the code**: + ```bash + make lint + ``` +4. **Format the code**: + ```bash + black . + isort . + ``` +5. **Commit and push** your changes + +## Additional Notes + +- The project uses both pytest and nose2 for testing. pytest is recommended for running individual tests. +- Coverage is configured in .coveragerc to exclude test files and templates. +- The build process is defined in setup.py with a custom UploadCommand for publishing to PyPI. +- JavaScript dependencies are managed through the js_dependencies attribute in Base class. +- The default locale is Chinese (ZH), but can be changed to English (EN). +- Charts support dark mode via set_dark_mode() method. +- Use the render() method to generate HTML files, and render_embed() for embedding charts. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..c32b0ce1c --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +# Variables +PACKAGE_NAME = pyecharts + +# Targets +.PHONY: help +help: + @echo "Please use \`make \` where is one of" + @echo " test -- run local unit tests" + @echo " build -- build the package" + @echo " lint -- run flake8 for code linting" + @echo " clean -- clean up temporary files" + +.PHONY: test +test: + @uv run pytest -v --cov-config=pyproject.toml --cov=./ test/ + +.PHONY: build +build: + @uv build + +.PHONY: lint +lint: + @uv run flake8 --exclude=build,images,example,examples,.venv --max-line-length=89 --ignore=F401,F824 + +.PHONY: clean +clean: + @rm -rf dist build $(PACKAGE_NAME).egg-info + @rm -f .coverage coverage.xml tests/.coverage tests/coverage.xml resize_render.html diff --git a/pyecharts/_version.py b/pyecharts/_version.py index b44c8e1e6..ae17e2131 100644 --- a/pyecharts/_version.py +++ b/pyecharts/_version.py @@ -1,2 +1,2 @@ -__version__ = "2.0.9" +__version__ = "2.1.0" __author__ = "chenjiandongx" diff --git a/pyecharts/charts/__init__.py b/pyecharts/charts/__init__.py index 36426a85d..42d20cfec 100644 --- a/pyecharts/charts/__init__.py +++ b/pyecharts/charts/__init__.py @@ -4,6 +4,7 @@ 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.chord import Chord from ..charts.basic_charts.custom import Custom from ..charts.basic_charts.effectscatter import EffectScatter from ..charts.basic_charts.funnel import Funnel diff --git a/pyecharts/charts/basic_charts/bar.py b/pyecharts/charts/basic_charts/bar.py index 8ceb44b40..370be4804 100644 --- a/pyecharts/charts/basic_charts/bar.py +++ b/pyecharts/charts/basic_charts/bar.py @@ -17,6 +17,9 @@ def add_yaxis( series_name: str, y_axis: types.Sequence[types.Union[types.Numeric, opts.BarItem, dict]], *, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, xaxis_index: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, polar_index: types.Optional[types.Numeric] = None, @@ -29,6 +32,7 @@ def add_yaxis( background_style: types.Union[types.BarBackground, dict, None] = None, stack: types.Optional[str] = None, stack_strategy: types.Optional[str] = "samesign", + stack_order: types.Optional[str] = None, sampling: types.Optional[str] = None, cursor: types.Optional[str] = "pointer", bar_width: types.Union[types.Numeric, str] = None, @@ -69,6 +73,9 @@ def add_yaxis( { "type": ChartType.BAR, "name": series_name, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "xAxisIndex": xaxis_index, "yAxisIndex": yaxis_index, "polarIndex": polar_index, @@ -81,6 +88,7 @@ def add_yaxis( "backgroundStyle": background_style, "stack": stack, "stackStrategy": stack_strategy, + "stackOrder": stack_order, "sampling": sampling, "cursor": cursor, "barWidth": bar_width, diff --git a/pyecharts/charts/basic_charts/boxplot.py b/pyecharts/charts/basic_charts/boxplot.py index f199ed74b..5d52e1813 100644 --- a/pyecharts/charts/basic_charts/boxplot.py +++ b/pyecharts/charts/basic_charts/boxplot.py @@ -21,8 +21,13 @@ def add_yaxis( ] = None, *, chart_type: str = ChartType.BOXPLOT, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, xaxis_index: types.Optional[types.Numeric] = None, + xaxis_id: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, + yaxis_id: types.Optional[types.Numeric] = None, dataset_index: types.Optional[types.Numeric] = None, color_by: types.Optional[str] = None, is_legend_hover_link: bool = True, @@ -52,8 +57,13 @@ def add_yaxis( { "type": chart_type, "name": series_name, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "xAxisIndex": xaxis_index, + "xAxisId": xaxis_id, "yAxisIndex": yaxis_index, + "yAxisId": yaxis_id, "datasetIndex": dataset_index, "colorBy": color_by, "legendHoverLink": is_legend_hover_link, diff --git a/pyecharts/charts/basic_charts/chord.py b/pyecharts/charts/basic_charts/chord.py new file mode 100644 index 000000000..bd45997d0 --- /dev/null +++ b/pyecharts/charts/basic_charts/chord.py @@ -0,0 +1,74 @@ +from ... import types +from ...charts.chart import Chart +from ...globals import ChartType + + +class Chord(Chart): + """ + <<< Chord >>> + + The chord diagram visually presents the flow and weights + within complex relationship networks, making it particularly + suitable for multidimensional relationship analysis in scenarios + such as financial transactions and social networks. + """ + + def add( + self, + series_name: str, + data: types.Sequence[types.ChordData], + links: types.Sequence[types.ChordLink], + *, + is_clockwise: bool = False, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + color_by: types.Optional[str] = None, + 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, + center: types.Optional[types.Sequence] = None, + radius: types.Optional[types.Union[types.Sequence, str]] = None, + start_angle: types.Numeric = 90, + end_angle: types.Optional[types.Numeric] = None, + min_angle: types.Optional[types.Numeric] = None, + pad_angle: types.Optional[types.Numeric] = None, + tooltip_opts: types.Tooltip = None, + label_opts: types.Label = None, + linestyle_opts: types.LineStyle = None, + itemstyle_opts: types.ItemStyle = None, + emphasis_opts: types.Emphasis = None, + ): + self.options.get("series").append({ + "type": ChartType.CHORD, + "name": series_name, + "data": data, + "links": links, + "clockwise": is_clockwise, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "colorBy": color_by, + "left": pos_left, + "top": pos_top, + "right": pos_right, + "bottom": pos_bottom, + "width": width, + "height": height, + "center": center, + "radius": radius, + "startAngle": start_angle, + "endAngle": end_angle, + "minAngle": min_angle, + "padAngle": pad_angle, + "tooltip": tooltip_opts, + "label": label_opts, + "lineStyle": linestyle_opts, + "itemStyle": itemstyle_opts, + "emphasis": emphasis_opts, + }) + + return self diff --git a/pyecharts/charts/basic_charts/custom.py b/pyecharts/charts/basic_charts/custom.py index d23fa7d72..6978a41f7 100644 --- a/pyecharts/charts/basic_charts/custom.py +++ b/pyecharts/charts/basic_charts/custom.py @@ -12,19 +12,73 @@ class Custom(Chart): in the series. This enables the extension of different charts. """ + def register_echarts_x(self, chart_type: str): + if chart_type not in [ + ChartType.VIOLIN, + ChartType.STAGE, + ChartType.DOUGHNUT, + ChartType.CONTOUR, + ChartType.BAR_RANGE, + ChartType.LINE_RANGE, + ]: + raise ValueError(f"Not support register this chart type: {chart_type}") + + if chart_type == ChartType.VIOLIN: + self.js_dependencies.add("echarts-x-violin") + self.options.update(xAxis=[{}], yAxis=[{}]) + + if chart_type == ChartType.STAGE: + self.js_dependencies.add("echarts-x-stage") + self.options.update(xAxis=[{}], yAxis=[{}]) + + if chart_type == ChartType.DOUGHNUT: + self.js_dependencies.add("echarts-x-segmented-doughnut") + + if chart_type == ChartType.LINE_RANGE: + self.js_dependencies.add("echarts-x-line-range") + self.options.update(xAxis=[{}], yAxis=[{}]) + + if chart_type == ChartType.CONTOUR: + self.js_dependencies.add("echarts-x-contour-d3") + self.js_dependencies.add("echarts-x-contour") + self.options.update(xAxis=[{}], yAxis=[{}]) + + if chart_type == ChartType.BAR_RANGE: + self.js_dependencies.add("echarts-x-bar-range") + self.options.update(xAxis=[{}], yAxis=[{}]) + + return self + + def add_xaxis(self, xaxis_data: types.Sequence): + self.options["xAxis"][0].update(data=xaxis_data) + self._xaxis_data = xaxis_data + return self + def add( self, series_name: str, render_item: types.JSFunc, *, + type_: str = ChartType.CUSTOM, color_by: str = "series", is_legend_hover_link: bool = True, coordinate_system: str = "cartesian2d", + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, x_axis_index: types.Numeric = 0, + xaxis_id: types.Optional[types.Numeric] = None, y_axis_index: types.Numeric = 0, + yaxis_id: types.Optional[types.Numeric] = None, polar_index: types.Numeric = 0, + polar_id: types.Optional[types.Numeric] = None, + single_axis_index: types.Optional[types.Numeric] = None, + single_axis_id: types.Optional[types.Numeric] = None, geo_index: types.Numeric = 0, + geo_id: types.Optional[types.Numeric] = None, calendar_index: types.Numeric = 0, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, dataset_index: types.Numeric = 0, series_layout_by: str = "column", selected_mode: types.Union[bool, str] = False, @@ -36,23 +90,36 @@ def add( z: types.Numeric = 2, itemstyle_opts: types.ItemStyle = None, tooltip_opts: types.Tooltip = None, + label_opts: types.Label = None, emphasis_opts: types.Emphasis = None, + item_payload_opts: types.CustomItemPayload = None, ): self._append_legend(series_name) self.options.get("series").append( { - "type": ChartType.CUSTOM, + "type": type_, "name": series_name, "renderItem": render_item, "colorBy": color_by, "legendHoverLink": is_legend_hover_link, "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "xAxisIndex": x_axis_index, + "xAxisId": xaxis_id, "yAxisIndex": y_axis_index, + "yAxisId": yaxis_id, "polarIndex": polar_index, + "polarId": polar_id, + "singleAxisIndex": single_axis_index, + "singleAxisId": single_axis_id, "geoIndex": geo_index, + "geoId": geo_id, "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "datasetIndex": dataset_index, "seriesLayoutBy": series_layout_by, "itemStyle": itemstyle_opts, @@ -64,7 +131,9 @@ def add( "zlevel": z_level, "z": z, "tooltip": tooltip_opts, + "label": label_opts, "emphasis": emphasis_opts, + "itemPayload": item_payload_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/effectscatter.py b/pyecharts/charts/basic_charts/effectscatter.py index 3ce1015ad..5b57901aa 100644 --- a/pyecharts/charts/basic_charts/effectscatter.py +++ b/pyecharts/charts/basic_charts/effectscatter.py @@ -17,16 +17,25 @@ def add_yaxis( y_axis: types.Sequence[types.Union[opts.EffectScatterItem, dict]], *, xaxis_index: types.Optional[types.Numeric] = None, + xaxis_id: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, + yaxis_id: types.Optional[types.Numeric] = None, polar_index: types.Optional[types.Numeric] = None, + polar_id: types.Optional[types.Numeric] = None, geo_index: types.Optional[types.Numeric] = None, + geo_id: types.Optional[types.Numeric] = None, calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: 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", + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, symbol: types.Optional[str] = None, symbol_size: types.Numeric = 10, symbol_rotate: types.Optional[types.Numeric] = None, @@ -56,15 +65,24 @@ def add_yaxis( "type": ChartType.EFFECT_SCATTER, "name": series_name, "xAxisIndex": xaxis_index, + "xAxisId": xaxis_id, "yAxisIndex": yaxis_index, + "yAxisId": yaxis_id, "polarIndex": polar_index, + "polarId": polar_id, "geoIndex": geo_index, + "geoId": geo_id, "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "colorBy": color_by, "legendHoverLink": is_legend_hover_link, "showEffectOn": show_effect_on, "rippleEffect": effect_opts, "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "datasetIndex": dataset_index, "symbol": symbol, "symbolSize": symbol_size, diff --git a/pyecharts/charts/basic_charts/funnel.py b/pyecharts/charts/basic_charts/funnel.py index 7dbf81cb5..454cc2259 100644 --- a/pyecharts/charts/basic_charts/funnel.py +++ b/pyecharts/charts/basic_charts/funnel.py @@ -22,6 +22,13 @@ def add( *, color: types.Optional[str] = None, color_by: types.Optional[str] = None, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, min_: types.Numeric = 0, max_: types.Numeric = 100, min_size: types.Union[str, types.Numeric] = "0%", @@ -69,6 +76,13 @@ def add( "name": series_name, "data": data, "colorBy": color_by, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "min": min_, "max": max_, "minSize": min_size, diff --git a/pyecharts/charts/basic_charts/gauge.py b/pyecharts/charts/basic_charts/gauge.py index 40c3651a1..581aa1141 100644 --- a/pyecharts/charts/basic_charts/gauge.py +++ b/pyecharts/charts/basic_charts/gauge.py @@ -21,6 +21,13 @@ def add( split_number: types.Numeric = 10, center: types.Sequence = None, radius: types.Union[types.Numeric, str] = "75%", + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, start_angle: types.Numeric = 225, end_angle: types.Numeric = -45, is_clock_wise: bool = True, @@ -56,6 +63,13 @@ def add( "splitNumber": split_number, "center": center, "radius": radius, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "startAngle": start_angle, "endAngle": end_angle, "clockwise": is_clock_wise, diff --git a/pyecharts/charts/basic_charts/geo.py b/pyecharts/charts/basic_charts/geo.py index 8e3199b29..2f84e92a5 100644 --- a/pyecharts/charts/basic_charts/geo.py +++ b/pyecharts/charts/basic_charts/geo.py @@ -268,7 +268,9 @@ def _feed_data(self, data_pair: types.Sequence, type_: str) -> types.Sequence: def add_schema( self, maptype: str = "china", + animation: types.Union[bool, str] = None, is_roam: bool = True, + roam_trigger: types.Optional[str] = None, zoom: types.Optional[types.Numeric] = None, center: types.Optional[types.Sequence] = None, aspect_scale: types.Numeric = 0.75, @@ -286,6 +288,21 @@ def add_schema( regions_opts: types.Union[ types.Sequence[types.GeoRegions], types.Sequence[dict] ] = None, + is_preserve_aspect: bool = False, + preserve_aspect_align: types.Optional[str] = None, + preserve_aspect_vertical_align: types.Optional[str] = None, + is_clip: bool = False, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Optional[types.Union[ + types.Sequence, types.Numeric, str] + ] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, + tooltip_opts: types.Tooltip = None, + select_opts: types.Select = None, ): self.js_dependencies.add(maptype) self._geo_json_name = maptype @@ -302,9 +319,11 @@ def add_schema( self.options.update( geo={ "map": maptype, + "animation": animation, "zoom": zoom, "center": center, "roam": is_roam, + "roamTrigger": roam_trigger, "aspectScale": aspect_scale, "boundingCoords": bounding_coords, "scaleLimit": scale_limit, @@ -319,6 +338,19 @@ def add_schema( "label": emphasis_label_opts, }, "regions": regions_opts, + "preserveAspect": is_preserve_aspect, + "preserveAspectAlign": preserve_aspect_align, + "preserveAspectVerticalAlign": preserve_aspect_vertical_align, + "clip": is_clip, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, + "tooltip": tooltip_opts, + "select": select_opts, } ) return self diff --git a/pyecharts/charts/basic_charts/graph.py b/pyecharts/charts/basic_charts/graph.py index bb8b5397e..03bf375fc 100644 --- a/pyecharts/charts/basic_charts/graph.py +++ b/pyecharts/charts/basic_charts/graph.py @@ -18,8 +18,27 @@ def add( links: types.Sequence[types.GraphLink], categories: types.Union[types.Sequence[types.GraphCategory], None] = None, *, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + xaxis_index: types.Optional[types.Numeric] = None, + xaxis_id: types.Optional[types.Numeric] = None, + yaxis_index: types.Optional[types.Numeric] = None, + yaxis_id: types.Optional[types.Numeric] = None, + polar_index: types.Optional[types.Numeric] = None, + polar_id: types.Optional[types.Numeric] = None, + single_axis_index: types.Optional[types.Numeric] = None, + single_axis_id: types.Optional[types.Numeric] = None, + geo_index: types.Optional[types.Numeric] = None, + geo_id: types.Optional[types.Numeric] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, is_focusnode: bool = True, is_roam: bool = True, + roam_trigger: types.Optional[str] = None, + node_scale_ratio: types.Numeric = 0.6, is_draggable: bool = False, is_rotate_label: bool = False, layout: str = "force", @@ -38,6 +57,9 @@ def add( tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, emphasis_opts: types.Emphasis = None, + is_preserve_aspect: bool = False, + preserve_aspect_align: types.Optional[str] = None, + preserve_aspect_vertical_align: types.Optional[str] = None, ): _nodes = [] for n in nodes: @@ -67,6 +89,23 @@ def add( { "type": ChartType.GRAPH, "name": series_name, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "xaxisIndex": xaxis_index, + "xaxisId": xaxis_id, + "yaxisIndex": yaxis_index, + "yaxisId": yaxis_id, + "polarIndex": polar_index, + "polarId": polar_id, + "singleAxisIndex": single_axis_index, + "singleAxisId": single_axis_id, + "geoIndex": geo_index, + "geoId": geo_id, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "layout": layout, "symbol": symbol, "symbolSize": symbol_size, @@ -81,6 +120,8 @@ def add( "label": label_opts, "lineStyle": linestyle_opts, "roam": is_roam, + "roamTrigger": roam_trigger, + "nodeScaleRatio": node_scale_ratio, "draggable": is_draggable, "focusNodeAdjacency": is_focusnode, "data": _nodes, @@ -92,6 +133,9 @@ def add( "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, "emphasis": emphasis_opts, + "preserveAspect": is_preserve_aspect, + "preserveAspectAlign": preserve_aspect_align, + "preserveAspectVerticalAlign": preserve_aspect_vertical_align, } ) return self diff --git a/pyecharts/charts/basic_charts/heatmap.py b/pyecharts/charts/basic_charts/heatmap.py index ec68c19ea..2b47fb42f 100644 --- a/pyecharts/charts/basic_charts/heatmap.py +++ b/pyecharts/charts/basic_charts/heatmap.py @@ -28,10 +28,18 @@ def add_yaxis( value: types.Sequence[types.Union[dict]], *, coordinate_system: str = "cartesian2d", + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, xaxis_index: types.Optional[types.Numeric] = None, + xaxis_id: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, + yaxis_id: types.Optional[types.Numeric] = None, geo_index: types.Optional[types.Numeric] = None, + geo_id: types.Optional[types.Numeric] = None, calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: 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, @@ -56,10 +64,18 @@ def add_yaxis( "type": ChartType.HEATMAP, "name": series_name, "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "xAxisIndex": xaxis_index, + "xAxisId": xaxis_id, "yAxisIndex": yaxis_index, + "yAxisId": yaxis_id, "geoIndex": geo_index, + "geoId": geo_id, "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "datasetIndex": dataset_index, "pointSize": point_size, "blurSize": blur_size, diff --git a/pyecharts/charts/basic_charts/kline.py b/pyecharts/charts/basic_charts/kline.py index 0750f3e4e..d89dd6146 100644 --- a/pyecharts/charts/basic_charts/kline.py +++ b/pyecharts/charts/basic_charts/kline.py @@ -31,13 +31,17 @@ def add_yaxis( y_axis: types.Sequence[types.Union[opts.CandleStickItem, dict]], *, coordinate_system: str = "cartesian2d", + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, 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, + xaxis_id: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, + yaxis_id: types.Optional[types.Numeric] = None, is_legend_hover_link: bool = True, is_hover_animation: bool = True, markline_opts: types.MarkLine = None, @@ -59,6 +63,8 @@ def add_yaxis( "type": ChartType.KLINE, "name": series_name, "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "colorBy": color_by, "legendHoverLink": is_legend_hover_link, "hoverAnimation": is_hover_animation, @@ -67,7 +73,9 @@ def add_yaxis( "barMinWidth": bar_min_width, "barMaxWidth": bar_max_width, "xAxisIndex": xaxis_index, + "xAxisId": xaxis_id, "yAxisIndex": yaxis_index, + "yAxisId": yaxis_id, "data": y_axis, "markPoint": markpoint_opts, "markLine": markline_opts, diff --git a/pyecharts/charts/basic_charts/line.py b/pyecharts/charts/basic_charts/line.py index e36abc715..2613d6681 100644 --- a/pyecharts/charts/basic_charts/line.py +++ b/pyecharts/charts/basic_charts/line.py @@ -19,10 +19,15 @@ def add_yaxis( *, is_connect_nones: bool = False, xaxis_index: types.Optional[types.Numeric] = None, + xaxis_id: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, + yaxis_id: types.Optional[types.Numeric] = None, polar_index: types.Optional[types.Numeric] = None, + polar_id: types.Optional[types.Numeric] = None, dataset_index: types.Optional[types.Numeric] = None, coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, color_by: types.Optional[str] = None, color: types.Optional[str] = None, is_symbol_show: bool = True, @@ -30,6 +35,7 @@ def add_yaxis( symbol_size: types.Union[types.Numeric, types.Sequence] = 4, stack: types.Optional[str] = None, stack_strategy: types.Optional[str] = "samesign", + stack_order: types.Optional[str] = None, is_smooth: bool = False, is_clip: bool = True, is_step: bool = False, @@ -78,10 +84,15 @@ def add_yaxis( "name": series_name, "connectNulls": is_connect_nones, "xAxisIndex": xaxis_index, + "xAxisId": xaxis_id, "yAxisIndex": yaxis_index, + "yAxisId": yaxis_id, "polarIndex": polar_index, + "polarId": polar_id, "datasetIndex": dataset_index, "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "colorBy": color_by, "symbol": symbol, "symbolSize": symbol_size, @@ -91,6 +102,7 @@ def add_yaxis( "step": is_step, "stack": stack, "stackStrategy": stack_strategy, + "stackOrder": stack_order, "data": data, "hoverAnimation": is_hover_animation, "label": label_opts, diff --git a/pyecharts/charts/basic_charts/map.py b/pyecharts/charts/basic_charts/map.py index f417dbd24..678e9ba40 100644 --- a/pyecharts/charts/basic_charts/map.py +++ b/pyecharts/charts/basic_charts/map.py @@ -37,10 +37,18 @@ def add( 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, + geo_id: 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, + is_preserve_aspect: bool = False, + preserve_aspect_align: types.Optional[str] = None, + preserve_aspect_vertical_align: types.Optional[str] = None, + is_clip: bool = False, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, label_opts: types.Label = opts.LabelOpts(), tooltip_opts: types.Tooltip = None, itemstyle_opts: types.ItemStyle = None, @@ -87,12 +95,20 @@ def add( "right": pos_right, "bottom": pos_bottom, "geoIndex": geo_index, + "geoId": geo_id, "seriesLayoutBy": series_layout_by, "datasetIndex": dataset_index, "mapValueCalculation": map_value_calculation, "showLegendSymbol": is_map_symbol_show, "layoutCenter": layout_center, "layoutSize": layout_size, + "preserveAspect": is_preserve_aspect, + "preserveAspectAlign": preserve_aspect_align, + "preserveAspectVerticalAlign": preserve_aspect_vertical_align, + "clip": is_clip, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, "emphasis": { diff --git a/pyecharts/charts/basic_charts/parallel.py b/pyecharts/charts/basic_charts/parallel.py index 8450c8a44..3138ad106 100644 --- a/pyecharts/charts/basic_charts/parallel.py +++ b/pyecharts/charts/basic_charts/parallel.py @@ -43,7 +43,11 @@ def add( series_name: str, data: types.Sequence[types.Union[opts.ParallelItem, dict]], *, + coordinate_system: types.Optional[str] = "parallel", + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, parallel_index: types.Optional[types.Numeric] = None, + parallel_id: 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, @@ -60,8 +64,11 @@ def add( self.options.get("series").append( { "type": ChartType.PARALLEL, - "coordinateSystem": "parallel", + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "parallelIndex": parallel_index, + "parallelId": parallel_id, "colorBy": color_by, "inactiveOpacity": inactive_opacity, "activeOpacity": active_opacity, diff --git a/pyecharts/charts/basic_charts/pictorialbar.py b/pyecharts/charts/basic_charts/pictorialbar.py index d7c493499..94c221d65 100644 --- a/pyecharts/charts/basic_charts/pictorialbar.py +++ b/pyecharts/charts/basic_charts/pictorialbar.py @@ -26,8 +26,13 @@ def add_yaxis( symbol_repeat_direction: types.Optional[str] = None, symbol_margin: types.Union[types.Numeric, str, None] = None, is_symbol_clip: bool = False, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, xaxis_index: types.Optional[types.Numeric] = None, + xaxis_id: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, + yaxis_id: types.Optional[types.Numeric] = None, color: types.Optional[str] = None, category_gap: types.Union[types.Numeric, str] = "20%", gap: types.Optional[str] = None, @@ -54,8 +59,13 @@ def add_yaxis( "symbolMargin": symbol_margin, "symbolClip": is_symbol_clip, "name": series_name, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "xAxisIndex": xaxis_index, + "xAxisId": xaxis_id, "yAxisIndex": yaxis_index, + "yAxisId": yaxis_id, "data": y_axis, "barCategoryGap": category_gap, "barGap": gap, diff --git a/pyecharts/charts/basic_charts/pie.py b/pyecharts/charts/basic_charts/pie.py index f39f73caf..87dd561b0 100644 --- a/pyecharts/charts/basic_charts/pie.py +++ b/pyecharts/charts/basic_charts/pie.py @@ -21,6 +21,15 @@ def add( color: types.Optional[str] = None, color_by: types.Optional[str] = "data", is_legend_hover_link: bool = True, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + geo_index: types.Optional[types.Numeric] = None, + geo_id: types.Optional[types.Numeric] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, selected_mode: types.Union[str, bool] = False, selected_offset: types.Numeric = 10, radius: types.Optional[types.Sequence] = None, @@ -28,6 +37,7 @@ def add( rosetype: types.Union[str, bool] = None, is_clockwise: bool = True, start_angle: types.Numeric = 90, + end_angle: types.Optional[types.Numeric] = None, min_angle: types.Numeric = 0, min_show_label_angle: types.Numeric = 0, is_avoid_label_overlap: bool = True, @@ -75,10 +85,20 @@ def add( "name": series_name, "colorBy": color_by, "legendHoverLink": is_legend_hover_link, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "geoIndex": geo_index, + "geoId": geo_id, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "selectedMode": selected_mode, "selectedOffset": selected_offset, "clockwise": is_clockwise, "startAngle": start_angle, + "endAngle": end_angle, "minAngle": min_angle, "minShowLabelAngle": min_show_label_angle, "avoidLabelOverlap": is_avoid_label_overlap, diff --git a/pyecharts/charts/basic_charts/polar.py b/pyecharts/charts/basic_charts/polar.py index 7d9c48d72..c0b34185c 100644 --- a/pyecharts/charts/basic_charts/polar.py +++ b/pyecharts/charts/basic_charts/polar.py @@ -42,6 +42,15 @@ def add( symbol_size: types.Numeric = 4, stack: types.Optional[str] = None, center: types.Optional[types.Sequence] = None, + z_level: types.Numeric = 0, + z: types.Numeric = 2, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, label_opts: types.Label = opts.LabelOpts(is_show=False), areastyle_opts: types.AreaStyle = opts.AreaStyleOpts(), effect_opts: types.Effect = opts.EffectOpts(), @@ -50,8 +59,21 @@ def add( emphasis_opts: types.Emphasis = None, ): self._append_legend(series_name) - self.options.update(polar={"center": center if center else ["50%", "50%"]}) + self.options.update(polar={ + "center": center if center else ["50%", "50%"], + "zlevel": z_level, + "z": z, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, + "tooltip": tooltip_opts, + }) + # polar with other series config if type_ in (ChartType.SCATTER, ChartType.LINE, ChartType.BAR): self.options.get("series").append( { diff --git a/pyecharts/charts/basic_charts/radar.py b/pyecharts/charts/basic_charts/radar.py index a671387db..8bd3ca772 100644 --- a/pyecharts/charts/basic_charts/radar.py +++ b/pyecharts/charts/basic_charts/radar.py @@ -17,6 +17,13 @@ def add_schema( shape: types.Optional[str] = None, center: types.Optional[types.Sequence] = None, radius: types.Optional[types.Union[types.Sequence, str]] = None, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, start_angle: types.Numeric = 90, textstyle_opts: types.TextStyle = opts.TextStyleOpts(), splitline_opt: types.SplitLine = opts.SplitLineOpts(is_show=True), @@ -31,7 +38,9 @@ def add_schema( polar_opts: types.Polar = None, ): self.options.update( - radiusAxis=radiusaxis_opts, angleAxis=angleaxis_opts, polar=polar_opts + radiusAxis=radiusaxis_opts, + angleAxis=angleaxis_opts, + polar=polar_opts, ) indicators = [] @@ -49,6 +58,13 @@ def add_schema( "shape": shape, "center": center, "radius": radius, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "startAngle": start_angle, "name": {"textStyle": textstyle_opts}, "splitLine": splitline_opt, diff --git a/pyecharts/charts/basic_charts/sankey.py b/pyecharts/charts/basic_charts/sankey.py index cbdd6b967..e0c8f628c 100644 --- a/pyecharts/charts/basic_charts/sankey.py +++ b/pyecharts/charts/basic_charts/sankey.py @@ -23,12 +23,25 @@ def add( pos_top: types.Union[str, types.Numeric] = "5%", pos_right: types.Union[str, types.Numeric] = "20%", pos_bottom: types.Union[str, types.Numeric] = "5%", + width: types.Union[str, types.Numeric] = None, + height: types.Union[str, types.Numeric] = None, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, 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, + center: types.Optional[types.Sequence] = None, + zoom: types.Numeric = 1, + is_roam: bool = False, + roam_trigger: types.Optional[str] = None, edge_label_opt: types.Label = None, levels: types.SankeyLevel = None, label_opts: types.Label = opts.LabelOpts(), @@ -48,12 +61,25 @@ def add( "top": pos_top, "right": pos_right, "bottom": pos_bottom, + "width": width, + "height": height, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "nodeWidth": node_width, "nodeGap": node_gap, "nodeAlign": node_align, "layoutIterations": layout_iterations, "orient": orient, "draggable": is_draggable, + "center": center, + "zoom": zoom, + "roam": is_roam, + "roamTrigger": roam_trigger, "edgeLabel": edge_label_opt, "levels": levels, "label": label_opts, diff --git a/pyecharts/charts/basic_charts/scatter.py b/pyecharts/charts/basic_charts/scatter.py index d4cbd3210..7ac26f591 100644 --- a/pyecharts/charts/basic_charts/scatter.py +++ b/pyecharts/charts/basic_charts/scatter.py @@ -38,13 +38,30 @@ def add_yaxis( series_name: str, y_axis: types.Sequence[types.Union[opts.ScatterItem, dict]], *, + color_by: types.Optional[str] = None, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, xaxis_index: types.Optional[types.Numeric] = None, + xaxis_id: types.Optional[types.Numeric] = None, yaxis_index: types.Optional[types.Numeric] = None, + yaxis_id: types.Optional[types.Numeric] = None, + polar_index: types.Optional[types.Numeric] = None, + polar_id: types.Optional[types.Numeric] = None, + single_axis_index: types.Optional[types.Numeric] = None, + single_axis_id: types.Optional[types.Numeric] = None, + geo_index: types.Optional[types.Numeric] = None, + geo_id: types.Optional[types.Numeric] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: 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"), + is_silent: bool = False, markpoint_opts: types.MarkPoint = None, markline_opts: types.MarkLine = None, markarea_opts: types.MarkArea = None, @@ -62,13 +79,30 @@ def add_yaxis( { "type": ChartType.SCATTER, "name": series_name, + "colorBy": color_by, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, "xAxisIndex": xaxis_index, + "xAxisId": xaxis_id, "yAxisIndex": yaxis_index, + "yAxisId": yaxis_id, + "polarIndex": polar_index, + "polarId": polar_id, + "singleAxisIndex": single_axis_index, + "singleAxisId": single_axis_id, + "geoIndex": geo_index, + "geoId": geo_id, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "symbol": symbol, "symbolSize": symbol_size, "symbolRotate": symbol_rotate, "data": data, "label": label_opts, + "silent": is_silent, "markPoint": markpoint_opts, "markLine": markline_opts, "markArea": markarea_opts, diff --git a/pyecharts/charts/basic_charts/sunburst.py b/pyecharts/charts/basic_charts/sunburst.py index afbe14d6b..15ad5c5d0 100644 --- a/pyecharts/charts/basic_charts/sunburst.py +++ b/pyecharts/charts/basic_charts/sunburst.py @@ -21,6 +21,13 @@ def add( *, center: types.Optional[types.Sequence] = None, radius: types.Optional[types.Sequence] = None, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, highlight_policy: str = "descendant", node_click: str = "rootToNode", sort_: types.Optional[types.JSFunc] = "desc", @@ -47,6 +54,13 @@ def add( "data": data_pair, "center": center, "radius": radius, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "highlightPolicy": highlight_policy, "nodeClick": node_click, "sort": sort_, diff --git a/pyecharts/charts/basic_charts/themeriver.py b/pyecharts/charts/basic_charts/themeriver.py index fc34e4cf4..b5ae258da 100644 --- a/pyecharts/charts/basic_charts/themeriver.py +++ b/pyecharts/charts/basic_charts/themeriver.py @@ -18,6 +18,19 @@ def add( series_name: types.Sequence, data: types.Sequence[types.Union[opts.ThemeRiverItem, dict]], *, + color_by: types.Optional[str] = None, + pos_left: types.Union[str, types.Numeric] = "5%", + pos_top: types.Union[str, types.Numeric] = "5%", + pos_right: types.Union[str, types.Numeric] = "5%", + pos_bottom: types.Union[str, types.Numeric] = "5%", + width: types.Union[str, types.Numeric] = None, + height: types.Union[str, types.Numeric] = None, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + single_axis_index: types.Optional[types.Numeric] = None, + single_axis_id: types.Optional[types.Numeric] = None, + boundary_gap: types.Optional[types.Sequence] = None, label_opts: types.Label = opts.LabelOpts(), singleaxis_opts: types.SingleAxis = opts.SingleAxisOpts(), tooltip_opts: types.Tooltip = None, @@ -32,6 +45,19 @@ def add( "type": ChartType.THEMERIVER, "name": series_name, "data": data, + "colorBy": color_by, + "left": pos_left, + "top": pos_top, + "right": pos_right, + "bottom": pos_bottom, + "width": width, + "height": height, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "singleAxisIndex": single_axis_index, + "singleAxisId": single_axis_id, + "boundaryGap": boundary_gap, "label": label_opts, "tooltip": tooltip_opts, "itemStyle": itemstyle_opts, diff --git a/pyecharts/charts/basic_charts/tree.py b/pyecharts/charts/basic_charts/tree.py index 787546632..34c006618 100644 --- a/pyecharts/charts/basic_charts/tree.py +++ b/pyecharts/charts/basic_charts/tree.py @@ -48,11 +48,19 @@ def add( 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, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = 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, + roam_trigger: types.Optional[str] = None, is_expand_and_collapse: bool = True, initial_tree_depth: types.Optional[types.Numeric] = None, label_opts: types.Label = opts.LabelOpts(), @@ -76,6 +84,13 @@ def add( "bottom": pos_bottom, "width": width, "height": height, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "center": center, "zoom": zoom, "symbol": symbol, @@ -83,6 +98,7 @@ def add( "edgeShape": edge_shape, "edgeForkPosition": edge_fork_position, "roam": is_roam, + "roamTrigger": roam_trigger, "expandAndCollapse": is_expand_and_collapse, "initialTreeDepth": initial_tree_depth, "layout": layout, diff --git a/pyecharts/charts/basic_charts/treemap.py b/pyecharts/charts/basic_charts/treemap.py index 1f1a79aa0..27395d5af 100644 --- a/pyecharts/charts/basic_charts/treemap.py +++ b/pyecharts/charts/basic_charts/treemap.py @@ -24,6 +24,13 @@ def add( pos_bottom: types.Optional[str] = None, width: types.Union[str, types.Numeric] = "80%", height: types.Union[str, types.Numeric] = "80%", + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Union[types.Sequence, types.Numeric, str] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, square_ratio: types.Optional[types.JSFunc] = None, drilldown_icon: str = "▶", roam: types.Union[bool, str] = True, @@ -56,6 +63,13 @@ def add( "top": pos_top, "width": width, "height": height, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "bottom": pos_bottom, "squareRatio": square_ratio, "label": label_opts, diff --git a/pyecharts/charts/chart.py b/pyecharts/charts/chart.py index fd8cb816e..f83071601 100644 --- a/pyecharts/charts/chart.py +++ b/pyecharts/charts/chart.py @@ -16,13 +16,6 @@ def __init__( 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()}], @@ -112,12 +105,10 @@ def _append_legend(self, 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) + if self.options.get("color"): + self.options.get("color").append(color) + else: + self.options.update(color=[color]) def set_global_opts( self, @@ -132,11 +123,14 @@ def set_global_opts( datazoom_opts: types.DataZoom = None, graphic_opts: types.Graphic = None, axispointer_opts: types.AxisPointer = None, + matrix_opts: types.Matrix = None, + thumbnail_opts: types.Thumbnail = 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, @@ -145,6 +139,8 @@ def set_global_opts( dataZoom=datazoom_opts, graphic=graphic_opts, axisPointer=axispointer_opts, + matrix=matrix_opts, + thumbnail=thumbnail_opts, ) if brush_opts is not None: @@ -253,8 +249,13 @@ def overlap(self, chart: Base): ) 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) + chart_colors = chart.options.get("color") + if self.options.get("color") is None: + if chart_colors: + self.options.update(color=chart_colors) + else: + if chart_colors: + self.options.get("color").extend(chart_colors) return self diff --git a/pyecharts/charts/composite_charts/grid.py b/pyecharts/charts/composite_charts/grid.py index 9f80cc653..4a220df9c 100644 --- a/pyecharts/charts/composite_charts/grid.py +++ b/pyecharts/charts/composite_charts/grid.py @@ -94,7 +94,11 @@ def add( self.js_dependencies.add(dep) if chart.options.get("geo") is not None: - self.options.update(geo=chart.options.get("geo")) + _grid_geo_option = self.options.get("geo") + if _grid_geo_option is None or isinstance(_grid_geo_option, dict): + self.options.update(geo=[chart.options.get("geo")]) + else: + _grid_geo_option.append(chart.options.get("geo")) if isinstance(chart, RectChart): if grid_index is None: diff --git a/pyecharts/charts/composite_charts/timeline.py b/pyecharts/charts/composite_charts/timeline.py index 0434122c4..648c8d4d1 100644 --- a/pyecharts/charts/composite_charts/timeline.py +++ b/pyecharts/charts/composite_charts/timeline.py @@ -49,6 +49,15 @@ def add_schema( progress_linestyle_opts: types.LineStyle = None, progress_itemstyle_opts: types.ItemStyle = None, progress_label_opts: types.Label = None, + coordinate_system: types.Optional[str] = None, + coordinate_system_usage: types.Optional[str] = None, + coord: types.Optional[types.Union[ + Sequence, types.Numeric, str] + ] = None, + calendar_index: types.Optional[types.Numeric] = None, + calendar_id: types.Optional[types.Numeric] = None, + matrix_index: types.Optional[types.Numeric] = None, + matrix_id: types.Optional[types.Numeric] = None, ): self.options.get("baseOption").get("timeline").update( { @@ -81,6 +90,13 @@ def add_schema( "itemStyle": progress_itemstyle_opts, "label": progress_label_opts, }, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, } ) return self diff --git a/pyecharts/datasets/map_filename.json b/pyecharts/datasets/map_filename.json index 0237c8882..37b63015f 100644 --- a/pyecharts/datasets/map_filename.json +++ b/pyecharts/datasets/map_filename.json @@ -13,6 +13,13 @@ "echarts-liquidfill": ["echarts-liquidfill.min", "js"], "echarts-wordcloud": ["echarts-wordcloud.min", "js"], "echarts-stat": ["ecStat.min", "js"], + "echarts-x-violin": ["echarts-x/violin/index.auto.min", "js"], + "echarts-x-stage": ["echarts-x/stage/index.auto.min", "js"], + "echarts-x-contour-d3": ["echarts-x/contour/d3.min", "js"], + "echarts-x-contour": ["echarts-x/contour/index.auto.min", "js"], + "echarts-x-segmented-doughnut": ["echarts-x/segmented-doughnut/index.auto.min", "js"], + "echarts-x-line-range": ["echarts-x/line-range/index.auto.min", "js"], + "echarts-x-bar-range": ["echarts-x/bar-range/index.auto.min", "js"], "bmap": ["bmap.min", "js"], "chalk": ["themes/chalk", "js"], "essos": ["themes/essos", "js"], diff --git a/pyecharts/globals.py b/pyecharts/globals.py index 52e41aa86..1d2d18e69 100644 --- a/pyecharts/globals.py +++ b/pyecharts/globals.py @@ -34,6 +34,7 @@ class _ChartType: BAR: str = "bar" BAR3D: str = "bar3D" BOXPLOT: str = "boxplot" + CHORD: str = "chord" EFFECT_SCATTER: str = "effectScatter" FUNNEL: str = "funnel" FLOWGL: str = "flowGL" @@ -67,6 +68,13 @@ class _ChartType: TREEMAP: str = "treemap" WORDCLOUD: str = "wordCloud" CUSTOM: str = "custom" + # below chart types are Echarts 6 new custom chart + VIOLIN: str = "violin" + STAGE: str = "stage" + DOUGHNUT: str = "segmentedDoughnut" + CONTOUR: str = "contour" + BAR_RANGE: str = "barRange" + LINE_RANGE: str = "lineRange" ToolTipFormatterType = { @@ -133,7 +141,7 @@ class _NotebookType: class _OnlineHost: - DEFAULT_HOST = "https://assets.pyecharts.org/assets/v5/" + DEFAULT_HOST = "https://assets.pyecharts.org/assets/v6/" NOTEBOOK_HOST = "http://localhost:8888/nbextensions/assets/" diff --git a/pyecharts/options/__init__.py b/pyecharts/options/__init__.py index 464b73303..704104fcf 100644 --- a/pyecharts/options/__init__.py +++ b/pyecharts/options/__init__.py @@ -11,7 +11,15 @@ BMapTypeControlOpts, BoxplotItem, CandleStickItem, + ChordData, + ChordLink, ComponentTitleOpts, + CustomBarRangeItemPayloadOpts, + CustomContourItemPayloadOpts, + CustomLineRangeItemPayloadOpts, + CustomSegmentedDoughnutItemPayloadOpts, + CustomStageItemPayloadOpts, + CustomViolinItemPayloadOpts, EffectScatterItem, FunnelItem, GaugeDetailOpts, @@ -72,6 +80,9 @@ AriaLabelOpts, AriaOpts, Axis3DOpts, + AxisBreakOpts, + AxisBreakAreaOpts, + AxisBreakLabelLayoutOpts, AxisLineOpts, AxisOpts, AxisPointerOpts, @@ -88,9 +99,17 @@ Emphasis3DOpts, Grid3DOpts, GridOpts, + GridOuterOpts, InitOpts, RenderOpts, LegendOpts, + MatrixAxisOpts, + MatrixAxisDataOpts, + MatrixBackgroundStyleOpts, + MatrixBodyDataOpts, + MatrixBodyOrCornerOpts, + MatrixDividerLineStyleOpts, + MatrixOpts, ParallelAxisOpts, ParallelOpts, PolarOpts, @@ -99,6 +118,8 @@ RadiusAxisOpts, SelectOpts, SingleAxisOpts, + ThumbnailOpts, + ThumbnailWindowStyleOpts, TitleOpts, ToolBoxFeatureBrushOpts, ToolBoxFeatureDataViewOpts, diff --git a/pyecharts/options/charts_options.py b/pyecharts/options/charts_options.py index c7857441d..17099cf78 100644 --- a/pyecharts/options/charts_options.py +++ b/pyecharts/options/charts_options.py @@ -18,6 +18,30 @@ # Chart Options +class ChordData(BasicOpts): + def __init__( + self, + name: Optional[str] = None, + ): + self.opts: dict = { + "name": name, + } + + +class ChordLink(BasicOpts): + def __init__( + self, + source: Union[str, int, None] = None, + target: Union[str, int, None] = None, + value: Optional[Numeric] = None, + ): + self.opts: dict = { + "source": source, + "target": target, + "value": value, + } + + class GraphNode(BasicOpts): def __init__( self, @@ -398,6 +422,7 @@ def __init__( pos_y: Numeric = 0, font: Optional[str] = None, font_size: Optional[Numeric] = 0, + font_weight: Optional[str] = None, text_align: str = "left", text_vertical_align: Optional[str] = None, graphic_basicstyle_opts: Union[GraphicBasicStyleOpts, dict, None] = None, @@ -408,6 +433,7 @@ def __init__( "y": pos_y, "font": font, "fontSize": font_size, + "fontWeight": font_weight, "textAlign": text_align, "textVerticalAlign": text_vertical_align, } @@ -424,7 +450,7 @@ def __init__( self, id_: Optional[str] = None, action: str = "merge", - position: [Sequence, Numeric, None] = None, + position: Union[Sequence, Numeric, None] = None, rotation: Union[Numeric, JSFunc, None] = 0, scale: Union[Sequence, Numeric, None] = None, origin: Union[Numeric, Sequence, None] = None, @@ -1595,7 +1621,7 @@ class ScatterItem(BasicOpts): def __init__( self, name: Union[str, Numeric] = None, - value: Union[str, Numeric] = None, + value: Union[str, Numeric, Sequence] = None, symbol: Optional[str] = None, symbol_size: Union[Sequence[Numeric], Numeric] = None, symbol_rotate: Optional[Numeric] = None, @@ -1665,3 +1691,85 @@ def __init__( "children": children, "label": label_opts, } + + +class CustomBarRangeItemPayloadOpts(BasicOpts): + def __init__( + self, + bar_width: Union[Numeric, str, None] = None, + border_radius: Optional[Numeric] = None, + margin: Optional[Numeric] = None, + ): + self.opts: dict = { + "barWidth": bar_width, + "borderRadius": border_radius, + "margin": margin, + } + + +class CustomContourItemPayloadOpts(BasicOpts): + def __init__( + self, + itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, + linestyle_opts: Union[LineStyleOpts, dict, None] = None, + bandwidth: Optional[Numeric] = None, + ): + self.opts: dict = { + "itemStyle": itemstyle_opts, + "lineStyle": linestyle_opts, + "bandwidth": bandwidth, + } + + +class CustomLineRangeItemPayloadOpts(BasicOpts): + def __init__( + self, + linestyle_opts: Union[LineStyleOpts, dict, None] = None, + areastyle_opts: Union[AreaStyleOpts, dict, None] = None, + ): + self.opts: dict = { + "areaStyle": areastyle_opts, + "lineStyle": linestyle_opts, + } + + +class CustomSegmentedDoughnutItemPayloadOpts(BasicOpts): + def __init__( + self, + center: Optional[Sequence] = None, + radius: Optional[Sequence] = None, + segment_count: Optional[Numeric] = None, + label_opts: Union[LabelOpts, dict, None] = None, + ): + self.opts: dict = { + "center": center, + "radius": radius, + "segmentCount": segment_count, + "label": label_opts, + } + + +class CustomStageItemPayloadOpts(BasicOpts): + def __init__( + self, + itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, + ): + self.opts: dict = { + "itemStyle": itemstyle_opts, + } + + +class CustomViolinItemPayloadOpts(BasicOpts): + def __init__( + self, + symbol_size: Optional[Numeric] = None, + area_opacity: Optional[Numeric] = None, + bandwidth_scale: Optional[Numeric] = None, + bin_count: Optional[Numeric] = None, + ): + self.opts: dict = { + "symbolSize": symbol_size, + "areaOpacity": area_opacity, + "bandWidthScale": bandwidth_scale, + "binCount": bin_count, + } diff --git a/pyecharts/options/global_options.py b/pyecharts/options/global_options.py index 3984cede2..aa9eed2be 100644 --- a/pyecharts/options/global_options.py +++ b/pyecharts/options/global_options.py @@ -166,6 +166,60 @@ def __init__(self, is_embed_js: bool = False): } +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, + is_display_transition: bool = True, + 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: Union[Numeric, Sequence[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, + "displayTransition": is_display_transition, + "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 ToolBoxFeatureSaveAsImageOpts(BasicOpts): def __init__( self, @@ -371,6 +425,18 @@ def __init__( pos_top: Optional[str] = None, pos_bottom: Optional[str] = None, feature: Union[ToolBoxFeatureOpts, dict] = ToolBoxFeatureOpts(), + z_level: Optional[Numeric] = None, + z: Optional[Numeric] = None, + width: Optional[str] = None, + height: Optional[str] = None, + coordinate_system: Optional[str] = None, + coordinate_system_usage: Optional[str] = None, + coord: Optional[Union[Sequence, Numeric, str]] = None, + calendar_index: Optional[Numeric] = None, + calendar_id: Optional[Numeric] = None, + matrix_index: Optional[Numeric] = None, + matrix_id: Optional[Numeric] = None, + tooltip_opts: Union[TooltipOpts, dict, None] = None, ): self.opts: dict = { "show": is_show, @@ -382,6 +448,18 @@ def __init__( "top": pos_top, "bottom": pos_bottom, "feature": feature, + "zlevel": z_level, + "z": z, + "width": width, + "height": height, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, + "tooltip": tooltip_opts, } @@ -445,10 +523,17 @@ def __init__( 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, + pos_left: Union[str, Numeric] = None, + pos_right: Union[str, Numeric] = None, + pos_top: Union[str, Numeric] = None, + pos_bottom: Union[str, Numeric] = None, + coordinate_system: Optional[str] = None, + coordinate_system_usage: Optional[str] = None, + coord: Optional[Union[Sequence, Numeric, str]] = None, + calendar_index: Optional[Numeric] = None, + calendar_id: Optional[Numeric] = None, + matrix_index: Optional[Numeric] = None, + matrix_id: Optional[Numeric] = None, padding: Union[Sequence, Numeric] = 5, item_gap: Numeric = 10, text_align: str = "auto", @@ -470,6 +555,13 @@ def __init__( "right": pos_right, "top": pos_top, "bottom": pos_bottom, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "padding": padding, "itemGap": item_gap, "textAlign": text_align, @@ -506,15 +598,24 @@ def __init__( 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, + pos_left: Union[Numeric, str] = None, + pos_right: Union[Numeric, str] = None, + pos_top: Union[Numeric, str] = None, + pos_bottom: Union[Numeric, str] = None, + width: Union[Numeric, str] = None, + height: Union[Numeric, 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, + coordinate_system: Optional[str] = None, + coordinate_system_usage: Optional[str] = None, + coord: Optional[Union[Sequence, Numeric, str]] = None, + calendar_index: Optional[Numeric] = None, + calendar_id: Optional[Numeric] = None, + matrix_index: Optional[Numeric] = None, + matrix_id: Optional[Numeric] = None, ): self.opts: dict = { "show": is_show, @@ -543,6 +644,13 @@ def __init__( "top": pos_top, "bottom": pos_bottom, "filterMode": filter_mode, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, } # inside have some different configurations. @@ -555,6 +663,20 @@ def __init__( "preventDefaultMouseMove": is_prevent_default_mouse_move, }) + # slider have some different configurations. + if type_ == "slider": + self.opts.update({ + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, + "width": width, + "height": height, + }) + class LegendOpts(BasicOpts): def __init__( @@ -567,6 +689,15 @@ def __init__( pos_right: Union[str, Numeric, None] = None, pos_top: Union[str, Numeric, None] = None, pos_bottom: Union[str, Numeric, None] = None, + width: Union[str, Numeric, None] = None, + height: Union[str, Numeric, None] = None, + coordinate_system: Optional[str] = None, + coordinate_system_usage: Optional[str] = None, + coord: Optional[Union[Sequence, Numeric, str]] = None, + calendar_index: Optional[Numeric] = None, + calendar_id: Optional[Numeric] = None, + matrix_index: Optional[Numeric] = None, + matrix_id: Optional[Numeric] = None, orient: Optional[str] = None, align: Optional[str] = None, padding: int = 5, @@ -591,9 +722,11 @@ def __init__( is_page_animation: Optional[bool] = None, page_animation_duration_update: int = 800, selector: Union[bool, Sequence] = False, + selector_label: Union[LabelOpts, dict, None] = None, selector_position: str = "auto", selector_item_gap: int = 7, selector_button_gap: int = 10, + is_trigger_event: bool = False, ): self.opts: dict = { "type": type_, @@ -604,6 +737,15 @@ def __init__( "right": pos_right, "top": pos_top, "bottom": pos_bottom, + "width": width, + "height": height, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "orient": orient, "align": align, "padding": padding, @@ -628,9 +770,11 @@ def __init__( "animation": is_page_animation, "animationDurationUpdate": page_animation_duration_update, "selector": selector, + "selectorLabel": selector_label, "selectorPosition": selector_position, "selectorItemGap": selector_item_gap, "selectorButtonGap": selector_button_gap, + "triggerEvent": is_trigger_event, } @@ -647,10 +791,10 @@ def __init__( 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, + pos_left: Union[str, Numeric] = None, + pos_right: Union[str, Numeric] = None, + pos_top: Union[str, Numeric] = None, + pos_bottom: Union[str, Numeric] = None, padding: Union[int, Sequence[int]] = 5, split_number: int = 5, series_index: Union[Numeric, Sequence, None] = None, @@ -661,12 +805,20 @@ def __init__( is_inverse: bool = False, precision: Optional[int] = None, pieces: Optional[Sequence] = None, + categories: 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, + coordinate_system: Optional[str] = None, + coordinate_system_usage: Optional[str] = None, + coord: Optional[Union[Sequence, Numeric, str]] = None, + calendar_index: Optional[Numeric] = None, + calendar_id: Optional[Numeric] = None, + matrix_index: Optional[Numeric] = None, + matrix_id: Optional[Numeric] = None, textstyle_opts: Union[TextStyleOpts, dict, None] = None, ): _inrange_op: dict = {} @@ -680,6 +832,14 @@ def __init__( _inrange_op.update(opacity=range_opacity) _visual_typ = "piecewise" if is_piecewise else "continuous" + if type_ in ["piecewise", "continuous"] and ( + range_color or range_size or range_opacity, + ): + _inrange_op.update( + color=range_color, + symbolSize=range_size, + opacity=range_opacity, + ) if is_piecewise and item_width == 0 and item_height == 0: item_width, item_height = 20, 14 @@ -694,7 +854,7 @@ def __init__( "text": range_text, "textStyle": textstyle_opts, "range": range_, - "inRange": _inrange_op, + "inRange": _inrange_op if _inrange_op else None, "calculable": is_calculable, "inverse": is_inverse, "precision": precision, @@ -715,61 +875,16 @@ def __init__( "backgroundColor": background_color, "borderColor": border_color, "borderWidth": border_width, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, } 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, - } + self.opts.update(pieces=pieces, categories=categories) class AxisLineOpts(BasicOpts): @@ -832,6 +947,54 @@ def __init__( } +class AxisBreakOpts(BasicOpts): + def __init__( + self, + start: Union[Numeric, str] = None, + end: Union[Numeric, str] = None, + gap: Union[Numeric, str] = None, + is_expanded: Optional[bool] = None, + ): + self.opts: dict = { + "start": start, + "end": end, + "gap": gap, + "isExpanded": is_expanded, + } + + +class AxisBreakAreaOpts(BasicOpts): + def __init__( + self, + is_show: Optional[bool] = None, + itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, + zigzag_amplitude: Optional[Numeric] = None, + zigzag_min_span: Optional[Numeric] = None, + zigzag_max_span: Optional[Numeric] = None, + zigzag_z: Optional[Numeric] = None, + is_expand_onclick: Optional[bool] = None, + ): + self.opts: dict = { + "show": is_show, + "itemStyle": itemstyle_opts, + "zigzagAmplitude": zigzag_amplitude, + "zigzagMinSpan": zigzag_min_span, + "zigzagMaxSpan": zigzag_max_span, + "zigzagZ": zigzag_z, + "expandOnClick": is_expand_onclick, + } + + +class AxisBreakLabelLayoutOpts(BasicOpts): + def __init__( + self, + is_move_overlap: Optional[bool] = None, + ): + self.opts: dict = { + "moveOverlap": is_move_overlap, + } + + class AxisOpts(BasicOpts): def __init__( self, @@ -843,16 +1006,32 @@ def __init__( name_location: str = "end", name_gap: Numeric = 15, name_rotate: Optional[Numeric] = None, + name_truncate_max_width: Optional[Numeric] = None, + name_truncate_ellipsis: Optional[str] = None, + is_name_move_overlap: bool = True, interval: Optional[Numeric] = None, grid_index: Optional[Numeric] = None, + grid_id: Optional[Numeric] = None, 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_: Union[Numeric, JSFunc, None] = None, + max_: Union[Numeric, JSFunc, None] = None, min_interval: Numeric = 0, max_interval: Optional[Numeric] = None, + log_base: Optional[Numeric] = None, + start_value: Optional[Numeric] = None, + is_silent: bool = False, + is_trigger_event: bool = False, + jitter: Optional[Numeric] = None, + is_jitter_overlap: Optional[bool] = None, + jitter_margin: Optional[Numeric] = None, + axisbreaks_opts: Sequence[Union[AxisBreakOpts, dict, None]] = None, + axisbreak_area_opts: Union[AxisBreakAreaOpts, dict, None] = None, + axisbreak_label_layout_opts: Union[ + AxisBreakLabelLayoutOpts, dict, None, + ] = None, axisline_opts: Union[AxisLineOpts, dict, None] = None, axistick_opts: Union[AxisTickOpts, dict, None] = None, axislabel_opts: Union[LabelOpts, dict, None] = None, @@ -872,9 +1051,15 @@ def __init__( "nameLocation": name_location, "nameGap": name_gap, "nameRotate": name_rotate, + "nameTruncate": { + "maxWidth": name_truncate_max_width, + "ellipsis": name_truncate_ellipsis, + }, + "nameMoveOverlap": is_name_move_overlap, "interval": interval, "nameTextStyle": name_textstyle_opts, "gridIndex": grid_index, + "gridId": grid_id, "axisLine": axisline_opts, "axisTick": axistick_opts, "axisLabel": axislabel_opts, @@ -888,6 +1073,16 @@ def __init__( "max": max_, "minInterval": min_interval, "maxInterval": max_interval, + "logBase": log_base, + "startValue": start_value, + "silent": is_silent, + "triggerEvent": is_trigger_event, + "jitter": jitter, + "jitterOverlap": is_jitter_overlap, + "jitterMargin": jitter_margin, + "breaks": axisbreaks_opts, + "breakArea": axisbreak_area_opts, + "breakLabelLayout": axisbreak_label_layout_opts, "splitLine": splitline_opts, "splitArea": splitarea_opts, "minorTick": minor_tick_opts, @@ -898,6 +1093,26 @@ def __init__( self.opts.update(**animation_opts.opts) +class GridOuterOpts(BasicOpts): + def __init__( + self, + 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, + ): + self.opts: dict = { + "left": pos_left, + "top": pos_top, + "right": pos_right, + "bottom": pos_bottom, + "width": width, + "height": height, + } + + class GridOpts(BasicOpts): def __init__( self, @@ -911,6 +1126,9 @@ def __init__( width: Union[Numeric, str, None] = None, height: Union[Numeric, str, None] = None, is_contain_label: bool = False, + outer_bounds_mode: Optional[str] = None, + outer_bounds_opts: Union[GridOuterOpts, dict, None] = None, + outer_bounds_contain: Optional[str] = None, background_color: str = "transparent", border_color: str = "#ccc", border_width: Numeric = 1, @@ -919,6 +1137,13 @@ def __init__( shadow_offset_x: Numeric = 0, shadow_offset_y: Numeric = 0, tooltip_opts: Union[TooltipOpts, dict, None] = None, + coordinate_system: Optional[str] = None, + coordinate_system_usage: Optional[str] = None, + coord: Optional[Union[Sequence, Numeric, str]] = None, + calendar_index: Optional[Numeric] = None, + calendar_id: Optional[Numeric] = None, + matrix_index: Optional[Numeric] = None, + matrix_id: Optional[Numeric] = None, ): self.opts: dict = { "show": is_show, @@ -931,6 +1156,9 @@ def __init__( "width": width, "height": height, "containLabel": is_contain_label, + "outerBoundsMode": outer_bounds_mode, + "outerBounds": outer_bounds_opts, + "outerBoundsContain": outer_bounds_contain, "backgroundColor": background_color, "borderColor": border_color, "borderWidth": border_width, @@ -939,6 +1167,13 @@ def __init__( "shadowOffsetX": shadow_offset_x, "shadowOffsetY": shadow_offset_y, "tooltip": tooltip_opts, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, } @@ -1053,6 +1288,17 @@ def __init__( pos_right: str = "13%", pos_bottom: str = "10%", pos_top: str = "20%", + z_level: Optional[Numeric] = None, + z: Optional[Numeric] = None, + width: Optional[str] = None, + height: Optional[str] = None, + coordinate_system: Optional[str] = None, + coordinate_system_usage: Optional[str] = None, + coord: Optional[Union[Sequence, Numeric, str]] = None, + calendar_index: Optional[Numeric] = None, + calendar_id: Optional[Numeric] = None, + matrix_index: Optional[Numeric] = None, + matrix_id: Optional[Numeric] = None, layout: Optional[str] = None, is_axis_expandable: bool = False, axis_expand_center: Optional[Numeric] = None, @@ -1065,6 +1311,17 @@ def __init__( "right": pos_right, "bottom": pos_bottom, "top": pos_top, + "zlevel": z_level, + "z": z, + "width": width, + "height": height, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, "layout": layout, "axisExpandable": is_axis_expandable, "axisExpandCenter": axis_expand_center, @@ -1539,11 +1796,11 @@ def __init__( 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, + 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, @@ -1569,3 +1826,273 @@ def __init__( "blur": blur_opts, "select": select_opts, } + + +class MatrixDividerLineStyleOpts(BasicOpts): + def __init__( + self, + color: Optional[str] = "#aaa", + width: Optional[Numeric] = 1, + type_: Optional[str] = "solid", + dash_offset: Optional[Numeric] = 0, + cap: Optional[str] = "butt", + join: Optional[str] = "bevel", + miter_limit: Optional[Numeric] = 10, + shadow_blur: Optional[Numeric] = None, + shadow_color: Optional[str] = None, + shadow_offset_x: Optional[Numeric] = None, + shadow_offset_y: Optional[Numeric] = None, + opacity: Optional[Numeric] = 1, + ): + self.opts: dict = { + "color": color, + "width": width, + "type": type_, + "dashOffset": dash_offset, + "cap": cap, + "join": join, + "miterLimit": miter_limit, + "shadowBlur": shadow_blur, + "shadowColor": shadow_color, + "shadowOffsetX": shadow_offset_x, + "shadowOffsetY": shadow_offset_y, + "opacity": opacity, + } + + +class MatrixAxisDataOpts(BasicOpts): + def __init__( + self, + value: Union[Numeric, str] = None, + children: Union[dict, JSFunc, None] = None, + size: Optional[Numeric] = None, + ): + self.opts: dict = { + "value": value, + "children": children, + "size": size, + } + + +class MatrixAxisOpts(BasicOpts): + def __init__( + self, + is_show: bool = True, + data: Union[Sequence, dict, JSFunc, None] = None, + label_opts: Union[LabelOpts, dict, None] = None, + itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, + is_silent: bool = False, + cursor: Optional[str] = None, + z2: Optional[Numeric] = None, + level_size: Union[Numeric, str, None] = None, + levels: Optional[Sequence] = None, + divider_line_style_opts: Union[ + MatrixDividerLineStyleOpts, dict, None, + ] = None, + ): + self.opts: dict = { + "show": is_show, + "data": data, + "label": label_opts, + "itemStyle": itemstyle_opts, + "silent": is_silent, + "cursor": cursor, + "z2": z2, + "levelSize": level_size, + "levels": levels, + "dividerLineStyle": divider_line_style_opts, + } + + +class MatrixBodyDataOpts(BasicOpts): + def __init__( + self, + coord: Optional[Sequence] = None, + is_coord_clamp: Optional[bool] = None, + is_merge_cells: Optional[bool] = None, + value: Union[Numeric, str, None] = None, + label_opts: Union[LabelOpts, dict, None] = None, + ): + self.opts: dict = { + "coord": coord, + "coordClamp": is_coord_clamp, + "mergeCells": is_merge_cells, + "value": value, + "label": label_opts, + } + + +class MatrixBodyOrCornerOpts(BasicOpts): + def __init__( + self, + data: Union[Sequence[MatrixBodyDataOpts], dict, None] = None, + label_opts: Union[LabelOpts, dict, None] = None, + itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, + is_silent: bool = False, + cursor: Optional[str] = None, + z2: Optional[Numeric] = None, + ): + self.opts: dict = { + "data": data, + "label": label_opts, + "itemStyle": itemstyle_opts, + "silent": is_silent, + "cursor": cursor, + "z2": z2, + } + + +class MatrixBackgroundStyleOpts(BasicOpts): + def __init__( + self, + color: Optional[str] = None, + border_color: str = "#ccc", + border_width: Numeric = 1, + border_type: str = "solid", + border_radius: Union[Numeric, Sequence] = 0, + border_cap: str = "butt", + border_join: str = "bevel", + border_miter_limit: Optional[Numeric] = 10, + shadow_blur: Optional[Numeric] = None, + shadow_color: Optional[str] = None, + shadow_offset_x: Numeric = 0, + shadow_offset_y: Numeric = 0, + opacity: Optional[Numeric] = 1, + ): + self.opts: dict = { + "color": color, + "borderColor": border_color, + "borderWidth": border_width, + "borderType": border_type, + "borderRadius": border_radius, + "borderCap": border_cap, + "borderJoin": border_join, + "borderMiterLimit": border_miter_limit, + "shadowBlur": shadow_blur, + "shadowColor": shadow_color, + "shadowOffsetX": shadow_offset_x, + "shadowOffsetY": shadow_offset_y, + "opacity": opacity, + } + + +class MatrixOpts(BasicOpts): + def __init__( + self, + 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, + x_data: Union[MatrixAxisOpts, dict, None] = None, + y_data: Union[MatrixAxisOpts, dict, None] = None, + body_opts: Union[MatrixBodyOrCornerOpts, dict, None] = None, + corner_opts: Union[MatrixBodyOrCornerOpts, dict, None] = None, + background_style: Union[MatrixBackgroundStyleOpts, dict, None] = None, + border_z2: Optional[Numeric] = None, + tooltip_opts: TooltipOpts = None, + ): + self.opts: dict = { + "zlevel": z_level, + "z": z, + "left": pos_left, + "top": pos_top, + "right": pos_right, + "bottom": pos_bottom, + "width": width, + "height": height, + "x": x_data, + "y": y_data, + "body": body_opts, + "corner": corner_opts, + "backgroundStyle": background_style, + "borderZ2": border_z2, + "tooltip": tooltip_opts, + } + + +class ThumbnailWindowStyleOpts(BasicOpts): + def __init__( + self, + color: Optional[str] = "#9ea0a5", + border_color: str = "#b7b9be", + border_width: Numeric = 1, + border_type: str = "solid", + border_dash_offset: Numeric = 0, + border_cap: str = "butt", + border_join: str = "bevel", + border_miter_limit: Optional[Numeric] = 10, + shadow_blur: Optional[Numeric] = None, + shadow_color: Optional[str] = None, + shadow_offset_x: Numeric = 0, + shadow_offset_y: Numeric = 0, + opacity: Optional[Numeric] = 0.3, + ): + 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, + "shadowBlur": shadow_blur, + "shadowColor": shadow_color, + "shadowOffsetX": shadow_offset_x, + "shadowOffsetY": shadow_offset_y, + "opacity": opacity, + } + + +class ThumbnailOpts(BasicOpts): + def __init__( + self, + is_show: bool = True, + 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, + coordinate_system: Optional[str] = None, + coordinate_system_usage: Optional[str] = None, + coord: Optional[Union[Sequence, Numeric, str]] = None, + calendar_index: Optional[Numeric] = None, + calendar_id: Optional[Numeric] = None, + matrix_index: Optional[Numeric] = None, + matrix_id: Optional[Numeric] = None, + itemstyle_opts: Union[ItemStyleOpts, dict, None] = None, + window_style_opts: Union[ + ThumbnailWindowStyleOpts, dict, None, + ] = None, + series_index: Optional[Numeric] = None, + series_id: Union[Numeric, str, 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, + "coordinateSystem": coordinate_system, + "coordinateSystemUsage": coordinate_system_usage, + "coord": coord, + "calendarIndex": calendar_index, + "calendarId": calendar_id, + "matrixIndex": matrix_index, + "matrixId": matrix_id, + "itemStyle": itemstyle_opts, + "windowStyle": window_style_opts, + "seriesIndex": series_index, + "seriesId": series_id, + } diff --git a/pyecharts/options/series_options.py b/pyecharts/options/series_options.py index 8117811c5..7e59a436f 100644 --- a/pyecharts/options/series_options.py +++ b/pyecharts/options/series_options.py @@ -87,6 +87,7 @@ def __init__( width: Optional[str] = None, height: Optional[str] = None, rich: Optional[dict] = None, + is_rich_inherit_plain_label: bool = True, ): self.opts: dict = { "color": color, @@ -107,6 +108,7 @@ def __init__( "width": width, "height": height, "rich": rich, + "richInheritPlainLabel": is_rich_inherit_plain_label, } @@ -116,13 +118,17 @@ def __init__( is_show: bool = True, position: Optional[Union[str, Sequence]] = None, color: Optional[str] = None, + opacity: Optional[Numeric] = 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, + offset: Optional[Sequence[Numeric]] = None, margin: Optional[Numeric] = 8, + text_margin: Union[Numeric, Sequence, None] = None, + min_margin: Optional[Numeric] = None, interval: Union[Numeric, str, None] = None, horizontal_align: Optional[str] = None, vertical_align: Optional[str] = None, @@ -140,18 +146,23 @@ def __init__( text_shadow_blur: Optional[Numeric] = None, text_shadow_offset_x: Optional[Numeric] = None, text_shadow_offset_y: Optional[Numeric] = None, - offset: Optional[Sequence[Numeric]] = None, overflow: Optional[str] = None, rich: Optional[dict] = None, + is_rich_inherit_plain_label: bool = True, is_value_animation: bool = False, + text_style_opts: Optional[TextStyleOpts] = None, ): self.opts: dict = { "show": is_show, "position": position, "color": color, + "opacity": opacity, "distance": distance, "rotate": rotate, + "offset": offset, "margin": margin, + "textMargin": text_margin, + "minMargin": min_margin, "interval": interval, "fontSize": font_size, "fontStyle": font_style, @@ -175,18 +186,20 @@ def __init__( "textShadowOffsetY": text_shadow_offset_y, "overflow": overflow, "rich": rich, + "richInheritPlainLabel": is_rich_inherit_plain_label, "valueAnimation": is_value_animation, + "textStyle": text_style_opts, } class LineStyleOpts(BasicOpts): def __init__( self, - is_show: bool = True, - width: Numeric = 1, - opacity: Numeric = 1, - curve: Numeric = 0, - type_: str = "solid", + is_show: bool = False, + width: Optional[Numeric] = None, + opacity: Optional[Numeric] = None, + curve: Optional[Numeric] = None, + type_: Optional[str] = None, color: Union[str, Sequence, None] = None, ): self.opts: dict = { @@ -432,8 +445,23 @@ def __init__( class AreaStyleOpts(BasicOpts): - def __init__(self, opacity: Optional[Numeric] = 0, color: Optional[JSFunc] = None): - self.opts: dict = {"opacity": opacity, "color": color} + def __init__( + self, + opacity: Optional[Numeric] = 0, + color: Optional[JSFunc] = None, + shadow_blur: Optional[Numeric] = None, + shadow_color: Optional[str] = None, + shadow_offset_x: Optional[Numeric] = None, + shadow_offset_y: Optional[Numeric] = None, + ): + self.opts: dict = { + "opacity": opacity, + "color": color, + "shadowBlur": shadow_blur, + "shadowColor": shadow_color, + "shadowOffsetX": shadow_offset_x, + "shadowOffsetY": shadow_offset_y, + } class SplitAreaOpts(BasicOpts): diff --git a/pyecharts/types.py b/pyecharts/types.py index a78ad8510..8784e12e3 100644 --- a/pyecharts/types.py +++ b/pyecharts/types.py @@ -11,6 +11,7 @@ ) from . import options as opts +from .options import ThumbnailOpts from .options.charts_options import BaseGraphic, GlobeLayersOpts from .options.series_options import JsCode, JSFunc, Numeric @@ -54,6 +55,7 @@ MarkArea = Union[opts.MarkAreaOpts, dict, None] MarkPoint = Union[opts.MarkPointOpts, dict, None] MarkLine = Union[opts.MarkLineOpts, dict, None] +Matrix = Union[opts.MatrixOpts, dict, None] MinorTick = Union[opts.MinorTickOpts, dict, None] Label = Union[opts.LabelOpts, dict, None] Legend = Union[opts.LegendOpts, dict] @@ -62,6 +64,7 @@ PieLabelLine = Union[opts.PieLabelLineOpts, dict, None] PieEmptyCircle = Union[opts.PieEmptyCircleStyle, dict, None] TextStyle = Union[opts.TextStyleOpts, dict, None] +Thumbnail = Union[ThumbnailOpts, dict, None] TimeLineControl = Union[opts.TimelineControlStyle, dict, None] TimeLinkCheckPoint = Union[opts.TimelineCheckPointerStyle, dict, None] Title = Union[opts.TitleOpts, dict] @@ -83,6 +86,20 @@ CalendarMonthLabelOpts = Union[opts.CalendarMonthLabelOpts, dict, None] CalendarYearLabelOpts = Union[opts.CalendarYearLabelOpts, dict, None] +CustomItemPayload = Union[ + opts.CustomBarRangeItemPayloadOpts, + opts.CustomContourItemPayloadOpts, + opts.CustomLineRangeItemPayloadOpts, + opts.CustomSegmentedDoughnutItemPayloadOpts, + opts.CustomStageItemPayloadOpts, + opts.CustomViolinItemPayloadOpts, + dict, + None, +] + +ChordData = Union[opts.ChordData, dict] +ChordLink = Union[opts.ChordLink, dict] + GraphNode = Union[opts.GraphNode, dict] GraphLink = Union[opts.GraphLink, dict] GraphCategory = Union[opts.GraphCategory, dict] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..3d8231b7d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,85 @@ +[build-system] +requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "pyecharts" +description = "Python options, make charting easier" +readme = "README.md" +license = {text = "MIT"} +authors = [ + {name = "chenjiandongx", email = "chenjiandongx@qq.com"}, + {name = "sunhailin-Leo", email = "shjkfld379978424@gmail.com"} +] +keywords = ["Echarts", "charts", "plotting-tool"] +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", + "Programming Language :: Python :: 3 :: Only", + "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", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Libraries", +] +requires-python = ">=3.7" +dependencies = [ + "jinja2>=2.11.3", + "prettytable", + "simplejson", +] +dynamic = ["version"] + +[project.optional-dependencies] +selenium = ["snapshot-selenium"] +phantomjs = ["snapshot-phantomjs"] +pyppeteer = ["snapshot-pyppeteer"] +images = ["Pillow"] + +[project.urls] +Homepage = "https://github.com/pyecharts/pyecharts" + +[tool.setuptools_scm] +version_scheme = "guess-next-dev" +local_scheme = "dirty-tag" + +[tool.uv] +dev-dependencies = [ + "nose2", + "codecov", + "coverage", + "flake8", + "mccabe", + "Pillow", + "pytest", + "pytest-cov", + "pytest-xdist", + "numpy", + "pandas", +] + +[tool.coverage.run] +omit = [ + "pyecharts/render/templates/*", + "setup.py", + "install.py", + "test.py", + "test/*", +] + +[tool.coverage.report] +show_missing = true +omit = [ + "pyecharts/render/templates/*", + "setup.py", + "install.py", +] \ No newline at end of file diff --git a/test/requirements.txt b/test/requirements.txt index 15fffef16..4434f480f 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -6,5 +6,6 @@ mccabe Pillow pytest pytest-cov +pytest-xdist numpy pandas \ No newline at end of file diff --git a/test/test_bar.py b/test/test_bar.py index 4454d9944..95c7d50d5 100644 --- a/test/test_bar.py +++ b/test/test_bar.py @@ -146,7 +146,7 @@ def test_bar_base_dict_config(self, fake_writer): @patch("pyecharts.render.engine.write_utf8_html_file") def test_bar_colors(self, fake_writer): c = Bar().add_xaxis(["A", "B", "C"]).add_yaxis("series0", [1, 2, 4]) - c.set_colors(["#AABBCC", "#BBCCDD", "#CCDDEE"] + c.colors) + c.set_colors(["#AABBCC", "#BBCCDD", "#CCDDEE"]) c.render() _, content = fake_writer.call_args[0] self.assertIn("#AABBCC", content) @@ -206,9 +206,9 @@ def test_bar_default_set_function(self, fake_writer): def test_bar_default_remote_host(self, fake_writer): c = Bar().add_xaxis(["A", "B", "C"]).add_yaxis("series0", [1, 2, 4]) c.render() - self.assertEqual(c.js_host, "https://assets.pyecharts.org/assets/v5/") + self.assertEqual(c.js_host, "https://assets.pyecharts.org/assets/v6/") _, content = fake_writer.call_args[0] - self.assertIn("https://assets.pyecharts.org/assets/v5/echarts.min.js", content) + self.assertIn("https://assets.pyecharts.org/assets/v6/echarts.min.js", content) @patch("pyecharts.render.engine.write_utf8_html_file") def test_bar_custom_remote_host(self, fake_writer): diff --git a/test/test_chart.py b/test/test_chart.py index e40b42bce..30b032ef9 100644 --- a/test/test_chart.py +++ b/test/test_chart.py @@ -181,19 +181,10 @@ def test_chart_append_color(self, fake_writer): ) c.render() _, content = fake_writer.call_args[0] - # Old Version (Before 2.0) - # default_colors = ( - # "#c23531 #2f4554 #61a0a8 #d48265 #749f83 #ca8622 #bda29a #6e7074 " - # "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa " - # "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597" - # ).split() # New Version - default_colors = ( - "#5470c6 #91cc75 #fac858 #ee6666 #73c0de #3ba272 #fc8452 #9a60b4 " "#ea7ccc" - ).split() - expected_result = ["#80FFA5", "#00DDFF", *default_colors] - self.assertEqual(c.colors, expected_result) + expected_result = ["#80FFA5", "#00DDFF"] + self.assertEqual(c.options.get("color"), expected_result) @patch("pyecharts.render.engine.write_utf8_html_file") def test_chart_add_dataset(self, fake_writer): diff --git a/test/test_chart_options.py b/test/test_chart_options.py index 8cc273bed..45769ee7f 100644 --- a/test/test_chart_options.py +++ b/test/test_chart_options.py @@ -3,6 +3,12 @@ from pyecharts.commons.utils import remove_key_with_none_value from pyecharts.options.charts_options import ( BarBackgroundStyleOpts, + CustomBarRangeItemPayloadOpts, + CustomContourItemPayloadOpts, + CustomLineRangeItemPayloadOpts, + CustomSegmentedDoughnutItemPayloadOpts, + CustomStageItemPayloadOpts, + CustomViolinItemPayloadOpts, GlobeLayersOpts, GraphCategory, SunburstLevelOpts, @@ -148,3 +154,28 @@ def test_sunburst_level_opts_remove_none(self): option = SunburstLevelOpts() expected = {} self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_custom_item_payload_opts_remove_none(self): + option = CustomBarRangeItemPayloadOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + option = CustomContourItemPayloadOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + option = CustomLineRangeItemPayloadOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + option = CustomSegmentedDoughnutItemPayloadOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + option = CustomStageItemPayloadOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + option = CustomViolinItemPayloadOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_chord.py b/test/test_chord.py new file mode 100644 index 000000000..348f71f38 --- /dev/null +++ b/test/test_chord.py @@ -0,0 +1,48 @@ +import unittest +from unittest.mock import patch + +from pyecharts import options as opts +from pyecharts.charts import Chord + + +class TestChordChart(unittest.TestCase): + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_chord_base(self, fake_writer): + c = ( + Chord() + .add( + series_name="chord", + data=[ + opts.ChordData(name="A"), + opts.ChordData(name="B"), + opts.ChordData(name="C"), + opts.ChordData(name="D"), + ], + links=[ + opts.ChordLink( + source="A", + target="B", + value=40, + ), + opts.ChordLink( + source="A", + target="C", + value=20, + ), + opts.ChordLink( + source="B", + target="D", + value=20, + ), + ], + is_clockwise=False, + label_opts=opts.LabelOpts(is_show=True), + linestyle_opts=opts.LineStyleOpts(color="target"), + ) + ) + c.render() + _, content = fake_writer.call_args[0] + self.assertGreater(len(content), 2000) + self.assertEqual(c.theme, "white") + self.assertEqual(c.renderer, "canvas") diff --git a/test/test_custom.py b/test/test_custom.py index b3e5915cd..1aee04b45 100644 --- a/test/test_custom.py +++ b/test/test_custom.py @@ -3,6 +3,7 @@ from pyecharts.charts import Custom from pyecharts.commons.utils import JsCode +from pyecharts.globals import ChartType class TestCustom(unittest.TestCase): @@ -42,3 +43,32 @@ def test_custom_base(self, fake_writer): _, content = fake_writer.call_args[0] self.assertGreater(len(content), 2000) self.assertIn("renderItem", content) + + def test_custom_echarts_x_with_error(self): + c = Custom() + try: + c.register_echarts_x(chart_type=ChartType.LINE) + except ValueError: + pass + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_custom_echarts_x(self, fake_writer): + for chart_type in [ + ChartType.VIOLIN, + ChartType.STAGE, + ChartType.DOUGHNUT, + ChartType.CONTOUR, + ChartType.BAR_RANGE, + ChartType.LINE_RANGE, + ]: + c = ( + Custom() + .register_echarts_x(chart_type=chart_type) + .add(series_name="test", render_item=chart_type) + ) + if chart_type != ChartType.DOUGHNUT: + c.add_xaxis(xaxis_data=["a", "b", "c"]) + c.render() + _, content = fake_writer.call_args[0] + self.assertIn("renderItem", content) + self.assertIn("xAxis", content) diff --git a/test/test_global_options.py b/test/test_global_options.py index 3eb01ddc1..1725b5974 100644 --- a/test/test_global_options.py +++ b/test/test_global_options.py @@ -7,12 +7,25 @@ AngleAxisOpts, AriaLabelOpts, AriaDecalOpts, + AxisBreakOpts, + AxisBreakAreaOpts, + AxisBreakLabelLayoutOpts, AxisTickOpts, BlurOpts, CalendarYearLabelOpts, DatasetTransformOpts, Emphasis3DOpts, + GridOuterOpts, InitOpts, + MatrixDividerLineStyleOpts, + MatrixAxisDataOpts, + MatrixAxisOpts, + MatrixBodyDataOpts, + MatrixBodyOrCornerOpts, + MatrixBackgroundStyleOpts, + MatrixOpts, + ThumbnailWindowStyleOpts, + ThumbnailOpts, ParallelAxisOpts, RadiusAxisItem, RadiusAxisOpts, @@ -236,6 +249,7 @@ def test_legend_options_remove_none(self): "selectorPosition": "auto", "selectorItemGap": 7, "selectorButtonGap": 10, + "triggerEvent": False, } self.assertEqual(expected, remove_key_with_none_value(option.opts)) @@ -260,6 +274,16 @@ def test_visual_map_options_remove_none(self): } self.assertEqual(expected, remove_key_with_none_value(option.opts)) + def test_visual_map_continuous_options(self): + option = VisualMapOpts( + type_="continuous", + range_color=["red", "yellow"] + ) + self.assertEqual( + option.opts.get("inRange").get("color"), + ["red", "yellow"], + ) + def test_tool_tip_options_remove_none(self): option = TooltipOpts(textstyle_opts=None) expected = { @@ -267,16 +291,17 @@ def test_tool_tip_options_remove_none(self): "axisPointer": {"type": "line"}, "borderWidth": 0, "hideDelay": 100, + "confine": False, + "displayTransition": True, + "enterable": False, + 'order': 'seriesAsc', "padding": 5, "show": True, "showContent": True, "showDelay": 0, "trigger": "item", - "enterable": False, - "confine": False, "appendToBody": False, "transitionDuration": 0.4, - "order": "seriesAsc", "triggerOn": "mousemove|click", } self.assertEqual(expected, remove_key_with_none_value(option.opts)) @@ -372,3 +397,107 @@ def test_select_opts_remove_none(self): option = SelectOpts() expected = {} self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_axis_break_opts_remove_none(self): + option = AxisBreakOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_axis_break_area_opts_remove_none(self): + option = AxisBreakAreaOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_axis_break_label_layout_opts_remove_none(self): + option = AxisBreakLabelLayoutOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_grid_outer_opts_remove_none(self): + option = GridOuterOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_matrix_divider_line_style_opts_remove_none(self): + option = MatrixDividerLineStyleOpts() + expected = { + "color": "#aaa", + "width": 1, + "type": "solid", + "dashOffset": 0, + "cap": "butt", + "join": "bevel", + "miterLimit": 10, + "opacity": 1, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_matrix_axis_data_opts_remove_none(self): + option = MatrixAxisDataOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_matrix_axis_opts_remove_none(self): + option = MatrixAxisOpts() + expected = { + "show": True, + "silent": False, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_matrix_body_data_opts_remove_none(self): + option = MatrixBodyDataOpts() + expected = {} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_matrix_body_or_corner_opts_remove_none(self): + option = MatrixBodyOrCornerOpts() + expected = {"silent": False} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_matrix_background_style_opts_remove_none(self): + option = MatrixBackgroundStyleOpts() + expected = { + "borderColor": "#ccc", + "borderWidth": 1, + "borderType": "solid", + "borderRadius": 0, + "borderCap": "butt", + "borderJoin": "bevel", + "borderMiterLimit": 10, + "shadowOffsetX": 0, + "shadowOffsetY": 0, + "opacity": 1, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_matrix_opts_remove_none(self): + option = MatrixOpts() + expected = {"zlevel": 0, "z": 2} + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_thumbnail_window_style_opts_remove_none(self): + option = ThumbnailWindowStyleOpts() + expected = { + "color": "#9ea0a5", + "borderColor": "#b7b9be", + "borderWidth": 1, + "borderType": "solid", + "borderDashOffset": 0, + "borderCap": "butt", + "borderJoin": "bevel", + "borderMiterLimit": 10, + "shadowOffsetX": 0, + "shadowOffsetY": 0, + "opacity": 0.3, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) + + def test_thumbnail_opts_remove_none(self): + option = ThumbnailOpts() + expected = { + "show": True, + "zlevel": 0, + "z": 2, + } + self.assertEqual(expected, remove_key_with_none_value(option.opts)) diff --git a/test/test_grid.py b/test/test_grid.py index 44a53eda0..58c02d8b7 100644 --- a/test/test_grid.py +++ b/test/test_grid.py @@ -318,7 +318,8 @@ def _chart_for_grid_v2(self): ) bar.overlap(line) - self.assertEqual(bar.colors[:3], ["red", "green", "blue"]) + self.assertEqual(bar.options.get("color")[:3], + ["red", "green", "blue"]) return bar @patch("pyecharts.render.engine.write_utf8_html_file") @@ -615,3 +616,32 @@ def test_grid_geo_map(self, fake_writer): grid_chart.render() _, content = fake_writer.call_args[0] self.assertIn("geo", content) + + @patch("pyecharts.render.engine.write_utf8_html_file") + def test_grid_multi_geo(self, fake_writer): + data_pair = [list(z) for z in zip(Faker.provinces, Faker.values())] + + grid_chart = Grid(init_opts=opts.InitOpts()) + geo_charts_len = 2 + for i in range(geo_charts_len): + geo_chart = ( + Geo() + .add_schema(maptype="china") + .add( + f"geo_{i}", + data_pair=data_pair, + type_=ChartType.SCATTER, + ) + .set_global_opts( + visualmap_opts=opts.VisualMapOpts(), + ) + ) + grid_chart.add( + chart=geo_chart, + grid_opts=opts.GridOpts(), + ) + + grid_chart.render() + _, content = fake_writer.call_args[0] + self.assertIn("geo", content) + self.assertEqual(len(grid_chart.options.get("geo")), geo_charts_len) diff --git a/test/test_map.py b/test/test_map.py index 2c89716a4..96b77c13d 100644 --- a/test/test_map.py +++ b/test/test_map.py @@ -46,8 +46,16 @@ def test_map_emphasis(self): ) options = json.loads(c.dump_options()) expected = { - "label": {"show": False, "margin": 8, "valueAnimation": False}, - "itemStyle": {"borderColor": "white", "areaColor": "red"}, + "label": { + "show": False, + "richInheritPlainLabel": True, + "margin": 8, + "valueAnimation": False, + }, + "itemStyle": { + "borderColor": "white", + "areaColor": "red", + }, } self.assertEqual(expected, options["series"][0]["emphasis"]) diff --git a/test/test_page.py b/test/test_page.py index e916603b6..17c40732d 100644 --- a/test/test_page.py +++ b/test/test_page.py @@ -53,7 +53,7 @@ def test_page_jshost_default(self): bar = _create_bar() line = _create_line() page = Page().add(bar, line) - self.assertEqual(page.js_host, "https://assets.pyecharts.org/assets/v5/") + self.assertEqual(page.js_host, "https://assets.pyecharts.org/assets/v6/") def test_page_jshost_custom(self): from pyecharts.globals import CurrentConfig @@ -85,7 +85,7 @@ def test_page_load_javascript(self): content = Page().add(bar, line).load_javascript() self.assertEqual("", content.data) self.assertEqual( - ["https://assets.pyecharts.org/assets/v5/echarts.min.js"], content.lib + ["https://assets.pyecharts.org/assets/v6/echarts.min.js"], content.lib ) def _get_new_page(self, unique: bool = True) -> Page: diff --git a/test/test_series_options.py b/test/test_series_options.py index 836a32fcc..6f36fac61 100644 --- a/test/test_series_options.py +++ b/test/test_series_options.py @@ -27,6 +27,9 @@ def test_label_options_defaults(self): "distance": None, "rotate": None, "margin": 8, + "minMargin": None, + "offset": None, + "opacity": None, "interval": None, "fontSize": None, "fontStyle": None, @@ -44,12 +47,15 @@ def test_label_options_defaults(self): "height": None, "textBorderColor": None, "textBorderWidth": None, + "textMargin": None, "textShadowColor": None, "textShadowBlur": None, "textShadowOffsetX": None, "textShadowOffsetY": None, + "textStyle": None, "overflow": None, "rich": None, + "richInheritPlainLabel": True, "valueAnimation": False, } self.assertEqual(expected, option.opts) @@ -74,6 +80,9 @@ def test_label_options_custom(self): "distance": None, "rotate": None, "margin": 8, + "minMargin": None, + "offset": None, + "opacity": None, "interval": None, "fontSize": None, "fontStyle": None, @@ -95,8 +104,11 @@ def test_label_options_custom(self): "textShadowBlur": .1, "textShadowOffsetX": .2, "textShadowOffsetY": .3, + "textMargin": None, + "textStyle": None, "overflow": None, "rich": None, + "richInheritPlainLabel": True, "valueAnimation": False, } self.assertEqual(expected, option.opts) diff --git a/test/test_tab.py b/test/test_tab.py index afaa35305..305834877 100644 --- a/test/test_tab.py +++ b/test/test_tab.py @@ -63,10 +63,10 @@ def test_tab_render_notebook(self): html = tab.render_notebook().__html__() self.assertIn("City name", html) - def test_page_jshost_default(self): + def test_tab_jshost_default(self): bar = _create_bar() tab = Tab().add(bar, "bar") - self.assertEqual(tab.js_host, "https://assets.pyecharts.org/assets/v5/") + self.assertEqual(tab.js_host, "https://assets.pyecharts.org/assets/v6/") def test_tab_jshost_custom(self): from pyecharts.globals import CurrentConfig diff --git a/test/test_wordcloud.py b/test/test_wordcloud.py index cbb1ba7f8..206e3d42a 100644 --- a/test/test_wordcloud.py +++ b/test/test_wordcloud.py @@ -57,17 +57,32 @@ def test_wordcloud_mask_image(self, fake_writer): self.assertEqual(c.theme, "white") self.assertEqual(c.renderer, "canvas") - @patch("pyecharts.render.engine.write_utf8_html_file") - def test_wordcloud_encode_image_to_base64_os_error(self, fake_writer): - error_path = "A" * 1000 - c = WordCloud().add( - "", - words, - word_size_range=[20, 100], - shape="cardioid", - mask_image=f"{error_path}", - ) - c.render() - _, content = fake_writer.call_args[0] - self.assertIn(error_path, content) - self.assertNotIn("data:image/", content) + @patch("pathlib.Path.is_file", side_effect=OSError("Simulated OS Error")) + def test_encode_image_to_base64_os_error(self, mock_is_file): + """ + 测试当 Path.is_file 抛出 OSError 时,_encode_image_to_base64 返回原始参数。 + """ + # 构造一个无效路径 + invalid_path = "/invalid/path/to/image.png" + + # 创建 WordCloud 实例并调用方法 + wordcloud = WordCloud() + result = wordcloud._encode_image_to_base64(invalid_path) + + # 验证返回值是原始路径 + self.assertEqual(result, invalid_path) + + @patch("pathlib.Path.exists", side_effect=OSError("Simulated OS Error")) + def test_encode_image_to_base64_os_error_on_exists(self, mock_exists): + """ + 测试当 Path.exists 抛出 OSError 时,_encode_image_to_base64 返回原始参数。 + """ + # 构造一个无效路径 + invalid_path = "/another/invalid/path.jpg" + + # 创建 WordCloud 实例并调用方法 + wordcloud = WordCloud() + result = wordcloud._encode_image_to_base64(invalid_path) + + # 验证返回值是原始路径 + self.assertEqual(result, invalid_path) diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..f5721c445 --- /dev/null +++ b/uv.lock @@ -0,0 +1,3866 @@ +version = 1 +revision = 2 +requires-python = ">=3.7" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] + +[[package]] +name = "appdirs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, +] + +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678, upload-time = "2024-08-06T14:37:38.364Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001, upload-time = "2024-08-06T14:37:36.958Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "cffi" +version = "1.15.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "pycparser", version = "2.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", size = 508501, upload-time = "2022-06-30T18:18:32.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/52/1e2b43cfdd7d9a39f48bc89fcaee8d8685b1295e205a4f1044909ac14d89/cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", size = 170412, upload-time = "2022-06-30T18:16:00.728Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e2/a23af3d81838c577571da4ff01b799b0c2bbde24bd924d97e228febae810/cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", size = 179060, upload-time = "2022-06-30T18:16:03.511Z" }, + { url = "https://files.pythonhosted.org/packages/87/ee/ddc23981fc0f5e7b5356e98884226bcb899f95ebaefc3e8e8b8742dd7e22/cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", size = 170313, upload-time = "2022-06-30T18:16:29.939Z" }, + { url = "https://files.pythonhosted.org/packages/43/a0/cc7370ef72b6ee586369bacd3961089ab3d94ae712febf07a244f1448ffd/cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", size = 179001, upload-time = "2022-06-30T18:16:34.521Z" }, + { url = "https://files.pythonhosted.org/packages/50/34/4cc590ad600869502c9838b4824982c122179089ed6791a8b1c95f0ff55e/cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", size = 169721, upload-time = "2022-06-30T18:17:20.322Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/63cb8c07d151de92ff9d897b2eb27ba6a0e78dda8e4c5f70d7b8c16cd6a2/cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", size = 179301, upload-time = "2022-06-30T18:17:25.797Z" }, + { url = "https://files.pythonhosted.org/packages/47/97/137f0e3d2304df2060abb872a5830af809d7559a5a4b6a295afb02728e65/cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", size = 170236, upload-time = "2022-06-30T18:17:51.708Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e3/0a52838832408cfbbf3a59cb19bcd17e64eb33795c9710ca7d29ae10b5b7/cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", size = 178835, upload-time = "2022-06-30T18:17:54.561Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3d/dd085bb831b22ce4d0b7ba8550e6d78960f02f770bbd1314fea3580727f8/cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", size = 170382, upload-time = "2022-06-30T18:18:26.229Z" }, + { url = "https://files.pythonhosted.org/packages/a8/16/06b84a7063a4c0a2b081030fdd976022086da9c14e80a9ed4ba0183a98a9/cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", size = 179079, upload-time = "2022-06-30T18:18:29.006Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "pycparser", version = "2.23", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, + { url = "https://files.pythonhosted.org/packages/e2/63/2bed8323890cb613bbecda807688a31ed11a7fe7afe31f8faaae0206a9a3/cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", size = 171576, upload-time = "2024-09-04T20:44:58.535Z" }, + { url = "https://files.pythonhosted.org/packages/2f/70/80c33b044ebc79527447fd4fbc5455d514c3bb840dede4455de97da39b4d/cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", size = 181229, upload-time = "2024-09-04T20:44:59.963Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820, upload-time = "2024-09-04T20:45:18.762Z" }, + { url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290, upload-time = "2024-09-04T20:45:20.226Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "pycparser", version = "2.23", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*' and implementation_name != 'PyPy'" }, + { name = "pycparser", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4e/3926a1c11f0433791985727965263f788af00db3482d89a7545ca5ecc921/charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", size = 198599, upload-time = "2025-10-14T04:41:53.213Z" }, + { url = "https://files.pythonhosted.org/packages/ec/7c/b92d1d1dcffc34592e71ea19c882b6709e43d20fa498042dea8b815638d7/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", size = 143090, upload-time = "2025-10-14T04:41:54.385Z" }, + { url = "https://files.pythonhosted.org/packages/84/ce/61a28d3bb77281eb24107b937a497f3c43089326d27832a63dcedaab0478/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", size = 139490, upload-time = "2025-10-14T04:41:55.551Z" }, + { url = "https://files.pythonhosted.org/packages/c0/bd/c9e59a91b2061c6f8bb98a150670cb16d4cd7c4ba7d11ad0cdf789155f41/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", size = 155334, upload-time = "2025-10-14T04:41:56.724Z" }, + { url = "https://files.pythonhosted.org/packages/bf/37/f17ae176a80f22ff823456af91ba3bc59df308154ff53aef0d39eb3d3419/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", size = 152823, upload-time = "2025-10-14T04:41:58.236Z" }, + { url = "https://files.pythonhosted.org/packages/bf/fa/cf5bb2409a385f78750e78c8d2e24780964976acdaaed65dbd6083ae5b40/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", size = 147618, upload-time = "2025-10-14T04:41:59.409Z" }, + { url = "https://files.pythonhosted.org/packages/9b/63/579784a65bc7de2d4518d40bb8f1870900163e86f17f21fd1384318c459d/charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", size = 145516, upload-time = "2025-10-14T04:42:00.579Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a9/94ec6266cd394e8f93a4d69cca651d61bf6ac58d2a0422163b30c698f2c7/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", size = 145266, upload-time = "2025-10-14T04:42:01.684Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/d6626eb97764b58c2779fa7928fa7d1a49adb8ce687c2dbba4db003c1939/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", size = 139559, upload-time = "2025-10-14T04:42:02.902Z" }, + { url = "https://files.pythonhosted.org/packages/09/01/ddbe6b01313ba191dbb0a43c7563bc770f2448c18127f9ea4b119c44dff0/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", size = 156653, upload-time = "2025-10-14T04:42:04.005Z" }, + { url = "https://files.pythonhosted.org/packages/95/c8/d05543378bea89296e9af4510b44c704626e191da447235c8fdedfc5b7b2/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", size = 145644, upload-time = "2025-10-14T04:42:05.211Z" }, + { url = "https://files.pythonhosted.org/packages/72/01/2866c4377998ef8a1f6802f6431e774a4c8ebe75b0a6e569ceec55c9cbfb/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", size = 153964, upload-time = "2025-10-14T04:42:06.341Z" }, + { url = "https://files.pythonhosted.org/packages/4a/66/66c72468a737b4cbd7851ba2c522fe35c600575fbeac944460b4fd4a06fe/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", size = 148777, upload-time = "2025-10-14T04:42:07.535Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/d0d56677fdddbffa8ca00ec411f67bb8c947f9876374ddc9d160d4f2c4b3/charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", size = 98687, upload-time = "2025-10-14T04:42:08.678Z" }, + { url = "https://files.pythonhosted.org/packages/00/64/c3bc303d1b586480b1c8e6e1e2191a6d6dd40255244e5cf16763dcec52e6/charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", size = 106115, upload-time = "2025-10-14T04:42:09.793Z" }, + { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, + { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, + { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, + { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, + { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, + { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, + { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, + { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "codecov" +version = "2.1.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", version = "7.2.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "coverage", version = "7.13.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "requests", version = "2.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "requests", version = "2.32.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/bb/594b26d2c85616be6195a64289c578662678afa4910cef2d3ce8417cf73e/codecov-2.1.13.tar.gz", hash = "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c", size = 21416, upload-time = "2023-04-17T23:11:39.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/02/18785edcdf6266cdd6c6dc7635f1cbeefd9a5b4c3bb8aff8bd681e9dd095/codecov-2.1.13-py2.py3-none-any.whl", hash = "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5", size = 16512, upload-time = "2023-04-17T23:11:37.344Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.2.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/45/8b/421f30467e69ac0e414214856798d4bc32da1336df745e49e49ae5c1e2a8/coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59", size = 762575, upload-time = "2023-05-29T20:08:50.273Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/24/be01e62a7bce89bcffe04729c540382caa5a06bee45ae42136c93e2499f5/coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8", size = 200724, upload-time = "2023-05-29T20:07:03.422Z" }, + { url = "https://files.pythonhosted.org/packages/3d/80/7060a445e1d2c9744b683dc935248613355657809d6c6b2716cdf4ca4766/coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb", size = 201024, upload-time = "2023-05-29T20:07:05.694Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9d/926fce7e03dbfc653104c2d981c0fa71f0572a9ebd344d24c573bd6f7c4f/coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6", size = 229528, upload-time = "2023-05-29T20:07:07.307Z" }, + { url = "https://files.pythonhosted.org/packages/d1/3a/67f5d18f911abf96857f6f7e4df37ca840e38179e2cc9ab6c0b9c3380f19/coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2", size = 227842, upload-time = "2023-05-29T20:07:09.331Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bd/1b2331e3a04f4cc9b7b332b1dd0f3a1261dfc4114f8479bebfcc2afee9e8/coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063", size = 228717, upload-time = "2023-05-29T20:07:11.38Z" }, + { url = "https://files.pythonhosted.org/packages/2b/86/3dbf9be43f8bf6a5ca28790a713e18902b2d884bc5fa9512823a81dff601/coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1", size = 234632, upload-time = "2023-05-29T20:07:13.376Z" }, + { url = "https://files.pythonhosted.org/packages/91/e8/469ed808a782b9e8305a08bad8c6fa5f8e73e093bda6546c5aec68275bff/coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353", size = 232875, upload-time = "2023-05-29T20:07:15.093Z" }, + { url = "https://files.pythonhosted.org/packages/29/8f/4fad1c2ba98104425009efd7eaa19af9a7c797e92d40cd2ec026fa1f58cb/coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495", size = 234094, upload-time = "2023-05-29T20:07:17.013Z" }, + { url = "https://files.pythonhosted.org/packages/94/4e/d4e46a214ae857be3d7dc5de248ba43765f60daeb1ab077cb6c1536c7fba/coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818", size = 203184, upload-time = "2023-05-29T20:07:18.69Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/d6730247d8dec2a3dddc520ebe11e2e860f0f98cee3639e23de6cf920255/coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850", size = 204096, upload-time = "2023-05-29T20:07:20.153Z" }, + { url = "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f", size = 200895, upload-time = "2023-05-29T20:07:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe", size = 201120, upload-time = "2023-05-29T20:07:23.765Z" }, + { url = "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3", size = 233178, upload-time = "2023-05-29T20:07:25.281Z" }, + { url = "https://files.pythonhosted.org/packages/c1/49/4d487e2ad5d54ed82ac1101e467e8994c09d6123c91b2a962145f3d262c2/coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f", size = 230754, upload-time = "2023-05-29T20:07:27.044Z" }, + { url = "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb", size = 232558, upload-time = "2023-05-29T20:07:28.743Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a8/12cc7b261f3082cc299ab61f677f7e48d93e35ca5c3c2f7241ed5525ccea/coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833", size = 241509, upload-time = "2023-05-29T20:07:30.434Z" }, + { url = "https://files.pythonhosted.org/packages/04/fa/43b55101f75a5e9115259e8be70ff9279921cb6b17f04c34a5702ff9b1f7/coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97", size = 239924, upload-time = "2023-05-29T20:07:32.065Z" }, + { url = "https://files.pythonhosted.org/packages/68/5f/d2bd0f02aa3c3e0311986e625ccf97fdc511b52f4f1a063e4f37b624772f/coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a", size = 240977, upload-time = "2023-05-29T20:07:34.184Z" }, + { url = "https://files.pythonhosted.org/packages/ba/92/69c0722882643df4257ecc5437b83f4c17ba9e67f15dc6b77bad89b6982e/coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a", size = 203168, upload-time = "2023-05-29T20:07:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/b1/96/c12ed0dfd4ec587f3739f53eb677b9007853fd486ccb0e7d5512a27bab2e/coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562", size = 204185, upload-time = "2023-05-29T20:07:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/52fa1891d1802ab2e1b346d37d349cb41cdd4fd03f724ebbf94e80577687/coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4", size = 201020, upload-time = "2023-05-29T20:07:38.724Z" }, + { url = "https://files.pythonhosted.org/packages/24/df/6765898d54ea20e3197a26d26bb65b084deefadd77ce7de946b9c96dfdc5/coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4", size = 233994, upload-time = "2023-05-29T20:07:40.274Z" }, + { url = "https://files.pythonhosted.org/packages/15/81/b108a60bc758b448c151e5abceed027ed77a9523ecbc6b8a390938301841/coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01", size = 231358, upload-time = "2023-05-29T20:07:41.998Z" }, + { url = "https://files.pythonhosted.org/packages/61/90/c76b9462f39897ebd8714faf21bc985b65c4e1ea6dff428ea9dc711ed0dd/coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6", size = 233316, upload-time = "2023-05-29T20:07:43.539Z" }, + { url = "https://files.pythonhosted.org/packages/04/d6/8cba3bf346e8b1a4fb3f084df7d8cea25a6b6c56aaca1f2e53829be17e9e/coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d", size = 240159, upload-time = "2023-05-29T20:07:44.982Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ea/4a252dc77ca0605b23d477729d139915e753ee89e4c9507630e12ad64a80/coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de", size = 238127, upload-time = "2023-05-29T20:07:46.522Z" }, + { url = "https://files.pythonhosted.org/packages/9f/5c/d9760ac497c41f9c4841f5972d0edf05d50cad7814e86ee7d133ec4a0ac8/coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d", size = 239833, upload-time = "2023-05-29T20:07:47.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/8c/26a95b08059db1cbb01e4b0e6d40f2e9debb628c6ca86b78f625ceaf9bab/coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511", size = 203463, upload-time = "2023-05-29T20:07:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/b7/00/14b00a0748e9eda26e97be07a63cc911108844004687321ddcc213be956c/coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3", size = 204347, upload-time = "2023-05-29T20:07:51.909Z" }, + { url = "https://files.pythonhosted.org/packages/80/d7/67937c80b8fd4c909fdac29292bc8b35d9505312cff6bcab41c53c5b1df6/coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f", size = 200580, upload-time = "2023-05-29T20:07:54.076Z" }, + { url = "https://files.pythonhosted.org/packages/7a/05/084864fa4bbf8106f44fb72a56e67e0cd372d3bf9d893be818338c81af5d/coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb", size = 226237, upload-time = "2023-05-29T20:07:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/67/a2/6fa66a50e6e894286d79a3564f42bd54a9bd27049dc0a63b26d9924f0aa3/coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9", size = 224256, upload-time = "2023-05-29T20:07:58.189Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c0/73f139794c742840b9ab88e2e17fe14a3d4668a166ff95d812ac66c0829d/coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd", size = 225550, upload-time = "2023-05-29T20:08:00.383Z" }, + { url = "https://files.pythonhosted.org/packages/03/ec/6f30b4e0c96ce03b0e64aec46b4af2a8c49b70d1b5d0d69577add757b946/coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a", size = 232440, upload-time = "2023-05-29T20:08:02.495Z" }, + { url = "https://files.pythonhosted.org/packages/22/c1/2f6c1b6f01a0996c9e067a9c780e1824351dbe17faae54388a4477e6d86f/coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959", size = 230897, upload-time = "2023-05-29T20:08:04.382Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d6/53e999ec1bf7498ca4bc5f3b8227eb61db39068d2de5dcc359dec5601b5a/coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02", size = 232024, upload-time = "2023-05-29T20:08:06.031Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/383305500d24122dbed73e505a4d6828f8f3356d1f68ab6d32c781754b81/coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f", size = 203293, upload-time = "2023-05-29T20:08:07.598Z" }, + { url = "https://files.pythonhosted.org/packages/0e/bc/7e3a31534fabb043269f14fb64e2bb2733f85d4cf39e5bbc71357c57553a/coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0", size = 204040, upload-time = "2023-05-29T20:08:09.919Z" }, + { url = "https://files.pythonhosted.org/packages/c6/fc/be19131010930a6cf271da48202c8cc1d3f971f68c02fb2d3a78247f43dc/coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5", size = 200689, upload-time = "2023-05-29T20:08:11.594Z" }, + { url = "https://files.pythonhosted.org/packages/28/d7/9a8de57d87f4bbc6f9a6a5ded1eaac88a89bf71369bb935dac3c0cf2893e/coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5", size = 200986, upload-time = "2023-05-29T20:08:13.228Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e4/e6182e4697665fb594a7f4e4f27cb3a4dd00c2e3d35c5c706765de8c7866/coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9", size = 230648, upload-time = "2023-05-29T20:08:15.11Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e3/f552d5871943f747165b92a924055c5d6daa164ae659a13f9018e22f3990/coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6", size = 228511, upload-time = "2023-05-29T20:08:16.877Z" }, + { url = "https://files.pythonhosted.org/packages/44/55/49f65ccdd4dfd6d5528e966b28c37caec64170c725af32ab312889d2f857/coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e", size = 229852, upload-time = "2023-05-29T20:08:18.47Z" }, + { url = "https://files.pythonhosted.org/packages/0d/31/340428c238eb506feb96d4fb5c9ea614db1149517f22cc7ab8c6035ef6d9/coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050", size = 235578, upload-time = "2023-05-29T20:08:20.298Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ce/97c1dd6592c908425622fe7f31c017d11cf0421729b09101d4de75bcadc8/coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5", size = 234079, upload-time = "2023-05-29T20:08:22.365Z" }, + { url = "https://files.pythonhosted.org/packages/de/a3/5a98dc9e239d0dc5f243ef5053d5b1bdcaa1dee27a691dfc12befeccf878/coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f", size = 234991, upload-time = "2023-05-29T20:08:24.974Z" }, + { url = "https://files.pythonhosted.org/packages/4a/fb/78986d3022e5ccf2d4370bc43a5fef8374f092b3c21d32499dee8e30b7b6/coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e", size = 203160, upload-time = "2023-05-29T20:08:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1c/6b3c9c363fb1433c79128e0d692863deb761b1b78162494abb9e5c328bc0/coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c", size = 204085, upload-time = "2023-05-29T20:08:28.146Z" }, + { url = "https://files.pythonhosted.org/packages/88/da/495944ebf0ad246235a6bd523810d9f81981f9b81c6059ba1f56e943abe0/coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9", size = 200725, upload-time = "2023-05-29T20:08:29.851Z" }, + { url = "https://files.pythonhosted.org/packages/ca/0c/3dfeeb1006c44b911ee0ed915350db30325d01808525ae7cc8d57643a2ce/coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2", size = 201022, upload-time = "2023-05-29T20:08:31.429Z" }, + { url = "https://files.pythonhosted.org/packages/61/af/5964b8d7d9a5c767785644d9a5a63cacba9a9c45cc42ba06d25895ec87be/coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7", size = 229102, upload-time = "2023-05-29T20:08:32.982Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1d/cd467fceb62c371f9adb1d739c92a05d4e550246daa90412e711226bd320/coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e", size = 227441, upload-time = "2023-05-29T20:08:35.044Z" }, + { url = "https://files.pythonhosted.org/packages/fe/57/e4f8ad64d84ca9e759d783a052795f62a9f9111585e46068845b1cb52c2b/coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1", size = 228265, upload-time = "2023-05-29T20:08:36.861Z" }, + { url = "https://files.pythonhosted.org/packages/88/8b/b0d9fe727acae907fa7f1c8194ccb6fe9d02e1c3e9001ecf74c741f86110/coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9", size = 234217, upload-time = "2023-05-29T20:08:38.837Z" }, + { url = "https://files.pythonhosted.org/packages/66/2e/c99fe1f6396d93551aa352c75410686e726cd4ea104479b9af1af22367ce/coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250", size = 232466, upload-time = "2023-05-29T20:08:40.768Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e9/88747b40c8fb4a783b40222510ce6d66170217eb05d7f46462c36b4fa8cc/coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2", size = 233669, upload-time = "2023-05-29T20:08:42.944Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d5/a8e276bc005e42114468d4fe03e0a9555786bc51cbfe0d20827a46c1565a/coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb", size = 203199, upload-time = "2023-05-29T20:08:44.734Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0c/4a848ae663b47f1195abcb09a951751dd61f80b503303b9b9d768e0fd321/coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27", size = 204109, upload-time = "2023-05-29T20:08:46.417Z" }, + { url = "https://files.pythonhosted.org/packages/67/fb/b3b1d7887e1ea25a9608b0776e480e4bbc303ca95a31fd585555ec4fff5a/coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d", size = 193207, upload-time = "2023-05-29T20:08:48.153Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] + +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690, upload-time = "2024-08-04T19:43:07.695Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127, upload-time = "2024-08-04T19:43:10.15Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654, upload-time = "2024-08-04T19:43:12.405Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598, upload-time = "2024-08-04T19:43:14.078Z" }, + { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732, upload-time = "2024-08-04T19:43:16.632Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816, upload-time = "2024-08-04T19:43:19.049Z" }, + { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325, upload-time = "2024-08-04T19:43:21.246Z" }, + { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418, upload-time = "2024-08-04T19:43:22.945Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343, upload-time = "2024-08-04T19:43:25.121Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136, upload-time = "2024-08-04T19:43:26.851Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796, upload-time = "2024-08-04T19:43:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244, upload-time = "2024-08-04T19:43:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279, upload-time = "2024-08-04T19:43:33.581Z" }, + { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859, upload-time = "2024-08-04T19:43:35.301Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549, upload-time = "2024-08-04T19:43:37.578Z" }, + { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477, upload-time = "2024-08-04T19:43:39.92Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134, upload-time = "2024-08-04T19:43:41.453Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910, upload-time = "2024-08-04T19:43:43.037Z" }, + { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348, upload-time = "2024-08-04T19:43:44.787Z" }, + { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230, upload-time = "2024-08-04T19:43:46.707Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983, upload-time = "2024-08-04T19:43:49.082Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221, upload-time = "2024-08-04T19:43:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342, upload-time = "2024-08-04T19:43:53.746Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371, upload-time = "2024-08-04T19:43:55.993Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455, upload-time = "2024-08-04T19:43:57.618Z" }, + { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924, upload-time = "2024-08-04T19:44:00.012Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252, upload-time = "2024-08-04T19:44:01.713Z" }, + { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897, upload-time = "2024-08-04T19:44:03.898Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606, upload-time = "2024-08-04T19:44:05.532Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373, upload-time = "2024-08-04T19:44:07.079Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007, upload-time = "2024-08-04T19:44:09.453Z" }, + { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269, upload-time = "2024-08-04T19:44:11.045Z" }, + { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886, upload-time = "2024-08-04T19:44:12.83Z" }, + { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037, upload-time = "2024-08-04T19:44:15.393Z" }, + { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038, upload-time = "2024-08-04T19:44:17.466Z" }, + { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690, upload-time = "2024-08-04T19:44:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765, upload-time = "2024-08-04T19:44:20.994Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611, upload-time = "2024-08-04T19:44:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671, upload-time = "2024-08-04T19:44:24.418Z" }, + { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368, upload-time = "2024-08-04T19:44:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758, upload-time = "2024-08-04T19:44:29.028Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035, upload-time = "2024-08-04T19:44:30.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839, upload-time = "2024-08-04T19:44:32.412Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569, upload-time = "2024-08-04T19:44:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927, upload-time = "2024-08-04T19:44:36.313Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401, upload-time = "2024-08-04T19:44:38.155Z" }, + { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301, upload-time = "2024-08-04T19:44:39.883Z" }, + { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598, upload-time = "2024-08-04T19:44:41.59Z" }, + { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307, upload-time = "2024-08-04T19:44:43.301Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453, upload-time = "2024-08-04T19:44:45.677Z" }, + { url = "https://files.pythonhosted.org/packages/81/d0/d9e3d554e38beea5a2e22178ddb16587dbcbe9a1ef3211f55733924bf7fa/coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", size = 206674, upload-time = "2024-08-04T19:44:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/cab2dc248d9f45b2b7f9f1f596a4d75a435cb364437c61b51d2eb33ceb0e/coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", size = 207101, upload-time = "2024-08-04T19:44:49.32Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6f/f82f9a500c7c5722368978a5390c418d2a4d083ef955309a8748ecaa8920/coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", size = 236554, upload-time = "2024-08-04T19:44:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/a6/94/d3055aa33d4e7e733d8fa309d9adf147b4b06a82c1346366fc15a2b1d5fa/coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", size = 234440, upload-time = "2024-08-04T19:44:53.464Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/885bcd787d9dd674de4a7d8ec83faf729534c63d05d51d45d4fa168f7102/coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", size = 235889, upload-time = "2024-08-04T19:44:55.165Z" }, + { url = "https://files.pythonhosted.org/packages/f4/63/df50120a7744492710854860783d6819ff23e482dee15462c9a833cc428a/coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", size = 235142, upload-time = "2024-08-04T19:44:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5d/9d0acfcded2b3e9ce1c7923ca52ccc00c78a74e112fc2aee661125b7843b/coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", size = 233805, upload-time = "2024-08-04T19:44:59.033Z" }, + { url = "https://files.pythonhosted.org/packages/c4/56/50abf070cb3cd9b1dd32f2c88f083aab561ecbffbcd783275cb51c17f11d/coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", size = 234655, upload-time = "2024-08-04T19:45:01.398Z" }, + { url = "https://files.pythonhosted.org/packages/25/ee/b4c246048b8485f85a2426ef4abab88e48c6e80c74e964bea5cd4cd4b115/coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", size = 209296, upload-time = "2024-08-04T19:45:03.819Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/96cf86b70b69ea2b12924cdf7cabb8ad10e6130eab8d767a1099fbd2a44f/coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", size = 210137, upload-time = "2024-08-04T19:45:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688, upload-time = "2024-08-04T19:45:08.358Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120, upload-time = "2024-08-04T19:45:11.526Z" }, + { url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249, upload-time = "2024-08-04T19:45:13.202Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237, upload-time = "2024-08-04T19:45:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311, upload-time = "2024-08-04T19:45:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453, upload-time = "2024-08-04T19:45:18.672Z" }, + { url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958, upload-time = "2024-08-04T19:45:20.63Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938, upload-time = "2024-08-04T19:45:23.062Z" }, + { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352, upload-time = "2024-08-04T19:45:25.042Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153, upload-time = "2024-08-04T19:45:27.079Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926, upload-time = "2024-08-04T19:45:28.875Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] + +[[package]] +name = "coverage" +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, + { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, + { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, + { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] + +[[package]] +name = "coverage" +version = "7.13.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/07/1c8099563a8a6c389a31c2d0aa1497cee86d6248bb4b9ba5e779215db9f9/coverage-7.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b4f345f7265cdbdb5ec2521ffff15fa49de6d6c39abf89fc7ad68aa9e3a55f0", size = 219143, upload-time = "2026-02-03T13:59:40.459Z" }, + { url = "https://files.pythonhosted.org/packages/69/39/a892d44af7aa092cab70e0cc5cdbba18eeccfe1d6930695dab1742eef9e9/coverage-7.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96c3be8bae9d0333e403cc1a8eb078a7f928b5650bae94a18fb4820cc993fb9b", size = 219663, upload-time = "2026-02-03T13:59:41.951Z" }, + { url = "https://files.pythonhosted.org/packages/9a/25/9669dcf4c2bb4c3861469e6db20e52e8c11908cf53c14ec9b12e9fd4d602/coverage-7.13.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d6f4a21328ea49d38565b55599e1c02834e76583a6953e5586d65cb1efebd8f8", size = 246424, upload-time = "2026-02-03T13:59:43.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/68/d9766c4e298aca62ea5d9543e1dd1e4e1439d7284815244d8b7db1840bfb/coverage-7.13.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc970575799a9d17d5c3fafd83a0f6ccf5d5117cdc9ad6fbd791e9ead82418b0", size = 248228, upload-time = "2026-02-03T13:59:44.816Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e2/eea6cb4a4bd443741adf008d4cccec83a1f75401df59b6559aca2bdd9710/coverage-7.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ff33b652b3556b05e204ae20793d1f872161b0fa5ec8a9ac76f8430e152ed6", size = 250103, upload-time = "2026-02-03T13:59:46.271Z" }, + { url = "https://files.pythonhosted.org/packages/db/77/664280ecd666c2191610842177e2fab9e5dbdeef97178e2078fed46a3d2c/coverage-7.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7df8759ee57b9f3f7b66799b7660c282f4375bef620ade1686d6a7b03699e75f", size = 247107, upload-time = "2026-02-03T13:59:48.53Z" }, + { url = "https://files.pythonhosted.org/packages/2b/df/2a672eab99e0d0eba52d8a63e47dc92245eee26954d1b2d3c8f7d372151f/coverage-7.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f45c9bcb16bee25a798ccba8a2f6a1251b19de6a0d617bb365d7d2f386c4e20e", size = 248143, upload-time = "2026-02-03T13:59:50.027Z" }, + { url = "https://files.pythonhosted.org/packages/a5/dc/a104e7a87c13e57a358b8b9199a8955676e1703bb372d79722b54978ae45/coverage-7.13.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:318b2e4753cbf611061e01b6cc81477e1cdfeb69c36c4a14e6595e674caadb56", size = 246148, upload-time = "2026-02-03T13:59:52.025Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/e113d3a58dc20b03b7e59aed1e53ebc9ca6167f961876443e002b10e3ae9/coverage-7.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:24db3959de8ee394eeeca89ccb8ba25305c2da9a668dd44173394cbd5aa0777f", size = 246414, upload-time = "2026-02-03T13:59:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/3f/60/a3fd0a6e8d89b488396019a2268b6a1f25ab56d6d18f3be50f35d77b47dc/coverage-7.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be14d0622125edef21b3a4d8cd2d138c4872bf6e38adc90fd92385e3312f406a", size = 247023, upload-time = "2026-02-03T13:59:55.454Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/de4840bb939dbb22ba0648a6d8069fa91c9cf3b3fca8b0d1df461e885b3d/coverage-7.13.3-cp310-cp310-win32.whl", hash = "sha256:53be4aab8ddef18beb6188f3a3fdbf4d1af2277d098d4e618be3a8e6c88e74be", size = 221751, upload-time = "2026-02-03T13:59:57.383Z" }, + { url = "https://files.pythonhosted.org/packages/de/87/233ff8b7ef62fb63f58c78623b50bef69681111e0c4d43504f422d88cda4/coverage-7.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:bfeee64ad8b4aae3233abb77eb6b52b51b05fa89da9645518671b9939a78732b", size = 222686, upload-time = "2026-02-03T13:59:58.825Z" }, + { url = "https://files.pythonhosted.org/packages/ec/09/1ac74e37cf45f17eb41e11a21854f7f92a4c2d6c6098ef4a1becb0c6d8d3/coverage-7.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", size = 219276, upload-time = "2026-02-03T14:00:00.296Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cb/71908b08b21beb2c437d0d5870c4ec129c570ca1b386a8427fcdb11cf89c/coverage-7.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", size = 219776, upload-time = "2026-02-03T14:00:02.414Z" }, + { url = "https://files.pythonhosted.org/packages/09/85/c4f3dd69232887666a2c0394d4be21c60ea934d404db068e6c96aa59cd87/coverage-7.13.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", size = 250196, upload-time = "2026-02-03T14:00:04.197Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", size = 252111, upload-time = "2026-02-03T14:00:05.639Z" }, + { url = "https://files.pythonhosted.org/packages/f0/66/3193985fb2c58e91f94cfbe9e21a6fdf941e9301fe2be9e92c072e9c8f8c/coverage-7.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", size = 254217, upload-time = "2026-02-03T14:00:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c5/78/f0f91556bf1faa416792e537c523c5ef9db9b1d32a50572c102b3d7c45b3/coverage-7.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", size = 250318, upload-time = "2026-02-03T14:00:09.224Z" }, + { url = "https://files.pythonhosted.org/packages/6f/aa/fc654e45e837d137b2c1f3a2cc09b4aea1e8b015acd2f774fa0f3d2ddeba/coverage-7.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", size = 251909, upload-time = "2026-02-03T14:00:10.712Z" }, + { url = "https://files.pythonhosted.org/packages/73/4d/ab53063992add8a9ca0463c9d92cce5994a29e17affd1c2daa091b922a93/coverage-7.13.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", size = 249971, upload-time = "2026-02-03T14:00:12.402Z" }, + { url = "https://files.pythonhosted.org/packages/29/25/83694b81e46fcff9899694a1b6f57573429cdd82b57932f09a698f03eea5/coverage-7.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", size = 249692, upload-time = "2026-02-03T14:00:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ef/d68fc304301f4cb4bf6aefa0045310520789ca38dabdfba9dbecd3f37919/coverage-7.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", size = 250597, upload-time = "2026-02-03T14:00:15.461Z" }, + { url = "https://files.pythonhosted.org/packages/8d/85/240ad396f914df361d0f71e912ddcedb48130c71b88dc4193fe3c0306f00/coverage-7.13.3-cp311-cp311-win32.whl", hash = "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb", size = 221773, upload-time = "2026-02-03T14:00:17.462Z" }, + { url = "https://files.pythonhosted.org/packages/2f/71/165b3a6d3d052704a9ab52d11ea64ef3426745de517dda44d872716213a7/coverage-7.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", size = 222711, upload-time = "2026-02-03T14:00:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/51/d0/0ddc9c5934cdd52639c5df1f1eb0fdab51bb52348f3a8d1c7db9c600d93a/coverage-7.13.3-cp311-cp311-win_arm64.whl", hash = "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", size = 221377, upload-time = "2026-02-03T14:00:20.968Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/330f8e83b143f6668778ed61d17ece9dc48459e9e74669177de02f45fec5/coverage-7.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", size = 219441, upload-time = "2026-02-03T14:00:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/08/e7/29db05693562c2e65bdf6910c0af2fd6f9325b8f43caf7a258413f369e30/coverage-7.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", size = 219801, upload-time = "2026-02-03T14:00:24.186Z" }, + { url = "https://files.pythonhosted.org/packages/90/ae/7f8a78249b02b0818db46220795f8ac8312ea4abd1d37d79ea81db5cae81/coverage-7.13.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", size = 251306, upload-time = "2026-02-03T14:00:25.798Z" }, + { url = "https://files.pythonhosted.org/packages/62/71/a18a53d1808e09b2e9ebd6b47dad5e92daf4c38b0686b4c4d1b2f3e42b7f/coverage-7.13.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", size = 254051, upload-time = "2026-02-03T14:00:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/4a/0a/eb30f6455d04c5a3396d0696cad2df0269ae7444bb322f86ffe3376f7bf9/coverage-7.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", size = 255160, upload-time = "2026-02-03T14:00:29.024Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/a45baac86274ce3ed842dbb84f14560c673ad30535f397d89164ec56c5df/coverage-7.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", size = 251709, upload-time = "2026-02-03T14:00:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/df/dd0dc12f30da11349993f3e218901fdf82f45ee44773596050c8f5a1fb25/coverage-7.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", size = 253083, upload-time = "2026-02-03T14:00:32.14Z" }, + { url = "https://files.pythonhosted.org/packages/ab/32/fc764c8389a8ce95cb90eb97af4c32f392ab0ac23ec57cadeefb887188d3/coverage-7.13.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", size = 251227, upload-time = "2026-02-03T14:00:34.721Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ca/d025e9da8f06f24c34d2da9873957cfc5f7e0d67802c3e34d0caa8452130/coverage-7.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", size = 250794, upload-time = "2026-02-03T14:00:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/45/c7/76bf35d5d488ec8f68682eb8e7671acc50a6d2d1c1182de1d2b6d4ffad3b/coverage-7.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", size = 252671, upload-time = "2026-02-03T14:00:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/bf/10/1921f1a03a7c209e1cb374f81a6b9b68b03cdb3ecc3433c189bc90e2a3d5/coverage-7.13.3-cp312-cp312-win32.whl", hash = "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", size = 221986, upload-time = "2026-02-03T14:00:40.442Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7c/f5d93297f8e125a80c15545edc754d93e0ed8ba255b65e609b185296af01/coverage-7.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", size = 222793, upload-time = "2026-02-03T14:00:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/43/59/c86b84170015b4555ebabca8649bdf9f4a1f737a73168088385ed0f947c4/coverage-7.13.3-cp312-cp312-win_arm64.whl", hash = "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", size = 221410, upload-time = "2026-02-03T14:00:43.726Z" }, + { url = "https://files.pythonhosted.org/packages/81/f3/4c333da7b373e8c8bfb62517e8174a01dcc373d7a9083698e3b39d50d59c/coverage-7.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", size = 219468, upload-time = "2026-02-03T14:00:45.829Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/0714337b7d23630c8de2f4d56acf43c65f8728a45ed529b34410683f7217/coverage-7.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", size = 219839, upload-time = "2026-02-03T14:00:47.407Z" }, + { url = "https://files.pythonhosted.org/packages/12/99/bd6f2a2738144c98945666f90cae446ed870cecf0421c767475fcf42cdbe/coverage-7.13.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", size = 250828, upload-time = "2026-02-03T14:00:49.029Z" }, + { url = "https://files.pythonhosted.org/packages/6f/99/97b600225fbf631e6f5bfd3ad5bcaf87fbb9e34ff87492e5a572ff01bbe2/coverage-7.13.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", size = 253432, upload-time = "2026-02-03T14:00:50.655Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5c/abe2b3490bda26bd4f5e3e799be0bdf00bd81edebedc2c9da8d3ef288fa8/coverage-7.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", size = 254672, upload-time = "2026-02-03T14:00:52.757Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/5d1957c76b40daff53971fe0adb84d9c2162b614280031d1d0653dd010c1/coverage-7.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", size = 251050, upload-time = "2026-02-03T14:00:54.332Z" }, + { url = "https://files.pythonhosted.org/packages/69/dc/dffdf3bfe9d32090f047d3c3085378558cb4eb6778cda7de414ad74581ed/coverage-7.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", size = 252801, upload-time = "2026-02-03T14:00:56.121Z" }, + { url = "https://files.pythonhosted.org/packages/87/51/cdf6198b0f2746e04511a30dc9185d7b8cdd895276c07bdb538e37f1cd50/coverage-7.13.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", size = 250763, upload-time = "2026-02-03T14:00:58.719Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1a/596b7d62218c1d69f2475b69cc6b211e33c83c902f38ee6ae9766dd422da/coverage-7.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", size = 250587, upload-time = "2026-02-03T14:01:01.197Z" }, + { url = "https://files.pythonhosted.org/packages/f7/46/52330d5841ff660f22c130b75f5e1dd3e352c8e7baef5e5fef6b14e3e991/coverage-7.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", size = 252358, upload-time = "2026-02-03T14:01:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/36/8a/e69a5be51923097ba7d5cff9724466e74fe486e9232020ba97c809a8b42b/coverage-7.13.3-cp313-cp313-win32.whl", hash = "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", size = 222007, upload-time = "2026-02-03T14:01:04.876Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/a5a069bcee0d613bdd48ee7637fa73bc09e7ed4342b26890f2df97cc9682/coverage-7.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", size = 222812, upload-time = "2026-02-03T14:01:07.296Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4f/d62ad7dfe32f9e3d4a10c178bb6f98b10b083d6e0530ca202b399371f6c1/coverage-7.13.3-cp313-cp313-win_arm64.whl", hash = "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", size = 221433, upload-time = "2026-02-03T14:01:09.156Z" }, + { url = "https://files.pythonhosted.org/packages/04/b2/4876c46d723d80b9c5b695f1a11bf5f7c3dabf540ec00d6edc076ff025e6/coverage-7.13.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", size = 220162, upload-time = "2026-02-03T14:01:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/fc/04/9942b64a0e0bdda2c109f56bda42b2a59d9d3df4c94b85a323c1cae9fc77/coverage-7.13.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", size = 220510, upload-time = "2026-02-03T14:01:13.038Z" }, + { url = "https://files.pythonhosted.org/packages/5a/82/5cfe1e81eae525b74669f9795f37eb3edd4679b873d79d1e6c1c14ee6c1c/coverage-7.13.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", size = 261801, upload-time = "2026-02-03T14:01:14.674Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ec/a553d7f742fd2cd12e36a16a7b4b3582d5934b496ef2b5ea8abeb10903d4/coverage-7.13.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", size = 263882, upload-time = "2026-02-03T14:01:16.343Z" }, + { url = "https://files.pythonhosted.org/packages/e1/58/8f54a2a93e3d675635bc406de1c9ac8d551312142ff52c9d71b5e533ad45/coverage-7.13.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", size = 266306, upload-time = "2026-02-03T14:01:18.02Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/e593399fd6ea1f00aee79ebd7cc401021f218d34e96682a92e1bae092ff6/coverage-7.13.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", size = 261051, upload-time = "2026-02-03T14:01:19.757Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e5/e9e0f6138b21bcdebccac36fbfde9cf15eb1bbcea9f5b1f35cd1f465fb91/coverage-7.13.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", size = 263868, upload-time = "2026-02-03T14:01:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bf/de72cfebb69756f2d4a2dde35efcc33c47d85cd3ebdf844b3914aac2ef28/coverage-7.13.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", size = 261498, upload-time = "2026-02-03T14:01:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/f2/91/4a2d313a70fc2e98ca53afd1c8ce67a89b1944cd996589a5b1fe7fbb3e5c/coverage-7.13.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", size = 260394, upload-time = "2026-02-03T14:01:24.949Z" }, + { url = "https://files.pythonhosted.org/packages/40/83/25113af7cf6941e779eb7ed8de2a677865b859a07ccee9146d4cc06a03e3/coverage-7.13.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", size = 262579, upload-time = "2026-02-03T14:01:26.703Z" }, + { url = "https://files.pythonhosted.org/packages/1e/19/a5f2b96262977e82fb9aabbe19b4d83561f5d063f18dde3e72f34ffc3b2f/coverage-7.13.3-cp313-cp313t-win32.whl", hash = "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", size = 222679, upload-time = "2026-02-03T14:01:28.553Z" }, + { url = "https://files.pythonhosted.org/packages/81/82/ef1747b88c87a5c7d7edc3704799ebd650189a9158e680a063308b6125ef/coverage-7.13.3-cp313-cp313t-win_amd64.whl", hash = "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", size = 223740, upload-time = "2026-02-03T14:01:30.776Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4c/a67c7bb5b560241c22736a9cb2f14c5034149ffae18630323fde787339e4/coverage-7.13.3-cp313-cp313t-win_arm64.whl", hash = "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", size = 221996, upload-time = "2026-02-03T14:01:32.495Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/677bb43427fed9298905106f39c6520ac75f746f81b8f01104526a8026e4/coverage-7.13.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", size = 219513, upload-time = "2026-02-03T14:01:34.29Z" }, + { url = "https://files.pythonhosted.org/packages/42/53/290046e3bbf8986cdb7366a42dab3440b9983711eaff044a51b11006c67b/coverage-7.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", size = 219850, upload-time = "2026-02-03T14:01:35.984Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/ab41f10345ba2e49d5e299be8663be2b7db33e77ac1b85cd0af985ea6406/coverage-7.13.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", size = 250886, upload-time = "2026-02-03T14:01:38.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/2d/b3f6913ee5a1d5cdd04106f257e5fac5d048992ffc2d9995d07b0f17739f/coverage-7.13.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", size = 253393, upload-time = "2026-02-03T14:01:40.118Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f6/b1f48810ffc6accf49a35b9943636560768f0812330f7456aa87dc39aff5/coverage-7.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", size = 254740, upload-time = "2026-02-03T14:01:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/57/d0/e59c54f9be0b61808f6bc4c8c4346bd79f02dd6bbc3f476ef26124661f20/coverage-7.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", size = 250905, upload-time = "2026-02-03T14:01:44.163Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f7/5291bcdf498bafbee3796bb32ef6966e9915aebd4d0954123c8eae921c32/coverage-7.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", size = 252753, upload-time = "2026-02-03T14:01:45.974Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a9/1dcafa918c281554dae6e10ece88c1add82db685be123e1b05c2056ff3fb/coverage-7.13.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", size = 250716, upload-time = "2026-02-03T14:01:48.844Z" }, + { url = "https://files.pythonhosted.org/packages/44/bb/4ea4eabcce8c4f6235df6e059fbc5db49107b24c4bdffc44aee81aeca5a8/coverage-7.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", size = 250530, upload-time = "2026-02-03T14:01:50.793Z" }, + { url = "https://files.pythonhosted.org/packages/6d/31/4a6c9e6a71367e6f923b27b528448c37f4e959b7e4029330523014691007/coverage-7.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", size = 252186, upload-time = "2026-02-03T14:01:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/27/92/e1451ef6390a4f655dc42da35d9971212f7abbbcad0bdb7af4407897eb76/coverage-7.13.3-cp314-cp314-win32.whl", hash = "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", size = 222253, upload-time = "2026-02-03T14:01:55.071Z" }, + { url = "https://files.pythonhosted.org/packages/8a/98/78885a861a88de020c32a2693487c37d15a9873372953f0c3c159d575a43/coverage-7.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", size = 223069, upload-time = "2026-02-03T14:01:56.95Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fb/3784753a48da58a5337972abf7ca58b1fb0f1bda21bc7b4fae992fd28e47/coverage-7.13.3-cp314-cp314-win_arm64.whl", hash = "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", size = 221633, upload-time = "2026-02-03T14:01:58.758Z" }, + { url = "https://files.pythonhosted.org/packages/40/f9/75b732d9674d32cdbffe801ed5f770786dd1c97eecedef2125b0d25102dc/coverage-7.13.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", size = 220243, upload-time = "2026-02-03T14:02:01.109Z" }, + { url = "https://files.pythonhosted.org/packages/cf/7e/2868ec95de5a65703e6f0c87407ea822d1feb3619600fbc3c1c4fa986090/coverage-7.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", size = 220515, upload-time = "2026-02-03T14:02:02.862Z" }, + { url = "https://files.pythonhosted.org/packages/7d/eb/9f0d349652fced20bcaea0f67fc5777bd097c92369f267975732f3dc5f45/coverage-7.13.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", size = 261874, upload-time = "2026-02-03T14:02:04.727Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a5/6619bc4a6c7b139b16818149a3e74ab2e21599ff9a7b6811b6afde99f8ec/coverage-7.13.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", size = 264004, upload-time = "2026-02-03T14:02:06.634Z" }, + { url = "https://files.pythonhosted.org/packages/29/b7/90aa3fc645a50c6f07881fca4fd0ba21e3bfb6ce3a7078424ea3a35c74c9/coverage-7.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", size = 266408, upload-time = "2026-02-03T14:02:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/55/08bb2a1e4dcbae384e638f0effef486ba5987b06700e481691891427d879/coverage-7.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", size = 260977, upload-time = "2026-02-03T14:02:11.755Z" }, + { url = "https://files.pythonhosted.org/packages/9b/76/8bd4ae055a42d8fb5dd2230e5cf36ff2e05f85f2427e91b11a27fea52ed7/coverage-7.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", size = 263868, upload-time = "2026-02-03T14:02:13.565Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/ba000560f11e9e32ec03df5aa8477242c2d95b379c99ac9a7b2e7fbacb1a/coverage-7.13.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", size = 261474, upload-time = "2026-02-03T14:02:16.069Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/4de4de8f9ca7af4733bfcf4baa440121b7dbb3856daf8428ce91481ff63b/coverage-7.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", size = 260317, upload-time = "2026-02-03T14:02:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/05/71/5cd8436e2c21410ff70be81f738c0dddea91bcc3189b1517d26e0102ccb3/coverage-7.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", size = 262635, upload-time = "2026-02-03T14:02:20.405Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f8/2834bb45bdd70b55a33ec354b8b5f6062fc90e5bb787e14385903a979503/coverage-7.13.3-cp314-cp314t-win32.whl", hash = "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", size = 223035, upload-time = "2026-02-03T14:02:22.323Z" }, + { url = "https://files.pythonhosted.org/packages/26/75/f8290f0073c00d9ae14056d2b84ab92dff21d5370e464cb6cb06f52bf580/coverage-7.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", size = 224142, upload-time = "2026-02-03T14:02:24.143Z" }, + { url = "https://files.pythonhosted.org/packages/03/01/43ac78dfea8946c4a9161bbc034b5549115cb2b56781a4b574927f0d141a/coverage-7.13.3-cp314-cp314t-win_arm64.whl", hash = "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", size = 222166, upload-time = "2026-02-03T14:02:26.005Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", hash = "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", size = 211237, upload-time = "2026-02-03T14:02:27.986Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "execnet" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/c8/d382dc7a1e68a165f4a4ab612a08b20d8534a7d20cc590630b734ca0c54b/execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af", size = 161098, upload-time = "2023-07-09T17:14:03.468Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/9c/a079946da30fac4924d92dbc617e5367d454954494cf1e71567bcc4e00ee/execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41", size = 37097, upload-time = "2023-07-09T17:14:01.888Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "flake8" +version = "3.9.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mccabe", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pycodestyle", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pyflakes", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/47/15b267dfe7e03dca4c4c06e7eadbd55ef4dfd368b13a0bab36d708b14366/flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", size = 164777, upload-time = "2021-05-08T19:52:34.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/80/35a0716e5d5101e643404dabd20f07f5528a21f3ef4032d31a49c913237b/flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907", size = 73147, upload-time = "2021-05-08T19:52:32.476Z" }, +] + +[[package]] +name = "flake8" +version = "5.0.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "mccabe", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.8.1'" }, + { name = "pycodestyle", version = "2.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.8.1'" }, + { name = "pyflakes", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.8.1'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/00/9808c62b2d529cefc69ce4e4a1ea42c0f855effa55817b7327ec5b75e60a/flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db", size = 145862, upload-time = "2022-08-03T23:21:27.108Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/a0/b881b63a17a59d9d07f5c0cc91a29182c8e8a9aa2bde5b3b2b16519c02f4/flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248", size = 61897, upload-time = "2022-08-03T23:21:25.027Z" }, +] + +[[package]] +name = "flake8" +version = "7.1.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", +] +dependencies = [ + { name = "mccabe", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" }, + { name = "pycodestyle", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" }, + { name = "pyflakes", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/16/3f2a0bb700ad65ac9663262905a025917c020a3f92f014d2ba8964b4602c/flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd", size = 48119, upload-time = "2025-02-16T18:45:44.296Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/f8/08d37b2cd89da306e3520bd27f8a85692122b42b56c0c2c3784ff09c022f/flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a", size = 57745, upload-time = "2025-02-16T18:45:42.351Z" }, +] + +[[package]] +name = "flake8" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "mccabe", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pycodestyle", version = "2.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pyflakes", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "zipp", version = "3.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/82/f6e29c8d5c098b6be61460371c2c5591f4a335923639edec43b3830650a4/importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4", size = 53569, upload-time = "2023-06-18T21:44:35.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/94/64287b38c7de4c90683630338cf28f129decbba0a44f0c6db35a873c73c4/importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5", size = 22934, upload-time = "2023-06-18T21:44:33.441Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "zipp", version = "3.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384, upload-time = "2024-02-02T16:31:22.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206, upload-time = "2024-02-02T16:30:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079, upload-time = "2024-02-02T16:30:06.5Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620, upload-time = "2024-02-02T16:30:08.31Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818, upload-time = "2024-02-02T16:30:09.577Z" }, + { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493, upload-time = "2024-02-02T16:30:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630, upload-time = "2024-02-02T16:30:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745, upload-time = "2024-02-02T16:30:14.222Z" }, + { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021, upload-time = "2024-02-02T16:30:16.032Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659, upload-time = "2024-02-02T16:30:17.079Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213, upload-time = "2024-02-02T16:30:18.251Z" }, + { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219, upload-time = "2024-02-02T16:30:19.988Z" }, + { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098, upload-time = "2024-02-02T16:30:21.063Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014, upload-time = "2024-02-02T16:30:22.926Z" }, + { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220, upload-time = "2024-02-02T16:30:24.76Z" }, + { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756, upload-time = "2024-02-02T16:30:25.877Z" }, + { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988, upload-time = "2024-02-02T16:30:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718, upload-time = "2024-02-02T16:30:28.111Z" }, + { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317, upload-time = "2024-02-02T16:30:29.214Z" }, + { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670, upload-time = "2024-02-02T16:30:30.915Z" }, + { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224, upload-time = "2024-02-02T16:30:32.09Z" }, + { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215, upload-time = "2024-02-02T16:30:33.081Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069, upload-time = "2024-02-02T16:30:34.148Z" }, + { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452, upload-time = "2024-02-02T16:30:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462, upload-time = "2024-02-02T16:30:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869, upload-time = "2024-02-02T16:30:37.834Z" }, + { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906, upload-time = "2024-02-02T16:30:39.366Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296, upload-time = "2024-02-02T16:30:40.413Z" }, + { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038, upload-time = "2024-02-02T16:30:42.243Z" }, + { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572, upload-time = "2024-02-02T16:30:43.326Z" }, + { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127, upload-time = "2024-02-02T16:30:44.418Z" }, + { url = "https://files.pythonhosted.org/packages/a7/88/a940e11827ea1c136a34eca862486178294ae841164475b9ab216b80eb8e/MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", size = 13982, upload-time = "2024-02-02T16:30:46.06Z" }, + { url = "https://files.pythonhosted.org/packages/cb/06/0d28bd178db529c5ac762a625c335a9168a7a23f280b4db9c95e97046145/MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", size = 26335, upload-time = "2024-02-02T16:30:47.676Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1d/c4f5016f87ced614eacc7d5fb85b25bcc0ff53e8f058d069fc8cbfdc3c7a/MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", size = 25557, upload-time = "2024-02-02T16:30:48.936Z" }, + { url = "https://files.pythonhosted.org/packages/b3/fb/c18b8c9fbe69e347fdbf782c6478f1bc77f19a830588daa224236678339b/MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", size = 25245, upload-time = "2024-02-02T16:30:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/2f/69/30d29adcf9d1d931c75001dd85001adad7374381c9c2086154d9f6445be6/MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", size = 31013, upload-time = "2024-02-02T16:30:51.795Z" }, + { url = "https://files.pythonhosted.org/packages/3a/03/63498d05bd54278b6ca340099e5b52ffb9cdf2ee4f2d9b98246337e21689/MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", size = 30178, upload-time = "2024-02-02T16:30:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/68/79/11b4fe15124692f8673b603433e47abca199a08ecd2a4851bfbdc97dc62d/MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", size = 30429, upload-time = "2024-02-02T16:30:53.983Z" }, + { url = "https://files.pythonhosted.org/packages/ed/88/408bdbf292eb86f03201c17489acafae8358ba4e120d92358308c15cea7c/MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", size = 16633, upload-time = "2024-02-02T16:30:55.317Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4c/3577a52eea1880538c435176bc85e5b3379b7ab442327ccd82118550758f/MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", size = 17215, upload-time = "2024-02-02T16:30:56.6Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ff/2c942a82c35a49df5de3a630ce0a8456ac2969691b230e530ac12314364c/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", size = 18192, upload-time = "2024-02-02T16:30:57.715Z" }, + { url = "https://files.pythonhosted.org/packages/4f/14/6f294b9c4f969d0c801a4615e221c1e084722ea6114ab2114189c5b8cbe0/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", size = 14072, upload-time = "2024-02-02T16:30:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/81/d4/fd74714ed30a1dedd0b82427c02fa4deec64f173831ec716da11c51a50aa/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", size = 26928, upload-time = "2024-02-02T16:30:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bd/50319665ce81bb10e90d1cf76f9e1aa269ea6f7fa30ab4521f14d122a3df/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", size = 26106, upload-time = "2024-02-02T16:31:01.582Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6f/f2b0f675635b05f6afd5ea03c094557bdb8622fa8e673387444fe8d8e787/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68", size = 25781, upload-time = "2024-02-02T16:31:02.71Z" }, + { url = "https://files.pythonhosted.org/packages/51/e0/393467cf899b34a9d3678e78961c2c8cdf49fb902a959ba54ece01273fb1/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", size = 30518, upload-time = "2024-02-02T16:31:04.392Z" }, + { url = "https://files.pythonhosted.org/packages/f6/02/5437e2ad33047290dafced9df741d9efc3e716b75583bbd73a9984f1b6f7/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", size = 29669, upload-time = "2024-02-02T16:31:05.53Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7d/968284145ffd9d726183ed6237c77938c021abacde4e073020f920e060b2/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", size = 29933, upload-time = "2024-02-02T16:31:06.636Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f3/ecb00fc8ab02b7beae8699f34db9357ae49d9f21d4d3de6f305f34fa949e/MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", size = 16656, upload-time = "2024-02-02T16:31:07.767Z" }, + { url = "https://files.pythonhosted.org/packages/92/21/357205f03514a49b293e214ac39de01fadd0970a6e05e4bf1ddd0ffd0881/MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", size = 17206, upload-time = "2024-02-02T16:31:08.843Z" }, + { url = "https://files.pythonhosted.org/packages/0f/31/780bb297db036ba7b7bbede5e1d7f1e14d704ad4beb3ce53fb495d22bc62/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", size = 18193, upload-time = "2024-02-02T16:31:10.155Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/d77701bbef72892affe060cdacb7a2ed7fd68dae3b477a8642f15ad3b132/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", size = 14073, upload-time = "2024-02-02T16:31:11.442Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a7/1e558b4f78454c8a3a0199292d96159eb4d091f983bc35ef258314fe7269/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", size = 26486, upload-time = "2024-02-02T16:31:12.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5a/360da85076688755ea0cceb92472923086993e86b5613bbae9fbc14136b0/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", size = 25685, upload-time = "2024-02-02T16:31:13.726Z" }, + { url = "https://files.pythonhosted.org/packages/6a/18/ae5a258e3401f9b8312f92b028c54d7026a97ec3ab20bfaddbdfa7d8cce8/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", size = 25338, upload-time = "2024-02-02T16:31:14.812Z" }, + { url = "https://files.pythonhosted.org/packages/0b/cc/48206bd61c5b9d0129f4d75243b156929b04c94c09041321456fd06a876d/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", size = 30439, upload-time = "2024-02-02T16:31:15.946Z" }, + { url = "https://files.pythonhosted.org/packages/d1/06/a41c112ab9ffdeeb5f77bc3e331fdadf97fa65e52e44ba31880f4e7f983c/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", size = 29531, upload-time = "2024-02-02T16:31:17.13Z" }, + { url = "https://files.pythonhosted.org/packages/02/8c/ab9a463301a50dab04d5472e998acbd4080597abc048166ded5c7aa768c8/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", size = 29823, upload-time = "2024-02-02T16:31:18.247Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/9bc18da763496b055d8e98ce476c8e718dcfd78157e17f555ce6dd7d0895/MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", size = 16658, upload-time = "2024-02-02T16:31:19.583Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f8/4da07de16f10551ca1f640c92b5f316f9394088b183c6a57183df6de5ae4/MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", size = 17211, upload-time = "2024-02-02T16:31:20.96Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, + { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, + { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, +] + +[[package]] +name = "mccabe" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/06/18/fa675aa501e11d6d6ca0ae73a101b2f3571a565e0f7d38e062eec18a91ee/mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f", size = 8612, upload-time = "2017-01-26T22:13:15.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/89/479dc97e18549e21354893e4ee4ef36db1d237534982482c3681ee6e7b57/mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", size = 8556, upload-time = "2017-01-26T22:13:14.36Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "nose2" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/07/9b/c2d9fc1a7e143a3f84feeff8373cecc3a0122fe2bf2486486c9557a77ba8/nose2-0.13.0.tar.gz", hash = "sha256:57c68ad676ef4b88b50694937eb52f4943aa1ca261074b4909b6e163046b46f0", size = 166892, upload-time = "2023-04-29T18:30:39.782Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/95/9ad320e22ee0007264d0efd75a094ad93d41f3b9b65282661edb71d16019/nose2-0.13.0-py3-none-any.whl", hash = "sha256:e29017c9309fe2c8b8a1f1db7a0e7526acaba0c3990f32a0d961e6a7bc5cc190", size = 205622, upload-time = "2023-04-29T18:30:38.116Z" }, +] + +[[package]] +name = "nose2" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/a6/f29c21026c40476ce3994ac55e16ef60b9c1d2a88d02c3fc20b07d253dab/nose2-0.15.1.tar.gz", hash = "sha256:36770f519df5becd3cbfe0bee4abbfbf9b9f6b4eb4e03361d282b7efcfc4f0df", size = 169809, upload-time = "2024-06-01T03:20:11.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/e6/6babe53a1dbfa55f6d30eb7408f4c4994658e5f27e3dbbb2b437912e5a32/nose2-0.15.1-py3-none-any.whl", hash = "sha256:564450c0c4f1602dfe171902ceb4726cc56658af7a620ae1826f1ffc86b09a86", size = 211274, upload-time = "2024-06-01T03:20:04.423Z" }, +] + +[[package]] +name = "numpy" +version = "1.21.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/45/b7/de7b8e67f2232c26af57c205aaad29fe17754f793404f59c8a730c7a191a/numpy-1.21.6.zip", hash = "sha256:ecb55251139706669fdec2ff073c98ef8e9a84473e51e716211b41aa0f18e656", size = 10274544, upload-time = "2022-04-12T15:23:55.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/c6/05ae3c7f75b596e1bb3d78131c331eada9376a03d1af9801bd40e4675023/numpy-1.21.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8737609c3bbdd48e380d463134a35ffad3b22dc56295eff6f79fd85bd0eeeb25", size = 27203123, upload-time = "2022-04-12T14:48:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/4a/72/a3379f83172f1431d7949138373e3a24beed68184c9362dab1b4d465be26/numpy-1.21.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fdffbfb6832cd0b300995a2b08b8f6fa9f6e856d562800fea9182316d99c4e8e", size = 16965603, upload-time = "2022-04-12T14:48:41.371Z" }, + { url = "https://files.pythonhosted.org/packages/26/e7/4a6f579af8186372b03e8480e47df309520d91cfead8759b64dd5ac62688/numpy-1.21.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3820724272f9913b597ccd13a467cc492a0da6b05df26ea09e78b171a0bb9da6", size = 12364595, upload-time = "2022-04-12T14:49:01.002Z" }, + { url = "https://files.pythonhosted.org/packages/57/ba/d8cbdfd507b541bb247beff24d9d7304ac8ffc379cf585701187d45d4512/numpy-1.21.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f17e562de9edf691a42ddb1eb4a5541c20dd3f9e65b09ded2beb0799c0cf29bb", size = 13019278, upload-time = "2022-04-12T14:49:20.896Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7b/036000a55680e6c7eb81502b0aa27ce0ed65d4d8805613909967d9f8baf6/numpy-1.21.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f30427731561ce75d7048ac254dbe47a2ba576229250fb60f0fb74db96501a1", size = 15906004, upload-time = "2022-04-12T14:49:44.734Z" }, + { url = "https://files.pythonhosted.org/packages/b0/77/ff8bbe56ff6cbbdbdb8a641c67cee61e29b2e8bfbb18732c2e1d2961fe4d/numpy-1.21.6-cp310-cp310-win32.whl", hash = "sha256:d4bf4d43077db55589ffc9009c0ba0a94fa4908b9586d6ccce2e0b164c86303c", size = 11706182, upload-time = "2022-04-12T14:50:03.227Z" }, + { url = "https://files.pythonhosted.org/packages/ec/03/93702ca9c4bd61791e46c80ff1f24943febb2317484cf7e8207688bbbd95/numpy-1.21.6-cp310-cp310-win_amd64.whl", hash = "sha256:d136337ae3cc69aa5e447e78d8e1514be8c3ec9b54264e680cf0b4bd9011574f", size = 14008913, upload-time = "2022-04-12T14:50:24.392Z" }, + { url = "https://files.pythonhosted.org/packages/32/dd/43d8b2b2ebf424f6555271a4c9f5b50dc3cc0aafa66c72b4d36863f71358/numpy-1.21.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6aaf96c7f8cebc220cdfc03f1d5a31952f027dda050e5a703a0d1c396075e3e7", size = 16894122, upload-time = "2022-04-12T14:50:50.66Z" }, + { url = "https://files.pythonhosted.org/packages/06/78/b184f13f5461812a17a90b380d70a93fa3532460f0af9d72b0d93d8bc4ff/numpy-1.21.6-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:67c261d6c0a9981820c3a149d255a76918278a6b03b6a036800359aba1256d46", size = 13670697, upload-time = "2022-04-12T14:51:11.878Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ad/ff3b21ebfe79a4d25b4a4f8e5cf9fd44a204adb6b33c09010f566f51027a/numpy-1.21.6-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a6be4cb0ef3b8c9250c19cc122267263093eee7edd4e3fa75395dfda8c17a8e2", size = 15702369, upload-time = "2022-04-12T14:51:35.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0d/86662f93102e42545cdf031da4fddf0ace9030ec67478932a628afc5973b/numpy-1.21.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4068a8c44014b2d55f3c3f574c376b2494ca9cc73d2f1bd692382b6dffe3db", size = 12974170, upload-time = "2022-04-12T14:51:55.493Z" }, + { url = "https://files.pythonhosted.org/packages/cd/eb/f6f3258e7b0e0cc5c327778312bf4ee4978c8514aa28e97119ee206f6e60/numpy-1.21.6-cp37-cp37m-win32.whl", hash = "sha256:7c7e5fa88d9ff656e067876e4736379cc962d185d5cd808014a8a928d529ef4e", size = 11680505, upload-time = "2022-04-12T14:52:14.056Z" }, + { url = "https://files.pythonhosted.org/packages/97/9f/da37cc4a188a1d5d203d65ab28d6504e17594b5342e0c1dc5610ee6f4535/numpy-1.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bcb238c9c96c00d3085b264e5c1a1207672577b93fa666c3b14a45240b14123a", size = 13969236, upload-time = "2022-04-12T14:52:35.45Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e2/b2df1f664d644e690b40179fc0a07c163c6decf986c7adee8a85a094e8ce/numpy-1.21.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:82691fda7c3f77c90e62da69ae60b5ac08e87e775b09813559f8901a88266552", size = 27127297, upload-time = "2022-04-12T14:53:15Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d4/be63d2bed7d10f443dee42469623326b6bc51c9e5cd096ebb7227bca456f/numpy-1.21.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:643843bcc1c50526b3a71cd2ee561cf0d8773f062c8cbaf9ffac9fdf573f83ab", size = 16926225, upload-time = "2022-04-12T14:53:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/0d/21/036363516c06737135ee58741e9c0af4899348ce3c5f5e04379240edd090/numpy-1.21.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:357768c2e4451ac241465157a3e929b265dfac85d9214074985b1786244f2ef3", size = 12329630, upload-time = "2022-04-12T14:53:59.949Z" }, + { url = "https://files.pythonhosted.org/packages/6a/52/a1dcf14b8e81d49c14112663290ee2ed545bd04988170138284a613bd926/numpy-1.21.6-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f411b2c3f3d76bba0865b35a425157c5dcf54937f82bbeb3d3c180789dd66a6", size = 13719672, upload-time = "2022-04-12T14:54:20.7Z" }, + { url = "https://files.pythonhosted.org/packages/d5/43/e88bb1fb7d040ae8e0e06e749341b13f57701aab11fe9d71c99af6202c5c/numpy-1.21.6-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4aa48afdce4660b0076a00d80afa54e8a97cd49f457d68a4342d188a09451c1a", size = 15749422, upload-time = "2022-04-12T14:54:44.87Z" }, + { url = "https://files.pythonhosted.org/packages/86/c7/3f68d0a8dcc9458879c614707e6ffaf64a108664cfbba9702d3ba7ca4c82/numpy-1.21.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a96eef20f639e6a97d23e57dd0c1b1069a7b4fd7027482a4c5c451cd7732f4", size = 13026205, upload-time = "2022-04-12T14:55:05.182Z" }, + { url = "https://files.pythonhosted.org/packages/6f/47/453023bd298f8b0be092d8a8bdd4b21f87a8c639ecb724a94cd75e23d216/numpy-1.21.6-cp38-cp38-win32.whl", hash = "sha256:5c3c8def4230e1b959671eb959083661b4a0d2e9af93ee339c7dada6759a9470", size = 11707551, upload-time = "2022-04-12T14:55:23.823Z" }, + { url = "https://files.pythonhosted.org/packages/48/5f/db4550e1c68206814a577ebd92c0dd082f3628fd7fc96725d44a521b0c92/numpy-1.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:bf2ec4b75d0e9356edea834d1de42b31fe11f726a81dfb2c2112bc1eaa508fcf", size = 14009785, upload-time = "2022-04-12T14:55:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/83/eb/a6a0d7fc8e718776c5c710692ea027607104710cba813c4b869182179334/numpy-1.21.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4391bd07606be175aafd267ef9bea87cf1b8210c787666ce82073b05f202add1", size = 27202394, upload-time = "2022-04-12T14:56:24.193Z" }, + { url = "https://files.pythonhosted.org/packages/4c/62/07402945bd5d5cf515a5f0cbc7263abf02ec0ddf3b19fbdc4af7537cd4d0/numpy-1.21.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67f21981ba2f9d7ba9ade60c9e8cbaa8cf8e9ae51673934480e45cf55e953673", size = 16965163, upload-time = "2022-04-12T14:56:50.429Z" }, + { url = "https://files.pythonhosted.org/packages/44/56/041e886b4a8da813b7ec297c270fb3582d2ae8b7f33e106eb5c7a5e9184c/numpy-1.21.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee5ec40fdd06d62fe5d4084bef4fd50fd4bb6bfd2bf519365f569dc470163ab0", size = 12364236, upload-time = "2022-04-12T14:57:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/61/f4/f01a8989e53a437ad660ab86c91514bec3d5067393e4a844b259f5a103de/numpy-1.21.6-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1dbe1c91269f880e364526649a52eff93ac30035507ae980d2fed33aaee633ac", size = 13721583, upload-time = "2022-04-12T14:57:30.408Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f2/0bdcf2c40ef144cbbc9e0947eea831a145a98b0e5f8438fc09cf7fda0b35/numpy-1.21.6-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d9caa9d5e682102453d96a0ee10c7241b72859b01a941a397fd965f23b3e016b", size = 15734025, upload-time = "2022-04-12T14:57:54.442Z" }, + { url = "https://files.pythonhosted.org/packages/76/7f/830cf169eede1b855538f962e3a70c31755db6423652695b813ed04ff54e/numpy-1.21.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58459d3bad03343ac4b1b42ed14d571b8743dc80ccbf27444f266729df1d6f5b", size = 13020515, upload-time = "2022-04-12T14:58:14.213Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b5/7178d5a22427a9195ac69d6ec150415734f7a7a19d1142f82b89ead1dac4/numpy-1.21.6-cp39-cp39-win32.whl", hash = "sha256:7f5ae4f304257569ef3b948810816bc87c9146e8c446053539947eedeaa32786", size = 11706846, upload-time = "2022-04-12T14:58:32.433Z" }, + { url = "https://files.pythonhosted.org/packages/4d/04/bcd62448f2e772bc90a73ba21bacaa19817ae9905ae639969462862bd071/numpy-1.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:e31f0bb5928b793169b87e3d1e070f2342b22d5245c755e2b81caa29756246c3", size = 14008965, upload-time = "2022-04-12T14:58:53.527Z" }, + { url = "https://files.pythonhosted.org/packages/2e/5a/6f3e280a10de48395053a559bfcb3b2221b74b57d062c1d6307fc965f549/numpy-1.21.6-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd1c8f6bd65d07d3810b90d02eba7997e32abbdf1277a481d698969e921a3be0", size = 15159527, upload-time = "2022-04-12T14:59:16.193Z" }, +] + +[[package]] +name = "numpy" +version = "1.24.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/9b/027bec52c633f6556dba6b722d9a0befb40498b9ceddd29cbe67a45a127c/numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", size = 10911229, upload-time = "2023-06-26T13:39:33.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/80/6cdfb3e275d95155a34659163b83c09e3a3ff9f1456880bec6cc63d71083/numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", size = 19789140, upload-time = "2023-06-26T13:22:33.184Z" }, + { url = "https://files.pythonhosted.org/packages/64/5f/3f01d753e2175cfade1013eea08db99ba1ee4bdb147ebcf3623b75d12aa7/numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", size = 13854297, upload-time = "2023-06-26T13:22:59.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b3/2f9c21d799fa07053ffa151faccdceeb69beec5a010576b8991f614021f7/numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", size = 13995611, upload-time = "2023-06-26T13:23:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/10/be/ae5bf4737cb79ba437879915791f6f26d92583c738d7d960ad94e5c36adf/numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", size = 17282357, upload-time = "2023-06-26T13:23:51.446Z" }, + { url = "https://files.pythonhosted.org/packages/c0/64/908c1087be6285f40e4b3e79454552a701664a079321cff519d8c7051d06/numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", size = 12429222, upload-time = "2023-06-26T13:24:13.849Z" }, + { url = "https://files.pythonhosted.org/packages/22/55/3d5a7c1142e0d9329ad27cece17933b0e2ab4e54ddc5c1861fbfeb3f7693/numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", size = 14841514, upload-time = "2023-06-26T13:24:38.129Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cc/5ed2280a27e5dab12994c884f1f4d8c3bd4d885d02ae9e52a9d213a6a5e2/numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", size = 19775508, upload-time = "2023-06-26T13:25:08.882Z" }, + { url = "https://files.pythonhosted.org/packages/c0/bc/77635c657a3668cf652806210b8662e1aff84b818a55ba88257abf6637a8/numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", size = 13840033, upload-time = "2023-06-26T13:25:33.417Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4c/96cdaa34f54c05e97c1c50f39f98d608f96f0677a6589e64e53104e22904/numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", size = 13991951, upload-time = "2023-06-26T13:25:55.725Z" }, + { url = "https://files.pythonhosted.org/packages/22/97/dfb1a31bb46686f09e68ea6ac5c63fdee0d22d7b23b8f3f7ea07712869ef/numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", size = 17278923, upload-time = "2023-06-26T13:26:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/35/e2/76a11e54139654a324d107da1d98f99e7aa2a7ef97cfd7c631fba7dbde71/numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", size = 12422446, upload-time = "2023-06-26T13:26:49.302Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ec/ebef2f7d7c28503f958f0f8b992e7ce606fb74f9e891199329d5f5f87404/numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", size = 14834466, upload-time = "2023-06-26T13:27:16.029Z" }, + { url = "https://files.pythonhosted.org/packages/11/10/943cfb579f1a02909ff96464c69893b1d25be3731b5d3652c2e0cf1281ea/numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", size = 19780722, upload-time = "2023-06-26T13:27:49.573Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ae/f53b7b265fdc701e663fbb322a8e9d4b14d9cb7b2385f45ddfabfc4327e4/numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", size = 13843102, upload-time = "2023-06-26T13:28:12.288Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/2586a50ad72e8dbb1d8381f837008a0321a3516dfd7cb57fc8cf7e4bb06b/numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", size = 14039616, upload-time = "2023-06-26T13:28:35.659Z" }, + { url = "https://files.pythonhosted.org/packages/98/5d/5738903efe0ecb73e51eb44feafba32bdba2081263d40c5043568ff60faf/numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", size = 17316263, upload-time = "2023-06-26T13:29:09.272Z" }, + { url = "https://files.pythonhosted.org/packages/d1/57/8d328f0b91c733aa9aa7ee540dbc49b58796c862b4fbcb1146c701e888da/numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", size = 12455660, upload-time = "2023-06-26T13:29:33.434Z" }, + { url = "https://files.pythonhosted.org/packages/69/65/0d47953afa0ad569d12de5f65d964321c208492064c38fe3b0b9744f8d44/numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", size = 14868112, upload-time = "2023-06-26T13:29:58.385Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cd/d5b0402b801c8a8b56b04c1e85c6165efab298d2f0ab741c2406516ede3a/numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", size = 19816549, upload-time = "2023-06-26T13:30:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/638aaa446f39113a3ed38b37a66243e21b38110d021bfcb940c383e120f2/numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", size = 13879950, upload-time = "2023-06-26T13:31:01.787Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/91894916e50627476cff1a4e4363ab6179d01077d71b9afed41d9e1f18bf/numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9", size = 14030228, upload-time = "2023-06-26T13:31:26.696Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/d7b2a0417af6428440c0ad7cb9799073e507b1a465f827d058b826236964/numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", size = 17311170, upload-time = "2023-06-26T13:31:56.615Z" }, + { url = "https://files.pythonhosted.org/packages/18/9d/e02ace5d7dfccee796c37b995c63322674daf88ae2f4a4724c5dd0afcc91/numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", size = 12454918, upload-time = "2023-06-26T13:32:16.8Z" }, + { url = "https://files.pythonhosted.org/packages/63/38/6cc19d6b8bfa1d1a459daf2b3fe325453153ca7019976274b6f33d8b5663/numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", size = 14867441, upload-time = "2023-06-26T13:32:40.521Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fd/8dff40e25e937c94257455c237b9b6bf5a30d42dd1cc11555533be099492/numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", size = 19156590, upload-time = "2023-06-26T13:33:10.36Z" }, + { url = "https://files.pythonhosted.org/packages/42/e7/4bf953c6e05df90c6d351af69966384fed8e988d0e8c54dad7103b59f3ba/numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", size = 16705744, upload-time = "2023-06-26T13:33:36.703Z" }, + { url = "https://files.pythonhosted.org/packages/fc/dd/9106005eb477d022b60b3817ed5937a43dad8fd1f20b0610ea8a32fcb407/numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", size = 14734290, upload-time = "2023-06-26T13:34:05.409Z" }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, +] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", version = "24.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "attrs", version = "25.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "attrs", version = "25.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060, upload-time = "2023-10-26T04:26:04.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" }, +] + +[[package]] +name = "packaging" +version = "24.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9", size = 147882, upload-time = "2024-03-10T09:39:28.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", size = 53488, upload-time = "2024-03-10T09:39:25.947Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandas" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "numpy", version = "1.21.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7.1'" }, + { name = "python-dateutil", marker = "python_full_version < '3.7.1'" }, + { name = "pytz", marker = "python_full_version < '3.7.1'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/e4/828bb9c2474ff6016e5ce96a78220d485436d5468c23068f4f6c2eb9cff8/pandas-1.1.5.tar.gz", hash = "sha256:f10fc41ee3c75a474d3bdf68d396f10782d013d7f67db99c0efbfd0acb99701b", size = 5229894, upload-time = "2020-12-07T15:11:10.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/ac/5ec0601ce698235cad8559dfe0ae5ace903a359d617a8c4c033f9879faf8/pandas-1.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26fa92d3ac743a149a31b21d6f4337b0594b6302ea5575b37af9ca9611e8981a", size = 9950654, upload-time = "2020-12-07T15:08:54.293Z" }, + { url = "https://files.pythonhosted.org/packages/12/ce/5da11d852dc0bacce86ec8abe802a6a55bfe20823845b4dc609b0280cf90/pandas-1.1.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c16d59c15d946111d2716856dd5479221c9e4f2f5c7bc2d617f39d870031e086", size = 9095405, upload-time = "2020-12-07T15:09:02.728Z" }, + { url = "https://files.pythonhosted.org/packages/fd/70/e8eee0cbddf926bf51958c7d6a86bc69167c300fa2ba8e592330a2377d1b/pandas-1.1.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3be7a7a0ca71a2640e81d9276f526bca63505850add10206d0da2e8a0a325dae", size = 9528950, upload-time = "2020-12-07T15:09:10.999Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/44c05005f9eafda56651b9f27c2b89c803d78ca2136e43b72c0e7a5033ee/pandas-1.1.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:573fba5b05bf2c69271a32e52399c8de599e4a15ab7cec47d3b9c904125ab788", size = 9439516, upload-time = "2020-12-07T15:09:19.537Z" }, + { url = "https://files.pythonhosted.org/packages/e1/08/b059d45fc7a14ff9dcfa010324a3e79ff24a333cc0a81ae097accf236213/pandas-1.1.5-cp37-cp37m-win32.whl", hash = "sha256:21b5a2b033380adbdd36b3116faaf9a4663e375325831dac1b519a44f9e439bb", size = 7767532, upload-time = "2020-12-07T15:09:26.726Z" }, + { url = "https://files.pythonhosted.org/packages/47/17/5c4b04caa8fe1dca2aa940dcc00319aa77c84fbdb71f83869a0900cac660/pandas-1.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:24c7f8d4aee71bfa6401faeba367dd654f696a77151a8a28bc2013f7ced4af98", size = 8749370, upload-time = "2020-12-07T15:09:34.264Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/fd9ccaf9c328ead3024950f0799d24fb4ad328d3949e21d68e2e2338df07/pandas-1.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2860a97cbb25444ffc0088b457da0a79dc79f9c601238a3e0644312fcc14bf11", size = 10091168, upload-time = "2020-12-07T15:09:43.337Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/416c4b26a1cbcc57640733b738189a66f1fc2be568bdc88a0356a5a7e33a/pandas-1.1.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5008374ebb990dad9ed48b0f5d0038124c73748f5384cc8c46904dace27082d9", size = 9035568, upload-time = "2020-12-07T15:09:51.9Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f4/ede7c643939c132b0692a737800747ce5ba0e8068af27730dfda936c9bf1/pandas-1.1.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2c2f7c670ea4e60318e4b7e474d56447cf0c7d83b3c2a5405a0dbb2600b9c48e", size = 9328954, upload-time = "2020-12-07T15:10:00.109Z" }, + { url = "https://files.pythonhosted.org/packages/97/7f/865e78beaac43e57b7dd066cc28cd85a2b5687334188dbeca439bf13845b/pandas-1.1.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0a643bae4283a37732ddfcecab3f62dd082996021b980f580903f4e8e01b3c5b", size = 9647042, upload-time = "2020-12-07T15:10:08.56Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b0/63aa0d048e4c3be3f0d2c3851cde44ce644bac3f527f9239df5ca15947d1/pandas-1.1.5-cp38-cp38-win32.whl", hash = "sha256:5447ea7af4005b0daf695a316a423b96374c9c73ffbd4533209c5ddc369e644b", size = 7897350, upload-time = "2020-12-07T15:10:15.748Z" }, + { url = "https://files.pythonhosted.org/packages/fd/f5/2b5dc56305fcb4bf0c176035ca1a2ed4fc2652362a8eb58f5b0d9bccfe22/pandas-1.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:4c62e94d5d49db116bef1bd5c2486723a292d79409fc9abd51adf9e05329101d", size = 8950447, upload-time = "2020-12-07T15:10:23.694Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4f/6a2bdef087388a59245976008e0e0b4297659fd9796112d85cce766c3cd1/pandas-1.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:731568be71fba1e13cae212c362f3d2ca8932e83cb1b85e3f1b4dd77d019254a", size = 10275477, upload-time = "2020-12-07T15:10:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/69/c1/bf486a9d03e1658bbab5212315dd0df49ddaccaa69f81c0dc17820ede978/pandas-1.1.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c61c043aafb69329d0f961b19faa30b1dab709dd34c9388143fc55680059e55a", size = 8965380, upload-time = "2020-12-07T15:10:41.801Z" }, + { url = "https://files.pythonhosted.org/packages/d0/e1/64f9c1fccd5eebdf177e917e5499b6da266c409b6eba75b93a8cd3b8ccee/pandas-1.1.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2b1c6cd28a0dfda75c7b5957363333f01d370936e4c6276b7b8e696dd500582a", size = 9310065, upload-time = "2020-12-07T15:10:50.044Z" }, + { url = "https://files.pythonhosted.org/packages/19/96/6bd84872fcdf082c4a60b2b996a96cce13295da7198f38abd01c92d04bcb/pandas-1.1.5-cp39-cp39-win32.whl", hash = "sha256:c94ff2780a1fd89f190390130d6d36173ca59fcfb3fe0ff596f9a56518191ccb", size = 7868105, upload-time = "2020-12-07T15:10:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/46/f9/d78955c88e045fa220392cc311e41764f8decf868594b5f92d8fa1d1b39e/pandas-1.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:edda9bacc3843dfbeebaf7a701763e68e741b08fccb889c003b0a52f0ee95782", size = 8936158, upload-time = "2020-12-07T15:11:04.835Z" }, +] + +[[package]] +name = "pandas" +version = "1.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", +] +dependencies = [ + { name = "numpy", version = "1.21.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.7.1' and python_full_version < '3.8'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.7.1' and python_full_version < '3.8'" }, + { name = "pytz", marker = "python_full_version >= '3.7.1' and python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/f0/f99700ef327e51d291efdf4a6de29e685c4d198cbf8531541fc84d169e0e/pandas-1.3.5.tar.gz", hash = "sha256:1e4285f5de1012de20ca46b188ccf33521bff61ba5c5ebd78b4fb28e5416a9f1", size = 4736591, upload-time = "2021-12-12T14:46:59.173Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/e9/170a5dab5e166610492f74e87b2998e848e920ed137844077c7a04b6c752/pandas-1.3.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:62d5b5ce965bae78f12c1c0df0d387899dd4211ec0bdc52822373f13a3a022b9", size = 17688091, upload-time = "2021-12-12T14:30:49.858Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a2/fb72a79cced335f86a5761d2e44cd750a54fe5eac5a1d239489430c6ef2b/pandas-1.3.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:adfeb11be2d54f275142c8ba9bf67acee771b7186a5745249c7d5a06c670136b", size = 11326728, upload-time = "2021-12-12T14:37:09.719Z" }, + { url = "https://files.pythonhosted.org/packages/fb/77/e1bb0628d38fef6e12914e4bb853231a9b406f355aa72fc3427a2fe21327/pandas-1.3.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60a8c055d58873ad81cae290d974d13dd479b82cbb975c3e1fa2cf1920715296", size = 10282482, upload-time = "2021-12-12T14:37:45.132Z" }, + { url = "https://files.pythonhosted.org/packages/4a/dd/ff9ff3a3330e4e37d8146e19ef7a33324072dabea7c355afe820b6c38a2d/pandas-1.3.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd541ab09e1f80a2a1760032d665f6e032d8e44055d602d65eeea6e6e85498cb", size = 10901427, upload-time = "2021-12-12T14:38:49.263Z" }, + { url = "https://files.pythonhosted.org/packages/ff/7a/1ce22f0f009ee31878f717bd5b3221e993a7ebc02391d7a315982c2224dc/pandas-1.3.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2651d75b9a167cc8cc572cf787ab512d16e316ae00ba81874b560586fa1325e0", size = 11549648, upload-time = "2021-12-12T14:39:44.997Z" }, + { url = "https://files.pythonhosted.org/packages/68/a7/96cde70dd2723a9cb79978a390cb3de448a72baafc949ef1fce1e804dbd0/pandas-1.3.5-cp310-cp310-win_amd64.whl", hash = "sha256:aaf183a615ad790801fa3cf2fa450e5b6d23a54684fe386f7e3208f8b9bfbef6", size = 10188149, upload-time = "2021-12-12T14:40:32.159Z" }, + { url = "https://files.pythonhosted.org/packages/44/d9/fa9cb383b482b574e6926eabc437fe57b59908a7ed940612c8c308471872/pandas-1.3.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:344295811e67f8200de2390093aeb3c8309f5648951b684d8db7eee7d1c81fb7", size = 11011237, upload-time = "2021-12-12T14:41:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7e/67f85be335fd9de515c4efe90d2d4d4a545e97c713febd2d230b0bd945be/pandas-1.3.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552020bf83b7f9033b57cbae65589c01e7ef1544416122da0c79140c93288f56", size = 10677966, upload-time = "2021-12-12T14:41:52.955Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0c/23764c4635dcb0a784a787498d56847b90ebf974e65f4ab4053a5d97b1a5/pandas-1.3.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cce0c6bbeb266b0e39e35176ee615ce3585233092f685b6a82362523e59e5b4", size = 11285953, upload-time = "2021-12-12T14:42:21.045Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/9f2e2053a6fd149009105a67e129ceab3e140a7915be6cbd4b13612cd3fa/pandas-1.3.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d28a3c65463fd0d0ba8bbb7696b23073efee0510783340a44b08f5e96ffce0c", size = 11380777, upload-time = "2021-12-12T14:42:47.575Z" }, + { url = "https://files.pythonhosted.org/packages/66/f3/b739d389ba70aeceb8e4eda1d7e7577b4fa44a7351d6d10fc5c6543bdb91/pandas-1.3.5-cp37-cp37m-win32.whl", hash = "sha256:a62949c626dd0ef7de11de34b44c6475db76995c2064e2d99c6498c3dba7fe58", size = 8929395, upload-time = "2021-12-12T14:43:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/b2/56/f886ed6f1777ffa9d54c6e80231b69db8a1f52dcc33f5967b06a105dcfe0/pandas-1.3.5-cp37-cp37m-win_amd64.whl", hash = "sha256:8025750767e138320b15ca16d70d5cdc1886e8f9cc56652d89735c016cd8aea6", size = 9970412, upload-time = "2021-12-12T14:43:18.667Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4f/154ccb493f76514a158b881c7c4995c8529b7d041612801eba633c2581bf/pandas-1.3.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fe95bae4e2d579812865db2212bb733144e34d0c6785c0685329e5b60fcb85dd", size = 11179803, upload-time = "2021-12-12T14:43:31.76Z" }, + { url = "https://files.pythonhosted.org/packages/79/f3/d6ccc0699c540c0f9f6302a553eea1efd9133f2c2a746987a96bcc22c253/pandas-1.3.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f261553a1e9c65b7a310302b9dbac31cf0049a51695c14ebe04e4bfd4a96f02", size = 10896053, upload-time = "2021-12-12T14:43:42.337Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/c4879904ed1706883eb0b126f1f4baa0992dfd61ad2aac7a7af82f01b256/pandas-1.3.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b6dbec5f3e6d5dc80dcfee250e0a2a652b3f28663492f7dab9a24416a48ac39", size = 11533670, upload-time = "2021-12-12T14:44:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/5e/09/0ea554021747118e47002f99fbbd67fb1e8ed91c564aaab687a338a97177/pandas-1.3.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3bc49af96cd6285030a64779de5b3688633a07eb75c124b0747134a63f4c05f", size = 11632727, upload-time = "2021-12-12T14:44:11.819Z" }, + { url = "https://files.pythonhosted.org/packages/05/4a/abc3bd95179a45b1f29b1f973acde14bee48fab60bf483fa15e2521e013b/pandas-1.3.5-cp38-cp38-win32.whl", hash = "sha256:b6b87b2fb39e6383ca28e2829cddef1d9fc9e27e55ad91ca9c435572cdba51bf", size = 9082087, upload-time = "2021-12-12T14:44:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/ba/9c/55bbffd9a2c55360eb2a1da5634f553d39db9df17da037989e2215c941b4/pandas-1.3.5-cp38-cp38-win_amd64.whl", hash = "sha256:a395692046fd8ce1edb4c6295c35184ae0c2bbe787ecbe384251da609e27edcb", size = 10192785, upload-time = "2021-12-12T14:44:35.364Z" }, + { url = "https://files.pythonhosted.org/packages/f6/56/4bad0852c2c7885c0b86e4344463969e1ffb91439479708e282eb87951c5/pandas-1.3.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd971a3f08b745a75a86c00b97f3007c2ea175951286cdda6abe543e687e5f2f", size = 11314167, upload-time = "2021-12-12T14:44:52.912Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8d/e7c3bc1c0f10a6bcb399eae3f570eacda7bdf0044f30659c819bea5d659f/pandas-1.3.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37f06b59e5bc05711a518aa10beaec10942188dccb48918bb5ae602ccbc9f1a0", size = 10916021, upload-time = "2021-12-12T14:45:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/64d407c9ec379a252ba659eb5086ffe5550f83674a43eca680b4a0992eb2/pandas-1.3.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c21778a688d3712d35710501f8001cdbf96eb70a7c587a3d5613573299fdca6", size = 11518536, upload-time = "2021-12-12T14:45:50.136Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ef/bfac4ddfff7b3817fa75ff8e4defe60c2b534a79ad734ab5ff2634dd9575/pandas-1.3.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3345343206546545bc26a05b4602b6a24385b5ec7c75cb6059599e3d56831da2", size = 11634519, upload-time = "2021-12-12T14:46:12.243Z" }, + { url = "https://files.pythonhosted.org/packages/02/b5/dbe37470c47b3e89c8aa6390ac4fd0baa76fc8b72126def9ddc71b77aeb3/pandas-1.3.5-cp39-cp39-win32.whl", hash = "sha256:c69406a2808ba6cf580c2255bcf260b3f214d2664a3a4197d0e640f573b46fd3", size = 9052239, upload-time = "2021-12-12T14:46:30.072Z" }, + { url = "https://files.pythonhosted.org/packages/17/81/8c5bdee74f7fb4edd8e58c3388954568a49230a61f6595020e7def31d889/pandas-1.3.5-cp39-cp39-win_amd64.whl", hash = "sha256:32e1a26d5ade11b547721a72f9bfc4bd113396947606e00d5b4a5b79b3dcb006", size = 10177282, upload-time = "2021-12-12T14:46:50.495Z" }, +] + +[[package]] +name = "pandas" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "python-dateutil", marker = "python_full_version == '3.8.*'" }, + { name = "pytz", marker = "python_full_version == '3.8.*'" }, + { name = "tzdata", marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/a7/824332581e258b5aa4f3763ecb2a797e5f9a54269044ba2e50ac19936b32/pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c", size = 5284455, upload-time = "2023-06-28T23:19:33.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/b2/0d4a5729ce1ce11630c4fc5d5522a33b967b3ca146c210f58efde7c40e99/pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8", size = 11760908, upload-time = "2023-06-28T23:15:57.001Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f6/f620ca62365d83e663a255a41b08d2fc2eaf304e0b8b21bb6d62a7390fe3/pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f", size = 10823486, upload-time = "2023-06-28T23:16:06.863Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/cb4234bc9b968c57e81861b306b10cd8170272c57b098b724d3de5eda124/pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183", size = 11571897, upload-time = "2023-06-28T23:16:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/e3/59/35a2892bf09ded9c1bf3804461efe772836a5261ef5dfb4e264ce813ff99/pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0", size = 12306421, upload-time = "2023-06-28T23:16:23.26Z" }, + { url = "https://files.pythonhosted.org/packages/94/71/3a0c25433c54bb29b48e3155b959ac78f4c4f2f06f94d8318aac612cb80f/pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210", size = 9540792, upload-time = "2023-06-28T23:16:30.876Z" }, + { url = "https://files.pythonhosted.org/packages/ed/30/b97456e7063edac0e5a405128065f0cd2033adfe3716fb2256c186bd41d0/pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e", size = 10664333, upload-time = "2023-06-28T23:16:39.209Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/a5e5133421b49e901a12e02a6a7ef3a0130e10d13db8cb657fdd0cba3b90/pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8", size = 11645672, upload-time = "2023-06-28T23:16:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/8f/bb/aea1fbeed5b474cb8634364718abe9030d7cc7a30bf51f40bd494bbc89a2/pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26", size = 10693229, upload-time = "2023-06-28T23:16:56.397Z" }, + { url = "https://files.pythonhosted.org/packages/d6/90/e7d387f1a416b14e59290baa7a454a90d719baebbf77433ff1bdcc727800/pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d", size = 11581591, upload-time = "2023-06-28T23:17:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/d0/28/88b81881c056376254618fad622a5e94b5126db8c61157ea1910cd1c040a/pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df", size = 12219370, upload-time = "2023-06-28T23:17:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a5/212b9039e25bf8ebb97e417a96660e3dc925dacd3f8653d531b8f7fd9be4/pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd", size = 9482935, upload-time = "2023-06-28T23:17:21.376Z" }, + { url = "https://files.pythonhosted.org/packages/9e/71/756a1be6bee0209d8c0d8c5e3b9fc72c00373f384a4017095ec404aec3ad/pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b", size = 10607692, upload-time = "2023-06-28T23:17:28.824Z" }, + { url = "https://files.pythonhosted.org/packages/78/a8/07dd10f90ca915ed914853cd57f79bfc22e1ef4384ab56cb4336d2fc1f2a/pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061", size = 11653303, upload-time = "2023-06-28T23:17:36.329Z" }, + { url = "https://files.pythonhosted.org/packages/53/c3/f8e87361f7fdf42012def602bfa2a593423c729f5cb7c97aed7f51be66ac/pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5", size = 10710932, upload-time = "2023-06-28T23:17:49.875Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/828d50c81ce0f434163bf70b925a0eec6076808e0bca312a79322b141f66/pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089", size = 11684018, upload-time = "2023-06-28T23:18:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7f/5b047effafbdd34e52c9e2d7e44f729a0655efafb22198c45cf692cdc157/pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0", size = 12353723, upload-time = "2023-06-28T23:18:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ae/26a2eda7fa581347d69e51f93892493b2074ef3352ac71033c9f32c52389/pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02", size = 9646403, upload-time = "2023-06-28T23:18:24.328Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/ea362eef61f05553aaf1a24b3e96b2d0603f5dc71a3bd35688a24ed88843/pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78", size = 10777638, upload-time = "2023-06-28T23:18:30.947Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c7/cfef920b7b457dff6928e824896cb82367650ea127d048ee0b820026db4f/pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b", size = 11834160, upload-time = "2023-06-28T23:18:40.332Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1c/689c9d99bc4e5d366a5fd871f0bcdee98a6581e240f96b78d2d08f103774/pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e", size = 10862752, upload-time = "2023-06-28T23:18:50.016Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b8/4d082f41c27c95bf90485d1447b647cc7e5680fea75e315669dc6e4cb398/pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b", size = 11715852, upload-time = "2023-06-28T23:19:00.594Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0d/91a9fd2c202f2b1d97a38ab591890f86480ecbb596cbc56d035f6f23fdcc/pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641", size = 12398496, upload-time = "2023-06-28T23:19:11.78Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/d8aa0a2c4f3f5f8ea59fb946c8eafe8f508090ca73e2b08a9af853c1103e/pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682", size = 9630766, upload-time = "2023-06-28T23:19:18.182Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f2/0ad053856debbe90c83de1b4f05915f85fd2146f20faf9daa3b320d36df3/pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc", size = 10755902, upload-time = "2023-06-28T23:19:25.151Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, + { url = "https://files.pythonhosted.org/packages/56/b4/52eeb530a99e2a4c55ffcd352772b599ed4473a0f892d127f4147cf0f88e/pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", size = 11567720, upload-time = "2025-09-29T23:33:06.209Z" }, + { url = "https://files.pythonhosted.org/packages/48/4a/2d8b67632a021bced649ba940455ed441ca854e57d6e7658a6024587b083/pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", size = 10810302, upload-time = "2025-09-29T23:33:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/13/e6/d2465010ee0569a245c975dc6967b801887068bc893e908239b1f4b6c1ac/pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", size = 12154874, upload-time = "2025-09-29T23:33:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/1f/18/aae8c0aa69a386a3255940e9317f793808ea79d0a525a97a903366bb2569/pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", size = 12790141, upload-time = "2025-09-29T23:34:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/f7/26/617f98de789de00c2a444fbe6301bb19e66556ac78cff933d2c98f62f2b4/pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", size = 13208697, upload-time = "2025-09-29T23:34:21.835Z" }, + { url = "https://files.pythonhosted.org/packages/b9/fb/25709afa4552042bd0e15717c75e9b4a2294c3dc4f7e6ea50f03c5136600/pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", size = 13879233, upload-time = "2025-09-29T23:34:35.079Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/7be05277859a7bc399da8ba68b88c96b27b48740b6cf49688899c6eb4176/pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", size = 11359119, upload-time = "2025-09-29T23:34:46.339Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/1e/b184654a856e75e975a6ee95d6577b51c271cd92cb2b020c9378f53e0032/pandas-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d64ce01eb9cdca96a15266aa679ae50212ec52757c79204dbc7701a222401850", size = 10313247, upload-time = "2026-01-21T15:50:15.775Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5e/e04a547ad0f0183bf151fd7c7a477468e3b85ff2ad231c566389e6cc9587/pandas-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:613e13426069793aa1ec53bdcc3b86e8d32071daea138bbcf4fa959c9cdaa2e2", size = 9913131, upload-time = "2026-01-21T15:50:18.611Z" }, + { url = "https://files.pythonhosted.org/packages/a2/93/bb77bfa9fc2aba9f7204db807d5d3fb69832ed2854c60ba91b4c65ba9219/pandas-3.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0192fee1f1a8e743b464a6607858ee4b071deb0b118eb143d71c2a1d170996d5", size = 10741925, upload-time = "2026-01-21T15:50:21.058Z" }, + { url = "https://files.pythonhosted.org/packages/62/fb/89319812eb1d714bfc04b7f177895caeba8ab4a37ef6712db75ed786e2e0/pandas-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b853319dec8d5e0c8b875374c078ef17f2269986a78168d9bd57e49bf650ae", size = 11245979, upload-time = "2026-01-21T15:50:23.413Z" }, + { url = "https://files.pythonhosted.org/packages/a9/63/684120486f541fc88da3862ed31165b3b3e12b6a1c7b93be4597bc84e26c/pandas-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:707a9a877a876c326ae2cb640fbdc4ef63b0a7b9e2ef55c6df9942dcee8e2af9", size = 11756337, upload-time = "2026-01-21T15:50:25.932Z" }, + { url = "https://files.pythonhosted.org/packages/39/92/7eb0ad232312b59aec61550c3c81ad0743898d10af5df7f80bc5e5065416/pandas-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:afd0aa3d0b5cda6e0b8ffc10dbcca3b09ef3cbcd3fe2b27364f85fdc04e1989d", size = 12325517, upload-time = "2026-01-21T15:50:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/51/27/bf9436dd0a4fc3130acec0828951c7ef96a0631969613a9a35744baf27f6/pandas-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:113b4cca2614ff7e5b9fee9b6f066618fe73c5a83e99d721ffc41217b2bf57dd", size = 9881576, upload-time = "2026-01-21T15:50:30.149Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2b/c618b871fce0159fd107516336e82891b404e3f340821853c2fc28c7830f/pandas-3.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c14837eba8e99a8da1527c0280bba29b0eb842f64aa94982c5e21227966e164b", size = 9140807, upload-time = "2026-01-21T15:50:32.308Z" }, + { url = "https://files.pythonhosted.org/packages/0b/38/db33686f4b5fa64d7af40d96361f6a4615b8c6c8f1b3d334eee46ae6160e/pandas-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9803b31f5039b3c3b10cc858c5e40054adb4b29b4d81cb2fd789f4121c8efbcd", size = 10334013, upload-time = "2026-01-21T15:50:34.771Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7b/9254310594e9774906bacdd4e732415e1f86ab7dbb4b377ef9ede58cd8ec/pandas-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14c2a4099cd38a1d18ff108168ea417909b2dea3bd1ebff2ccf28ddb6a74d740", size = 9874154, upload-time = "2026-01-21T15:50:36.67Z" }, + { url = "https://files.pythonhosted.org/packages/63/d4/726c5a67a13bc66643e66d2e9ff115cead482a44fc56991d0c4014f15aaf/pandas-3.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d257699b9a9960e6125686098d5714ac59d05222bef7a5e6af7a7fd87c650801", size = 10384433, upload-time = "2026-01-21T15:50:39.132Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2e/9211f09bedb04f9832122942de8b051804b31a39cfbad199a819bb88d9f3/pandas-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69780c98f286076dcafca38d8b8eee1676adf220199c0a39f0ecbf976b68151a", size = 10864519, upload-time = "2026-01-21T15:50:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/00/8d/50858522cdc46ac88b9afdc3015e298959a70a08cd21e008a44e9520180c/pandas-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4a66384f017240f3858a4c8a7cf21b0591c3ac885cddb7758a589f0f71e87ebb", size = 11394124, upload-time = "2026-01-21T15:50:43.377Z" }, + { url = "https://files.pythonhosted.org/packages/86/3f/83b2577db02503cd93d8e95b0f794ad9d4be0ba7cb6c8bcdcac964a34a42/pandas-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be8c515c9bc33989d97b89db66ea0cececb0f6e3c2a87fcc8b69443a6923e95f", size = 11920444, upload-time = "2026-01-21T15:50:45.932Z" }, + { url = "https://files.pythonhosted.org/packages/64/2d/4f8a2f192ed12c90a0aab47f5557ece0e56b0370c49de9454a09de7381b2/pandas-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a453aad8c4f4e9f166436994a33884442ea62aa8b27d007311e87521b97246e1", size = 9730970, upload-time = "2026-01-21T15:50:47.962Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/ff571be435cf1e643ca98d0945d76732c0b4e9c37191a89c8550b105eed1/pandas-3.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:da768007b5a33057f6d9053563d6b74dd6d029c337d93c6d0d22a763a5c2ecc0", size = 9041950, upload-time = "2026-01-21T15:50:50.422Z" }, + { url = "https://files.pythonhosted.org/packages/6f/fa/7f0ac4ca8877c57537aaff2a842f8760e630d8e824b730eb2e859ffe96ca/pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6", size = 10307129, upload-time = "2026-01-21T15:50:52.877Z" }, + { url = "https://files.pythonhosted.org/packages/6f/11/28a221815dcea4c0c9414dfc845e34a84a6a7dabc6da3194498ed5ba4361/pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f", size = 9850201, upload-time = "2026-01-21T15:50:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/ba/da/53bbc8c5363b7e5bd10f9ae59ab250fc7a382ea6ba08e4d06d8694370354/pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70", size = 10354031, upload-time = "2026-01-21T15:50:57.463Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a3/51e02ebc2a14974170d51e2410dfdab58870ea9bcd37cda15bd553d24dc4/pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e", size = 10861165, upload-time = "2026-01-21T15:50:59.32Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/05a51e3cac11d161472b8297bd41723ea98013384dd6d76d115ce3482f9b/pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3", size = 11359359, upload-time = "2026-01-21T15:51:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/ee/56/ba620583225f9b85a4d3e69c01df3e3870659cc525f67929b60e9f21dcd1/pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e", size = 11912907, upload-time = "2026-01-21T15:51:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8c/c6638d9f67e45e07656b3826405c5cc5f57f6fd07c8b2572ade328c86e22/pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e", size = 9732138, upload-time = "2026-01-21T15:51:07.569Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bf/bd1335c3bf1770b6d8fed2799993b11c4971af93bb1b729b9ebbc02ca2ec/pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be", size = 9033568, upload-time = "2026-01-21T15:51:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c6/f5e2171914d5e29b9171d495344097d54e3ffe41d2d85d8115baba4dc483/pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98", size = 10741936, upload-time = "2026-01-21T15:51:11.693Z" }, + { url = "https://files.pythonhosted.org/packages/51/88/9a0164f99510a1acb9f548691f022c756c2314aad0d8330a24616c14c462/pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327", size = 10393884, upload-time = "2026-01-21T15:51:14.197Z" }, + { url = "https://files.pythonhosted.org/packages/e0/53/b34d78084d88d8ae2b848591229da8826d1e65aacf00b3abe34023467648/pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb", size = 10310740, upload-time = "2026-01-21T15:51:16.093Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d3/bee792e7c3d6930b74468d990604325701412e55d7aaf47460a22311d1a5/pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812", size = 10700014, upload-time = "2026-01-21T15:51:18.818Z" }, + { url = "https://files.pythonhosted.org/packages/55/db/2570bc40fb13aaed1cbc3fbd725c3a60ee162477982123c3adc8971e7ac1/pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08", size = 11323737, upload-time = "2026-01-21T15:51:20.784Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2e/297ac7f21c8181b62a4cccebad0a70caf679adf3ae5e83cb676194c8acc3/pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c", size = 11771558, upload-time = "2026-01-21T15:51:22.977Z" }, + { url = "https://files.pythonhosted.org/packages/0a/46/e1c6876d71c14332be70239acce9ad435975a80541086e5ffba2f249bcf6/pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa", size = 10473771, upload-time = "2026-01-21T15:51:25.285Z" }, + { url = "https://files.pythonhosted.org/packages/c0/db/0270ad9d13c344b7a36fa77f5f8344a46501abf413803e885d22864d10bf/pandas-3.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:597c08fb9fef0edf1e4fa2f9828dd27f3d78f9b8c9b4a748d435ffc55732310b", size = 10312075, upload-time = "2026-01-21T15:51:28.5Z" }, + { url = "https://files.pythonhosted.org/packages/09/9f/c176f5e9717f7c91becfe0f55a52ae445d3f7326b4a2cf355978c51b7913/pandas-3.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:447b2d68ac5edcbf94655fe909113a6dba6ef09ad7f9f60c80477825b6c489fe", size = 9900213, upload-time = "2026-01-21T15:51:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e7/63ad4cc10b257b143e0a5ebb04304ad806b4e1a61c5da25f55896d2ca0f4/pandas-3.0.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debb95c77ff3ed3ba0d9aa20c3a2f19165cc7956362f9873fce1ba0a53819d70", size = 10428768, upload-time = "2026-01-21T15:51:33.018Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/4e4c2d8210f20149fd2248ef3fff26623604922bd564d915f935a06dd63d/pandas-3.0.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fedabf175e7cd82b69b74c30adbaa616de301291a5231138d7242596fc296a8d", size = 10882954, upload-time = "2026-01-21T15:51:35.287Z" }, + { url = "https://files.pythonhosted.org/packages/c6/60/c9de8ac906ba1f4d2250f8a951abe5135b404227a55858a75ad26f84db47/pandas-3.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:412d1a89aab46889f3033a386912efcdfa0f1131c5705ff5b668dda88305e986", size = 11430293, upload-time = "2026-01-21T15:51:37.57Z" }, + { url = "https://files.pythonhosted.org/packages/a1/69/806e6637c70920e5787a6d6896fd707f8134c2c55cd761e7249a97b7dc5a/pandas-3.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e979d22316f9350c516479dd3a92252be2937a9531ed3a26ec324198a99cdd49", size = 11952452, upload-time = "2026-01-21T15:51:39.618Z" }, + { url = "https://files.pythonhosted.org/packages/cb/de/918621e46af55164c400ab0ef389c9d969ab85a43d59ad1207d4ddbe30a5/pandas-3.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:083b11415b9970b6e7888800c43c82e81a06cd6b06755d84804444f0007d6bb7", size = 9851081, upload-time = "2026-01-21T15:51:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/a1/3562a18dd0bd8c73344bfa26ff90c53c72f827df119d6d6b1dacc84d13e3/pandas-3.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:5db1e62cb99e739fa78a28047e861b256d17f88463c76b8dafc7c1338086dca8", size = 9174610, upload-time = "2026-01-21T15:51:44.312Z" }, + { url = "https://files.pythonhosted.org/packages/ce/26/430d91257eaf366f1737d7a1c158677caaf6267f338ec74e3a1ec444111c/pandas-3.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:697b8f7d346c68274b1b93a170a70974cdc7d7354429894d5927c1effdcccd73", size = 10761999, upload-time = "2026-01-21T15:51:46.899Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1a/954eb47736c2b7f7fe6a9d56b0cb6987773c00faa3c6451a43db4beb3254/pandas-3.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8cb3120f0d9467ed95e77f67a75e030b67545bcfa08964e349252d674171def2", size = 10410279, upload-time = "2026-01-21T15:51:48.89Z" }, + { url = "https://files.pythonhosted.org/packages/20/fc/b96f3a5a28b250cd1b366eb0108df2501c0f38314a00847242abab71bb3a/pandas-3.0.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33fd3e6baa72899746b820c31e4b9688c8e1b7864d7aec2de7ab5035c285277a", size = 10330198, upload-time = "2026-01-21T15:51:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/90/b3/d0e2952f103b4fbef1ef22d0c2e314e74fc9064b51cee30890b5e3286ee6/pandas-3.0.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8942e333dc67ceda1095227ad0febb05a3b36535e520154085db632c40ad084", size = 10728513, upload-time = "2026-01-21T15:51:53.387Z" }, + { url = "https://files.pythonhosted.org/packages/76/81/832894f286df828993dc5fd61c63b231b0fb73377e99f6c6c369174cf97e/pandas-3.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:783ac35c4d0fe0effdb0d67161859078618b1b6587a1af15928137525217a721", size = 11345550, upload-time = "2026-01-21T15:51:55.329Z" }, + { url = "https://files.pythonhosted.org/packages/34/a0/ed160a00fb4f37d806406bc0a79a8b62fe67f29d00950f8d16203ff3409b/pandas-3.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:125eb901e233f155b268bbef9abd9afb5819db74f0e677e89a61b246228c71ac", size = 11799386, upload-time = "2026-01-21T15:51:57.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/c8/2ac00d7255252c5e3cf61b35ca92ca25704b0188f7454ca4aec08a33cece/pandas-3.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b86d113b6c109df3ce0ad5abbc259fe86a1bd4adfd4a31a89da42f84f65509bb", size = 10873041, upload-time = "2026-01-21T15:52:00.034Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/a80ac00acbc6b35166b42850e98a4f466e2c0d9c64054161ba9620f95680/pandas-3.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1c39eab3ad38f2d7a249095f0a3d8f8c22cc0f847e98ccf5bbe732b272e2d9fa", size = 9441003, upload-time = "2026-01-21T15:52:02.281Z" }, +] + +[[package]] +name = "pillow" +version = "9.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/00/d5/4903f310765e0ff2b8e91ffe55031ac6af77d982f0156061e20a4d1a8b2d/Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1", size = 50488147, upload-time = "2023-04-01T09:31:37.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/bc/cff591742feea45f88a3b8a83f7cab4a1dcdb4bcdfc51a06d92f96c81165/Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16", size = 3395758, upload-time = "2023-04-01T09:28:03.251Z" }, + { url = "https://files.pythonhosted.org/packages/38/06/de304914ecd2c911939a28579546bd4d9b6ae0b3c07ce5fe9bd7d100eb34/Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa", size = 3077111, upload-time = "2023-04-01T09:28:07.916Z" }, + { url = "https://files.pythonhosted.org/packages/9a/57/7864b6a22acb5f1d4b70af8c92cbd5e3af25f4d5869c24cd8074ca1f3593/Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38", size = 3112529, upload-time = "2023-04-01T09:28:10.564Z" }, + { url = "https://files.pythonhosted.org/packages/62/88/46a35f690ee4f8b08aef5fdb47f63d29c34f6874834155e52bf4456d9566/Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062", size = 3386670, upload-time = "2023-04-01T09:28:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/59/1d/26a56ed1deae695a8c7d13fb514284ba8b9fd62bab9ebe6d6b474523b8b0/Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e", size = 3308572, upload-time = "2023-04-01T09:28:16.585Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/d22b0fac821a14572fdb9a8015b2bf19ee81eaa560ea25a6772760c86a30/Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5", size = 3163999, upload-time = "2023-04-01T09:28:19.777Z" }, + { url = "https://files.pythonhosted.org/packages/25/6b/d3c35d207c9c0b6c2f855420f62e64ef43d348e8c797ad1c32b9f2106a19/Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d", size = 3415623, upload-time = "2023-04-01T09:28:23.176Z" }, + { url = "https://files.pythonhosted.org/packages/7a/6a/a7df39c502caeadd942d8bf97bc2fdfc819fbdc7499a2ab05e7db43611ac/Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903", size = 3350658, upload-time = "2023-04-01T09:28:26.277Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ad/d29c8c48498da680521665b8483beb78a9343269bbd0730970e9396b01f0/Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a", size = 3414574, upload-time = "2023-04-01T09:28:30.143Z" }, + { url = "https://files.pythonhosted.org/packages/93/54/9d7f01fd3fe4069c88827728646e3c8f1aff0995e8422d841b38f034f39a/Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44", size = 2211916, upload-time = "2023-04-01T09:28:33.723Z" }, + { url = "https://files.pythonhosted.org/packages/3e/14/0030e542f2acfea43635e55584c114e6cfd94d342393a5f71f74c172dc35/Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb", size = 2511474, upload-time = "2023-04-01T09:28:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/78/a8/3c2d737d856eb9cd8c18e78f6fe0ed08a2805bded74cbb0455584859023b/Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32", size = 3395792, upload-time = "2023-04-01T09:28:38.917Z" }, + { url = "https://files.pythonhosted.org/packages/a9/15/310cde63cb15a091de889ded26281924cf9cfa5c000b36b06bd0c7f50261/Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c", size = 3077092, upload-time = "2023-04-01T09:28:41.28Z" }, + { url = "https://files.pythonhosted.org/packages/17/66/20db69c0361902a2f6ee2086d3e83c70133e3fb4cb31470e59a8ed37184e/Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3", size = 3112543, upload-time = "2023-04-01T09:28:43.89Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a8/ff526cdec6b56eb20c992e7083f02c8065049ed1e62fbc159390d7a3dd5e/Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a", size = 3386654, upload-time = "2023-04-01T09:28:46.378Z" }, + { url = "https://files.pythonhosted.org/packages/3b/70/e9a45a2e9c58c23e023fcda5af9686f5b42c718cc9bc86194e0025cf0ec5/Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1", size = 3308566, upload-time = "2023-04-01T09:28:49.521Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/ee306d6cc53c9a30c23ba2313b43b67fdf76c611ca5afd0cdd62922cbd3e/Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99", size = 3164027, upload-time = "2023-04-01T09:28:52.295Z" }, + { url = "https://files.pythonhosted.org/packages/3d/59/e6bd2c3715ace343d9739276ceed79657fe116923238d102cf731ab463dd/Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625", size = 3415610, upload-time = "2023-04-01T09:28:54.667Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6d/9beb596ba5a5e61081c843187bcdbb42a5c9a9ef552751b554894247da7a/Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579", size = 3350704, upload-time = "2023-04-01T09:28:57.098Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e4/de633d85be3b3c770c554a37a89e8273069bd19c34b15a419c2795600310/Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296", size = 3414604, upload-time = "2023-04-01T09:29:03.375Z" }, + { url = "https://files.pythonhosted.org/packages/46/a0/e410f655300932308e70e883dd60c0c51e6f74bed138641ea9193e64fd7c/Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec", size = 2211929, upload-time = "2023-04-01T09:29:06.338Z" }, + { url = "https://files.pythonhosted.org/packages/0c/02/7729c8aecbc525b560c7eb283ffa34c6f5a6d0ed6d1339570c65a3e63088/Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4", size = 2511551, upload-time = "2023-04-01T09:29:08.636Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8b/d38cc68796be4ac238db327682a1acfbc5deccf64a150aa44ee1efbaafae/Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089", size = 2489206, upload-time = "2023-04-01T20:01:51.312Z" }, + { url = "https://files.pythonhosted.org/packages/5d/38/b7bcbab3bfe1946ba9cf71c1fa03e541b498069457be49eadcdc229412ef/Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb", size = 2211914, upload-time = "2023-04-01T09:29:10.935Z" }, + { url = "https://files.pythonhosted.org/packages/29/8a/f4cf3f32bc554f9260b645ea1151449ac13525796d3d1a42076d75945d8d/Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b", size = 2511483, upload-time = "2023-04-01T09:29:13.217Z" }, + { url = "https://files.pythonhosted.org/packages/ec/7d/01404982db598f271ac7c0d0207860f60ab9288cfacce9872eb567cfbfe3/Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906", size = 3394876, upload-time = "2023-04-01T09:29:16.167Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d9/8599b0e4f750aa3cc43613f57cae5a0dfe841b1a8c8c8bde97e83828cdfd/Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf", size = 3112088, upload-time = "2023-04-01T09:29:20.059Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/2c1daeb1e7c44d477f4f2d92f3316d922c9f8926378afcba424c6d1850aa/Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78", size = 3385803, upload-time = "2023-04-01T09:29:23.05Z" }, + { url = "https://files.pythonhosted.org/packages/37/95/48565d6beb34deaacda1543b515dab9479b8fa8b9046703fd08ad447ddfe/Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270", size = 3307826, upload-time = "2023-04-01T09:29:25.997Z" }, + { url = "https://files.pythonhosted.org/packages/b0/02/baf83c103657285542bba78978f5f6fb21d419944c2a4c54f950eb84a7bc/Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392", size = 3163643, upload-time = "2023-04-01T09:29:28.433Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a2/2d565cb1d754384a88998b9c86daf803a3a7908577875231eb99b8c7973d/Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47", size = 3415119, upload-time = "2023-04-01T09:29:32.268Z" }, + { url = "https://files.pythonhosted.org/packages/97/d2/f0b4c006c8997aff5277cdde18187c55ce767f9fd32b2dd657c1bf71b570/Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7", size = 2228447, upload-time = "2023-04-01T09:29:34.551Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3c/4f3ef1a14e903d7b2bc43672c20f732b874e1e50a9a58ac9a1726ef3773d/Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6", size = 2536969, upload-time = "2023-04-01T09:29:36.831Z" }, + { url = "https://files.pythonhosted.org/packages/49/ef/98941488c7491a249692787dc741c97c22d5212a6d85f017519011195cfe/Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597", size = 3395670, upload-time = "2023-04-01T09:29:39.764Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a5/ba2eeb1a242babb23a21a782356f8b6fe1312b24b69062ee1cb60107fd95/Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c", size = 3077136, upload-time = "2023-04-01T09:29:43.015Z" }, + { url = "https://files.pythonhosted.org/packages/18/e4/f13369726d14e550f0028265b299f7c8262ccb7fb295df29e4f2fd79e0ab/Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a", size = 3112507, upload-time = "2023-04-01T09:29:45.525Z" }, + { url = "https://files.pythonhosted.org/packages/90/00/123c546069abac47bd4ce2e0a78e6ad4040e43294ebbb266a3a21d3616b2/Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082", size = 3386666, upload-time = "2023-04-01T09:29:48.132Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/2d0919633097a93dcad35a2fb97304f4a9297f746e830a8b441af3db2007/Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf", size = 3308600, upload-time = "2023-04-01T09:29:50.886Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/0d37d73387b8ea9cb3ad391a93e65ed9f62a331c0dfed1869891b6efd7a2/Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf", size = 3163998, upload-time = "2023-04-01T09:29:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/a6/8b/cca45afbbd58ca032594ea465ded859b9da6d8bc226afe0e60e64bd8872e/Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51", size = 3415559, upload-time = "2023-04-01T09:29:57.018Z" }, + { url = "https://files.pythonhosted.org/packages/5d/06/2f319e3244bdd84567ed2d7d405a6e0fd9dd03fc6d7e24794ac1e14d570d/Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96", size = 3350681, upload-time = "2023-04-01T09:29:59.42Z" }, + { url = "https://files.pythonhosted.org/packages/db/5c/ba9e291850f594f89436cdca93d36c6f8610d4fb7833a6c257f4481d4174/Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f", size = 3414578, upload-time = "2023-04-01T09:30:02.245Z" }, + { url = "https://files.pythonhosted.org/packages/85/4d/d0b5c3610a39f01e380489770b10e2b8644a2188eace45c84e40d439b0dd/Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc", size = 2229252, upload-time = "2023-04-01T09:30:04.719Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ba/c4c2a1411561cd9725979115e7450f1367b44997ae1ff29e5845bce92d52/Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569", size = 2537784, upload-time = "2023-04-01T09:30:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/64/46/672289c0ff87733fb93854dedf3a8d65642a25c0bfc88e7f6d722f9161a5/Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66", size = 3395750, upload-time = "2023-04-01T09:30:10.542Z" }, + { url = "https://files.pythonhosted.org/packages/a9/70/9259e93534d01f846f7d0501f19bb7d8cc1751741bc20826fc8d3a20fe32/Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e", size = 3077133, upload-time = "2023-04-01T09:30:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/95/62/8a943681db5f6588498ed86aa1568dd31c63f6afdabe50841589fc662c68/Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115", size = 3112534, upload-time = "2023-04-01T09:30:15.616Z" }, + { url = "https://files.pythonhosted.org/packages/f2/43/0892913d499c8df2c88dee69d59e77de19e0c51754a9be82023880641c09/Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3", size = 3386725, upload-time = "2023-04-01T09:30:19.221Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fc/48a51c0fe2a00d5def57b9981a1e0f8339b516351da7a51500383d833bc8/Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef", size = 3308605, upload-time = "2023-04-01T09:30:29.279Z" }, + { url = "https://files.pythonhosted.org/packages/af/b7/f9faf80e3c93b02712c5748f10c75a8948e74eca61ec2408f7e1d4c9dd16/Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705", size = 3164057, upload-time = "2023-04-01T09:30:32.484Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2b/57915b8af178e2c20bfd403ffed4521947881f9dbbfbaba48210dc59b9d7/Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1", size = 3415613, upload-time = "2023-04-01T09:30:34.978Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2a/f3ed578595f8486ee2cc07434460097d89aedd406a3db849b890ca8ec416/Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a", size = 3350667, upload-time = "2023-04-01T09:30:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/28/a2/f2d0d584d45100a5419fd70a1233ade8f12469ffe6e8e3acd40364beaadb/Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865", size = 3414552, upload-time = "2023-04-01T09:30:48.682Z" }, + { url = "https://files.pythonhosted.org/packages/51/3a/a6701b987007aaa43559b7d8510629845b25686f09a0eb29f8946a62d767/Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964", size = 2229361, upload-time = "2023-04-01T09:30:51.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/72/48cc52bff8731cf72bc4101e34dc44807a410c171f921afb582a511da50e/Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d", size = 2538580, upload-time = "2023-04-01T09:30:54.345Z" }, + { url = "https://files.pythonhosted.org/packages/05/80/40ec3390eb39f128f9c81dfdce6fe419fad1296e816232c2785e74bb6255/Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5", size = 3350068, upload-time = "2023-04-01T09:30:57.413Z" }, + { url = "https://files.pythonhosted.org/packages/52/f8/099a6b9de39763b40ed6be5c0aa5b5aed800ecad98535c6c77dfa79484f1/Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140", size = 3281820, upload-time = "2023-04-01T09:31:00.728Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4f/e31e4814b09f15c13d6fe069458a3b32a240ffaeb603b973456de3ea6d2a/Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba", size = 3222897, upload-time = "2023-04-01T09:31:04.332Z" }, + { url = "https://files.pythonhosted.org/packages/22/3b/db9837995e3d51ff356e39726e2ec0925850fdfef104996c2767baca4407/Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829", size = 3298302, upload-time = "2023-04-01T09:31:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/d9/0e/7c6f054022235830dc2c37ec83e947d9ca09b0b0361e1e5e29983da92294/Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd", size = 2537868, upload-time = "2023-04-01T09:31:10.529Z" }, + { url = "https://files.pythonhosted.org/packages/24/35/92032a00f41bea9bf93f19d48f15daac27d1365c0038fe22dc4e7fc7c8b0/Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572", size = 3349772, upload-time = "2023-04-01T09:31:13.438Z" }, + { url = "https://files.pythonhosted.org/packages/50/ce/d39869c22904558ce32e664904cf72f13a9d47703b72392e881d9e7b6082/Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe", size = 3281583, upload-time = "2023-04-01T09:31:16.7Z" }, + { url = "https://files.pythonhosted.org/packages/7a/75/4a382d1567efc6f4e3054f693167f8ce2d1ad939c5f6f12aa5c50f74b997/Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1", size = 3222603, upload-time = "2023-04-01T09:31:19.548Z" }, + { url = "https://files.pythonhosted.org/packages/51/d2/c10f72c44e000d08e41f822083cf322bb59afa7ed01ae7e3e47875b47600/Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7", size = 3298174, upload-time = "2023-04-01T09:31:23.005Z" }, + { url = "https://files.pythonhosted.org/packages/02/4a/d362f7f44f1e5801c6726f0eaaeaf869d0d43c554b717072b2c5540cefb4/Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799", size = 2538628, upload-time = "2023-04-01T09:31:26.082Z" }, +] + +[[package]] +name = "pillow" +version = "10.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059, upload-time = "2024-07-01T09:48:43.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271, upload-time = "2024-07-01T09:45:22.07Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658, upload-time = "2024-07-01T09:45:25.292Z" }, + { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075, upload-time = "2024-07-01T09:45:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808, upload-time = "2024-07-01T09:45:30.305Z" }, + { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290, upload-time = "2024-07-01T09:45:32.868Z" }, + { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163, upload-time = "2024-07-01T09:45:35.279Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100, upload-time = "2024-07-01T09:45:37.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880, upload-time = "2024-07-01T09:45:39.89Z" }, + { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218, upload-time = "2024-07-01T09:45:42.771Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487, upload-time = "2024-07-01T09:45:45.176Z" }, + { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219, upload-time = "2024-07-01T09:45:47.274Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265, upload-time = "2024-07-01T09:45:49.812Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655, upload-time = "2024-07-01T09:45:52.462Z" }, + { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304, upload-time = "2024-07-01T09:45:55.006Z" }, + { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804, upload-time = "2024-07-01T09:45:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126, upload-time = "2024-07-01T09:46:00.713Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541, upload-time = "2024-07-01T09:46:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616, upload-time = "2024-07-01T09:46:05.356Z" }, + { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802, upload-time = "2024-07-01T09:46:08.145Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213, upload-time = "2024-07-01T09:46:10.211Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498, upload-time = "2024-07-01T09:46:12.685Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219, upload-time = "2024-07-01T09:46:14.83Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350, upload-time = "2024-07-01T09:46:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980, upload-time = "2024-07-01T09:46:19.169Z" }, + { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799, upload-time = "2024-07-01T09:46:21.883Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973, upload-time = "2024-07-01T09:46:24.321Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054, upload-time = "2024-07-01T09:46:26.825Z" }, + { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484, upload-time = "2024-07-01T09:46:29.355Z" }, + { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375, upload-time = "2024-07-01T09:46:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773, upload-time = "2024-07-01T09:46:33.73Z" }, + { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690, upload-time = "2024-07-01T09:46:36.587Z" }, + { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951, upload-time = "2024-07-01T09:46:38.777Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427, upload-time = "2024-07-01T09:46:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685, upload-time = "2024-07-01T09:46:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883, upload-time = "2024-07-01T09:46:47.331Z" }, + { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837, upload-time = "2024-07-01T09:46:49.647Z" }, + { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562, upload-time = "2024-07-01T09:46:51.811Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761, upload-time = "2024-07-01T09:46:53.961Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767, upload-time = "2024-07-01T09:46:56.664Z" }, + { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989, upload-time = "2024-07-01T09:46:58.977Z" }, + { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255, upload-time = "2024-07-01T09:47:01.189Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603, upload-time = "2024-07-01T09:47:03.918Z" }, + { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972, upload-time = "2024-07-01T09:47:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375, upload-time = "2024-07-01T09:47:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/56/70/f40009702a477ce87d8d9faaa4de51d6562b3445d7a314accd06e4ffb01d/pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736", size = 3509213, upload-time = "2024-07-01T09:47:11.662Z" }, + { url = "https://files.pythonhosted.org/packages/10/43/105823d233c5e5d31cea13428f4474ded9d961652307800979a59d6a4276/pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b", size = 3375883, upload-time = "2024-07-01T09:47:14.453Z" }, + { url = "https://files.pythonhosted.org/packages/3c/ad/7850c10bac468a20c918f6a5dbba9ecd106ea1cdc5db3c35e33a60570408/pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2", size = 4330810, upload-time = "2024-07-01T09:47:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/69bbed9e436ac22f9ed193a2b64f64d68fcfbc9f4106249dc7ed4889907b/pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680", size = 4444341, upload-time = "2024-07-01T09:47:19.334Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4f/c183c63828a3f37bf09644ce94cbf72d4929b033b109160a5379c2885932/pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b", size = 4356005, upload-time = "2024-07-01T09:47:21.805Z" }, + { url = "https://files.pythonhosted.org/packages/fb/ad/435fe29865f98a8fbdc64add8875a6e4f8c97749a93577a8919ec6f32c64/pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd", size = 4525201, upload-time = "2024-07-01T09:47:24.457Z" }, + { url = "https://files.pythonhosted.org/packages/80/74/be8bf8acdfd70e91f905a12ae13cfb2e17c0f1da745c40141e26d0971ff5/pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84", size = 4460635, upload-time = "2024-07-01T09:47:26.841Z" }, + { url = "https://files.pythonhosted.org/packages/e4/90/763616e66dc9ad59c9b7fb58f863755e7934ef122e52349f62c7742b82d3/pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0", size = 4590283, upload-time = "2024-07-01T09:47:29.247Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/03002cb5b2c27bb519cba63b9f9aa3709c6f7a5d3b285406c01f03fb77e5/pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e", size = 2235185, upload-time = "2024-07-01T09:47:32.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/75/3cb820b2812405fc7feb3d0deb701ef0c3de93dc02597115e00704591bc9/pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab", size = 2554594, upload-time = "2024-07-01T09:47:34.285Z" }, + { url = "https://files.pythonhosted.org/packages/31/85/955fa5400fa8039921f630372cfe5056eed6e1b8e0430ee4507d7de48832/pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d", size = 3509283, upload-time = "2024-07-01T09:47:36.394Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/343827267eb28d41cd82b4180d33b10d868af9077abcec0af9793aa77d2d/pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b", size = 3375691, upload-time = "2024-07-01T09:47:38.853Z" }, + { url = "https://files.pythonhosted.org/packages/60/a3/7ebbeabcd341eab722896d1a5b59a3df98c4b4d26cf4b0385f8aa94296f7/pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd", size = 4328295, upload-time = "2024-07-01T09:47:41.765Z" }, + { url = "https://files.pythonhosted.org/packages/32/3f/c02268d0c6fb6b3958bdda673c17b315c821d97df29ae6969f20fb49388a/pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126", size = 4440810, upload-time = "2024-07-01T09:47:44.27Z" }, + { url = "https://files.pythonhosted.org/packages/67/5d/1c93c8cc35f2fdd3d6cc7e4ad72d203902859a2867de6ad957d9b708eb8d/pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b", size = 4352283, upload-time = "2024-07-01T09:47:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a8/8655557c9c7202b8abbd001f61ff36711cefaf750debcaa1c24d154ef602/pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c", size = 4521800, upload-time = "2024-07-01T09:47:48.813Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/6f95797af64d137124f68af1bdaa13b5332da282b86031f6fa70cf368261/pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1", size = 4459177, upload-time = "2024-07-01T09:47:52.104Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/2b3ce34f1c4266d79a78c9a51d1289a33c3c02833fe294ef0dcbb9cba4ed/pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df", size = 4589079, upload-time = "2024-07-01T09:47:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e0/456258c74da1ff5bf8ef1eab06a95ca994d8b9ed44c01d45c3f8cbd1db7e/pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef", size = 2235247, upload-time = "2024-07-01T09:47:57.666Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/bef952bdb32aa53741f58bf21798642209e994edc3f6598f337f23d5400a/pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5", size = 2554479, upload-time = "2024-07-01T09:47:59.881Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8e/805201619cad6651eef5fc1fdef913804baf00053461522fabbc5588ea12/pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e", size = 2243226, upload-time = "2024-07-01T09:48:02.508Z" }, + { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889, upload-time = "2024-07-01T09:48:04.815Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160, upload-time = "2024-07-01T09:48:07.206Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020, upload-time = "2024-07-01T09:48:09.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539, upload-time = "2024-07-01T09:48:12.529Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125, upload-time = "2024-07-01T09:48:14.891Z" }, + { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373, upload-time = "2024-07-01T09:48:17.601Z" }, + { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661, upload-time = "2024-07-01T09:48:20.293Z" }, + { url = "https://files.pythonhosted.org/packages/e1/1f/5a9fcd6ced51633c22481417e11b1b47d723f64fb536dfd67c015eb7f0ab/pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b", size = 3493850, upload-time = "2024-07-01T09:48:23.03Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e6/3ea4755ed5320cb62aa6be2f6de47b058c6550f752dd050e86f694c59798/pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908", size = 3346118, upload-time = "2024-07-01T09:48:25.256Z" }, + { url = "https://files.pythonhosted.org/packages/0a/22/492f9f61e4648422b6ca39268ec8139277a5b34648d28f400faac14e0f48/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b", size = 3434958, upload-time = "2024-07-01T09:48:28.078Z" }, + { url = "https://files.pythonhosted.org/packages/f9/19/559a48ad4045704bb0547965b9a9345f5cd461347d977a56d178db28819e/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8", size = 3490340, upload-time = "2024-07-01T09:48:30.734Z" }, + { url = "https://files.pythonhosted.org/packages/d9/de/cebaca6fb79905b3a1aa0281d238769df3fb2ede34fd7c0caa286575915a/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a", size = 3476048, upload-time = "2024-07-01T09:48:33.292Z" }, + { url = "https://files.pythonhosted.org/packages/71/f0/86d5b2f04693b0116a01d75302b0a307800a90d6c351a8aa4f8ae76cd499/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27", size = 3579366, upload-time = "2024-07-01T09:48:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/37/ae/2dbfc38cc4fd14aceea14bc440d5151b21f64c4c3ba3f6f4191610b7ee5d/pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", size = 2554652, upload-time = "2024-07-01T09:48:38.789Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" }, + { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" }, + { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" }, + { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" }, + { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" }, + { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8e/9c089f01677d1264ab8648352dcb7773f37da6ad002542760c80107da816/pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f", size = 5316478, upload-time = "2025-07-01T09:15:52.209Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a9/5749930caf674695867eb56a581e78eb5f524b7583ff10b01b6e5048acb3/pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081", size = 4686522, upload-time = "2025-07-01T09:15:54.162Z" }, + { url = "https://files.pythonhosted.org/packages/43/46/0b85b763eb292b691030795f9f6bb6fcaf8948c39413c81696a01c3577f7/pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4", size = 5853376, upload-time = "2025-07-03T13:11:01.066Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c6/1a230ec0067243cbd60bc2dad5dc3ab46a8a41e21c15f5c9b52b26873069/pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc", size = 7626020, upload-time = "2025-07-03T13:11:06.479Z" }, + { url = "https://files.pythonhosted.org/packages/63/dd/f296c27ffba447bfad76c6a0c44c1ea97a90cb9472b9304c94a732e8dbfb/pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06", size = 5956732, upload-time = "2025-07-01T09:15:56.111Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a0/98a3630f0b57f77bae67716562513d3032ae70414fcaf02750279c389a9e/pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a", size = 6624404, upload-time = "2025-07-01T09:15:58.245Z" }, + { url = "https://files.pythonhosted.org/packages/de/e6/83dfba5646a290edd9a21964da07674409e410579c341fc5b8f7abd81620/pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978", size = 6067760, upload-time = "2025-07-01T09:16:00.003Z" }, + { url = "https://files.pythonhosted.org/packages/bc/41/15ab268fe6ee9a2bc7391e2bbb20a98d3974304ab1a406a992dcb297a370/pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d", size = 6700534, upload-time = "2025-07-01T09:16:02.29Z" }, + { url = "https://files.pythonhosted.org/packages/64/79/6d4f638b288300bed727ff29f2a3cb63db054b33518a95f27724915e3fbc/pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71", size = 6277091, upload-time = "2025-07-01T09:16:04.4Z" }, + { url = "https://files.pythonhosted.org/packages/46/05/4106422f45a05716fd34ed21763f8ec182e8ea00af6e9cb05b93a247361a/pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada", size = 6986091, upload-time = "2025-07-01T09:16:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/287fd55c2c12761d0591549d48885187579b7c257bef0c6660755b0b59ae/pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb", size = 2422632, upload-time = "2025-07-01T09:16:08.142Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" }, + { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, + { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" }, + { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" }, + { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, + { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, + { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" }, + { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" }, + { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" }, + { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" }, + { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" }, + { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" }, + { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" }, + { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" }, + { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" }, + { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" }, + { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, + { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, + { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, + { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, + { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, + { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3", size = 61613, upload-time = "2023-06-21T09:12:28.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", size = 17695, upload-time = "2023-06-21T09:12:27.397Z" }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prettytable" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "wcwidth", version = "0.2.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/8d/f6b4448e386eb1382a99cbceabe3899f3aa431992582cc90496843548303/prettytable-3.7.0.tar.gz", hash = "sha256:ef8334ee40b7ec721651fc4d37ecc7bb2ef55fde5098d994438f0dfdaa385c0c", size = 47169, upload-time = "2023-04-11T03:32:11.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/cd/bec5850e23eb005c6fe30fe4c26bafd9a07b3d2524771f22a0fa01270078/prettytable-3.7.0-py3-none-any.whl", hash = "sha256:f4aaf2ed6e6062a82fd2e6e5289bbbe705ec2788fe401a3a1f62a1cea55526d2", size = 27508, upload-time = "2023-04-10T09:05:16.908Z" }, +] + +[[package]] +name = "prettytable" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "wcwidth", version = "0.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/57/0a642bec16d5736b9baaac7e830bedccd10341dc2858075c34d5aec5c8b6/prettytable-3.11.0.tar.gz", hash = "sha256:7e23ca1e68bbfd06ba8de98bf553bf3493264c96d5e8a615c0471025deeba722", size = 57527, upload-time = "2024-08-12T13:29:59.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/bfdc26c0e19156992b1dc9de47f0b2e8992fe43db9981d814f860bdce2b3/prettytable-3.11.0-py3-none-any.whl", hash = "sha256:aa17083feb6c71da11a68b2c213b04675c4af4ce9c541762632ca3f2cb3546dd", size = 28734, upload-time = "2024-08-12T13:29:31.981Z" }, +] + +[[package]] +name = "prettytable" +version = "3.16.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "wcwidth", version = "0.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/b1/85e18ac92afd08c533603e3393977b6bc1443043115a47bb094f3b98f94f/prettytable-3.16.0.tar.gz", hash = "sha256:3c64b31719d961bf69c9a7e03d0c1e477320906a98da63952bc6698d6164ff57", size = 66276, upload-time = "2025-03-24T19:39:04.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl", hash = "sha256:b5eccfabb82222f5aa46b798ff02a8452cf530a352c31bddfa29be41242863aa", size = 33863, upload-time = "2025-03-24T19:39:02.359Z" }, +] + +[[package]] +name = "prettytable" +version = "3.17.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "wcwidth", version = "0.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/45/b0847d88d6cfeb4413566738c8bbf1e1995fad3d42515327ff32cc1eb578/prettytable-3.17.0.tar.gz", hash = "sha256:59f2590776527f3c9e8cf9fe7b66dd215837cca96a9c39567414cbc632e8ddb0", size = 67892, upload-time = "2025-11-14T17:33:20.212Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl", hash = "sha256:aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287", size = 34433, upload-time = "2025-11-14T17:33:19.093Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/02/b3/c832123f2699892c715fcdfebb1a8fdeffa11bb7b2350e46ecdd76b45a20/pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef", size = 103640, upload-time = "2021-03-14T18:44:04.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/cc/227251b1471f129bc35e966bb0fceb005969023926d744139642d847b7ae/pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", size = 41725, upload-time = "2021-03-14T18:44:02.097Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/83/5bcaedba1f47200f0665ceb07bcb00e2be123192742ee0edfb66b600e5fd/pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785", size = 102127, upload-time = "2022-08-03T23:13:29.715Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/e4/fc77f1039c34b3612c4867b69cbb2b8a4e569720b1f19b0637002ee03aff/pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b", size = 41493, upload-time = "2022-08-03T23:13:27.416Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/43/aa/210b2c9aedd8c1cbeea31a50e42050ad56187754b34eb214c46709445801/pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521", size = 39232, upload-time = "2024-08-04T20:26:54.576Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d8/a211b3f85e99a0daa2ddec96c949cac6824bd305b040571b82a03dd62636/pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", size = 31284, upload-time = "2024-08-04T20:26:53.173Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, +] + +[[package]] +name = "pycparser" +version = "2.21" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206", size = 170877, upload-time = "2021-11-06T12:48:46.095Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", size = 118697, upload-time = "2021-11-06T12:50:13.61Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pyecharts" +source = { editable = "." } +dependencies = [ + { name = "jinja2" }, + { name = "prettytable", version = "3.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "prettytable", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "prettytable", version = "3.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "prettytable", version = "3.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "simplejson" }, +] + +[package.optional-dependencies] +images = [ + { name = "pillow", version = "9.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +phantomjs = [ + { name = "snapshot-phantomjs" }, +] +pyppeteer = [ + { name = "snapshot-pyppeteer" }, +] +selenium = [ + { name = "snapshot-selenium" }, +] + +[package.dev-dependencies] +dev = [ + { name = "codecov" }, + { name = "coverage", version = "7.2.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "coverage", version = "7.13.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "flake8", version = "3.9.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "flake8", version = "5.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.8.1'" }, + { name = "flake8", version = "7.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" }, + { name = "flake8", version = "7.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "mccabe", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "mccabe", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "nose2", version = "0.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "nose2", version = "0.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "numpy", version = "1.21.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "1.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7.1'" }, + { name = "pandas", version = "1.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.7.1' and python_full_version < '3.8'" }, + { name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pillow", version = "9.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-cov", version = "4.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest-cov", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pytest-cov", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pytest-xdist", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest-xdist", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pytest-xdist", version = "3.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] + +[package.metadata] +requires-dist = [ + { name = "jinja2", specifier = ">=2.11.3" }, + { name = "pillow", marker = "extra == 'images'" }, + { name = "prettytable" }, + { name = "simplejson" }, + { name = "snapshot-phantomjs", marker = "extra == 'phantomjs'" }, + { name = "snapshot-pyppeteer", marker = "extra == 'pyppeteer'" }, + { name = "snapshot-selenium", marker = "extra == 'selenium'" }, +] +provides-extras = ["selenium", "phantomjs", "pyppeteer", "images"] + +[package.metadata.requires-dev] +dev = [ + { name = "codecov" }, + { name = "coverage" }, + { name = "flake8" }, + { name = "mccabe" }, + { name = "nose2" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-xdist" }, +] + +[[package]] +name = "pyee" +version = "8.2.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/88/a917aaa0da1915292022745184275e095516b490a85d89fc48fd4de1c01a/pyee-8.2.2.tar.gz", hash = "sha256:5c7e60f8df95710dbe17550e16ce0153f83990c00ef744841b43f371ed53ebea", size = 16938, upload-time = "2021-08-14T23:22:32.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/37/29d137df23ed1d88d8dcee8a6b8e789d1162042f194b5ccd0a48f503429b/pyee-8.2.2-py2.py3-none-any.whl", hash = "sha256:c09f56e36eb10bf23aa2aacf145f690ded75b990a3d9523fd478b005940303d2", size = 12709, upload-time = "2021-08-14T23:22:29.953Z" }, +] + +[[package]] +name = "pyee" +version = "11.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/20/3e646860d5a2f9c24b23b1964fb7c65312236883fa7b62d0f64456addc93/pyee-11.1.1.tar.gz", hash = "sha256:82e1eb1853f8497c4ff1a0c7fa26b9cd2f1253e2b6ffb93b4700fda907017302", size = 30002, upload-time = "2024-08-30T19:15:39.282Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/99/7e80837f60b13227f03334e3b0537d650dea2c0cea44c543b0a2e719a48f/pyee-11.1.1-py3-none-any.whl", hash = "sha256:9e4cdd7c2f9fcf247db94bad39a260aceffefdbe52286ce71be01959de34a5c2", size = 15300, upload-time = "2024-08-30T19:15:37.73Z" }, +] + +[[package]] +name = "pyflakes" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/0f/0dc480da9162749bf629dca76570972dd9cce5bedc60196a3c912875c87d/pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db", size = 68567, upload-time = "2021-03-24T16:32:56.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/11/2a745612f1d3cbbd9c69ba14b1b43a35a2f5c3c81cd0124508c52c64307f/pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", size = 68805, upload-time = "2021-03-24T16:32:54.562Z" }, +] + +[[package]] +name = "pyflakes" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/07/92/f0cb5381f752e89a598dd2850941e7f570ac3cb8ea4a344854de486db152/pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3", size = 66388, upload-time = "2022-07-30T17:29:05.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/13/63178f59f74e53acc2165aee4b002619a3cfa7eeaeac989a9eb41edf364e/pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2", size = 66116, upload-time = "2022-07-30T17:29:04.179Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788, upload-time = "2024-01-05T00:28:47.703Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725, upload-time = "2024-01-05T00:28:45.903Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyppeteer" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "appdirs", marker = "python_full_version < '3.8'" }, + { name = "certifi", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pyee", version = "8.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "tqdm", marker = "python_full_version < '3.8'" }, + { name = "urllib3", marker = "python_full_version < '3.8'" }, + { name = "websockets", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/d6/10e7dfaa677888f59bb68902729e0b3d4dee540642533f60a4cdb8c1eb63/pyppeteer-1.0.2.tar.gz", hash = "sha256:ddb0d15cb644720160d49abb1ad0d97e87a55581febf1b7531be9e983aad7742", size = 70613, upload-time = "2022-01-11T05:39:30.06Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/46/33c0a9e7d37bf33487074de4399963462093043ad224d1881e41cbd937f3/pyppeteer-1.0.2-py3-none-any.whl", hash = "sha256:11a734d8f02c6b128035aba8faf32748f2016310a6a1cbc6aa5b1e2580742e8f", size = 83378, upload-time = "2022-01-11T05:39:28.285Z" }, +] + +[[package]] +name = "pyppeteer" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "appdirs", marker = "python_full_version >= '3.8'" }, + { name = "certifi", marker = "python_full_version >= '3.8'" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pyee", version = "11.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "tqdm", marker = "python_full_version >= '3.8'" }, + { name = "urllib3", marker = "python_full_version >= '3.8'" }, + { name = "websockets", marker = "python_full_version >= '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/fe/6d0bbf58c8e9348f7ab35ea172b1339cef102385b345cf35e13578e8df19/pyppeteer-2.0.0.tar.gz", hash = "sha256:4af63473ff36a746a53347b2336a49efda669bcd781e400bc1799b81838358d9", size = 71077, upload-time = "2024-02-18T03:02:44.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ee/fb2757a38025421fd3844a0ed0a230b78c9c04a66355024436cf3005a70c/pyppeteer-2.0.0-py3-none-any.whl", hash = "sha256:96f4c574fb36f1d15e02746303ab742b98941f0da58337187e7c1d2ef982adea", size = 82891, upload-time = "2024-02-18T03:02:41.989Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, +] + +[[package]] +name = "pytest" +version = "7.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.8' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "iniconfig", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "packaging", version = "24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pluggy", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "tomli", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116, upload-time = "2023-12-31T12:00:18.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287, upload-time = "2023-12-31T12:00:13.963Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.8.*' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.8.*'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "packaging", version = "26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "tomli", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.9.*' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.9.*'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "packaging", version = "26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pygments", marker = "python_full_version == '3.9.*'" }, + { name = "tomli", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", version = "26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "coverage", version = "7.2.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.8'" }, + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6", size = 63245, upload-time = "2023-05-24T18:44:56.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a", size = 21949, upload-time = "2023-05-24T18:44:54.079Z" }, +] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.8.*'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042, upload-time = "2024-03-24T20:16:34.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990, upload-time = "2024-03-24T20:16:32.444Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.9.*'" }, + { name = "coverage", version = "7.13.3", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "execnet", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/f4/ac9c4ccbc5984ebc3bef6dbdbcdaf553a1aae07c08e63b8b25a6239ecc45/pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a", size = 78977, upload-time = "2023-11-21T15:21:15.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/37/125fe5ec459321e2d48a0c38672cfc2419ad87d580196fd894e5f25230b0/pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24", size = 42017, upload-time = "2023-11-21T15:21:13.278Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "execnet", version = "2.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060, upload-time = "2024-04-28T19:29:54.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108, upload-time = "2024-04-28T19:29:52.813Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "execnet", version = "2.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "requests" +version = "2.31.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.8'" }, + { name = "charset-normalizer", marker = "python_full_version < '3.8'" }, + { name = "idna", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "urllib3", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794, upload-time = "2023-05-22T15:12:44.175Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574, upload-time = "2023-05-22T15:12:42.313Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version == '3.8.*'" }, + { name = "charset-normalizer", marker = "python_full_version == '3.8.*'" }, + { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "urllib3", marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.9'" }, + { name = "charset-normalizer", marker = "python_full_version >= '3.9'" }, + { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "urllib3", marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "selenium" +version = "4.11.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.8'" }, + { name = "trio", version = "0.22.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "trio-websocket", version = "0.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "urllib3", extra = ["socks"], marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/21/dc6e78b967b62cb39748bfd1af10cc245c8bc81ad8568b1efe80e73fd8a7/selenium-4.11.2.tar.gz", hash = "sha256:9f9a5ed586280a3594f7461eb1d9dab3eac9d91e28572f365e9b98d9d03e02b5", size = 6975568, upload-time = "2023-08-01T04:07:20.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/56/8288d1813a68c1e0638515dbb777fce6d87d0d240e683216f956145310e6/selenium-4.11.2-py3-none-any.whl", hash = "sha256:98e72117b194b3fa9c69b48998f44bf7dd4152c7bd98544911a1753b9f03cc7d", size = 7188599, upload-time = "2023-08-01T04:07:16.444Z" }, +] + +[[package]] +name = "selenium" +version = "4.27.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version == '3.8.*'" }, + { name = "trio", version = "0.27.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "trio-websocket", version = "0.12.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "urllib3", extra = ["socks"], marker = "python_full_version == '3.8.*'" }, + { name = "websocket-client", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/8c/62c47c91072aa03af1c3b7d7f1c59b987db41c9fec0f158fb03a0da51aa6/selenium-4.27.1.tar.gz", hash = "sha256:5296c425a75ff1b44d0d5199042b36a6d1ef76c04fb775b97b40be739a9caae2", size = 973526, upload-time = "2024-11-26T14:56:47.893Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/1e/5f1a5dd2a28528c4b3ec6e076b58e4c035810c805328f9936123283ca14e/selenium-4.27.1-py3-none-any.whl", hash = "sha256:b89b1f62b5cfe8025868556fe82360d6b649d464f75d2655cb966c8f8447ea18", size = 9707007, upload-time = "2024-11-26T14:56:45.191Z" }, +] + +[[package]] +name = "selenium" +version = "4.32.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.9'" }, + { name = "trio", version = "0.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "trio", version = "0.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "trio-websocket", version = "0.12.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "urllib3", extra = ["socks"], marker = "python_full_version >= '3.9'" }, + { name = "websocket-client", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/2d/fafffe946099033ccf22bf89e12eede14c1d3c5936110c5f6f2b9830722c/selenium-4.32.0.tar.gz", hash = "sha256:b9509bef4056f4083772abb1ae19ff57247d617a29255384b26be6956615b206", size = 870997, upload-time = "2025-05-02T20:35:27.325Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/37/d07ed9d13e571b2115d4ed6956d156c66816ceec0b03b2e463e80d09f572/selenium-4.32.0-py3-none-any.whl", hash = "sha256:c4d9613f8a45693d61530c9660560fadb52db7d730237bc788ddedf442391f97", size = 9369668, upload-time = "2025-05-02T20:35:24.726Z" }, +] + +[[package]] +name = "simplejson" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f4/a1ac5ed32f7ed9a088d62a59d410d4c204b3b3815722e2ccfb491fa8251b/simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649", size = 85784, upload-time = "2025-09-26T16:29:36.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/09/2bf3761de89ea2d91bdce6cf107dcd858892d0adc22c995684878826cc6b/simplejson-3.20.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6d7286dc11af60a2f76eafb0c2acde2d997e87890e37e24590bb513bec9f1bc5", size = 94039, upload-time = "2025-09-26T16:27:29.283Z" }, + { url = "https://files.pythonhosted.org/packages/0f/33/c3277db8931f0ae9e54b9292668863365672d90fb0f632f4cf9829cb7d68/simplejson-3.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01379b4861c3b0aa40cba8d44f2b448f5743999aa68aaa5d3ef7049d4a28a2d", size = 75894, upload-time = "2025-09-26T16:27:30.378Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ea/ae47b04d03c7c8a7b7b1a8b39a6e27c3bd424e52f4988d70aca6293ff5e5/simplejson-3.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16b029ca25645b3bc44e84a4f941efa51bf93c180b31bd704ce6349d1fc77c1", size = 76116, upload-time = "2025-09-26T16:27:31.42Z" }, + { url = "https://files.pythonhosted.org/packages/4b/42/6c9af551e5a8d0f171d6dce3d9d1260068927f7b80f1f09834e07887c8c4/simplejson-3.20.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e22a5fb7b1437ffb057e02e1936a3bfb19084ae9d221ec5e9f4cf85f69946b6", size = 138827, upload-time = "2025-09-26T16:27:32.486Z" }, + { url = "https://files.pythonhosted.org/packages/2b/22/5e268bbcbe9f75577491e406ec0a5536f5b2fa91a3b52031fea51cd83e1d/simplejson-3.20.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b6ff02fc7b8555c906c24735908854819b0d0dc85883d453e23ca4c0445d01", size = 146772, upload-time = "2025-09-26T16:27:34.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/b4/800f14728e2ad666f420dfdb57697ca128aeae7f991b35759c09356b829a/simplejson-3.20.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bfc1c396ad972ba4431130b42307b2321dba14d988580c1ac421ec6a6b7cee3", size = 134497, upload-time = "2025-09-26T16:27:35.211Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b9/c54eef4226c6ac8e9a389bbe5b21fef116768f97a2dc1a683c716ffe66ef/simplejson-3.20.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a97249ee1aee005d891b5a211faf58092a309f3d9d440bc269043b08f662eda", size = 138172, upload-time = "2025-09-26T16:27:36.44Z" }, + { url = "https://files.pythonhosted.org/packages/09/36/4e282f5211b34620f1b2e4b51d9ddaab5af82219b9b7b78360a33f7e5387/simplejson-3.20.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1036be00b5edaddbddbb89c0f80ed229714a941cfd21e51386dc69c237201c2", size = 140272, upload-time = "2025-09-26T16:27:37.605Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/94ad2cf32f477c449e1f63c863d8a513e2408d651c4e58fe4b6a7434e168/simplejson-3.20.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d6f5bacb8cdee64946b45f2680afa3f54cd38e62471ceda89f777693aeca4e4", size = 140468, upload-time = "2025-09-26T16:27:39.015Z" }, + { url = "https://files.pythonhosted.org/packages/e5/46/827731e4163be3f987cb8ee90f5d444161db8f540b5e735355faa098d9bc/simplejson-3.20.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8db6841fb796ec5af632f677abf21c6425a1ebea0d9ac3ef1a340b8dc69f52b8", size = 148700, upload-time = "2025-09-26T16:27:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/c7/28/c32121064b1ec2fb7b5d872d9a1abda62df064d35e0160eddfa907118343/simplejson-3.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0a341f7cc2aae82ee2b31f8a827fd2e51d09626f8b3accc441a6907c88aedb7", size = 141323, upload-time = "2025-09-26T16:27:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/46/b6/c897c54326fe86dd12d101981171a49361949f4728294f418c3b86a1af77/simplejson-3.20.2-cp310-cp310-win32.whl", hash = "sha256:27f9c01a6bc581d32ab026f515226864576da05ef322d7fc141cd8a15a95ce53", size = 74377, upload-time = "2025-09-26T16:27:42.533Z" }, + { url = "https://files.pythonhosted.org/packages/ad/87/a6e03d4d80cca99c1fee4e960f3440e2f21be9470e537970f960ca5547f1/simplejson-3.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0a63ec98a4547ff366871bf832a7367ee43d047bcec0b07b66c794e2137b476", size = 76081, upload-time = "2025-09-26T16:27:43.945Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3e/96898c6c66d9dca3f9bd14d7487bf783b4acc77471b42f979babbb68d4ca/simplejson-3.20.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06190b33cd7849efc413a5738d3da00b90e4a5382fd3d584c841ac20fb828c6f", size = 92633, upload-time = "2025-09-26T16:27:45.028Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a2/cd2e10b880368305d89dd540685b8bdcc136df2b3c76b5ddd72596254539/simplejson-3.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ad4eac7d858947a30d2c404e61f16b84d16be79eb6fb316341885bdde864fa8", size = 75309, upload-time = "2025-09-26T16:27:46.142Z" }, + { url = "https://files.pythonhosted.org/packages/5d/02/290f7282eaa6ebe945d35c47e6534348af97472446951dce0d144e013f4c/simplejson-3.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b392e11c6165d4a0fde41754a0e13e1d88a5ad782b245a973dd4b2bdb4e5076a", size = 75308, upload-time = "2025-09-26T16:27:47.542Z" }, + { url = "https://files.pythonhosted.org/packages/43/91/43695f17b69e70c4b0b03247aa47fb3989d338a70c4b726bbdc2da184160/simplejson-3.20.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51eccc4e353eed3c50e0ea2326173acdc05e58f0c110405920b989d481287e51", size = 143733, upload-time = "2025-09-26T16:27:48.673Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4b/fdcaf444ac1c3cbf1c52bf00320c499e1cf05d373a58a3731ae627ba5e2d/simplejson-3.20.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:306e83d7c331ad833d2d43c76a67f476c4b80c4a13334f6e34bb110e6105b3bd", size = 153397, upload-time = "2025-09-26T16:27:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/c4/83/21550f81a50cd03599f048a2d588ffb7f4c4d8064ae091511e8e5848eeaa/simplejson-3.20.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f820a6ac2ef0bc338ae4963f4f82ccebdb0824fe9caf6d660670c578abe01013", size = 141654, upload-time = "2025-09-26T16:27:51.168Z" }, + { url = "https://files.pythonhosted.org/packages/cf/54/d76c0e72ad02450a3e723b65b04f49001d0e73218ef6a220b158a64639cb/simplejson-3.20.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e7a066528a5451433eb3418184f05682ea0493d14e9aae690499b7e1eb6b81", size = 144913, upload-time = "2025-09-26T16:27:52.331Z" }, + { url = "https://files.pythonhosted.org/packages/3f/49/976f59b42a6956d4aeb075ada16ad64448a985704bc69cd427a2245ce835/simplejson-3.20.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:438680ddde57ea87161a4824e8de04387b328ad51cfdf1eaf723623a3014b7aa", size = 144568, upload-time = "2025-09-26T16:27:53.41Z" }, + { url = "https://files.pythonhosted.org/packages/60/c7/30bae30424ace8cd791ca660fed454ed9479233810fe25c3f3eab3d9dc7b/simplejson-3.20.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cac78470ae68b8d8c41b6fca97f5bf8e024ca80d5878c7724e024540f5cdaadb", size = 146239, upload-time = "2025-09-26T16:27:54.502Z" }, + { url = "https://files.pythonhosted.org/packages/79/3e/7f3b7b97351c53746e7b996fcd106986cda1954ab556fd665314756618d2/simplejson-3.20.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7524e19c2da5ef281860a3d74668050c6986be15c9dd99966034ba47c68828c2", size = 154497, upload-time = "2025-09-26T16:27:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/1d/48/7241daa91d0bf19126589f6a8dcbe8287f4ed3d734e76fd4a092708947be/simplejson-3.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e9b6d845a603b2eef3394eb5e21edb8626cd9ae9a8361d14e267eb969dbe413", size = 148069, upload-time = "2025-09-26T16:27:57.039Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/ef18d2962fe53e7be5123d3784e623859eec7ed97060c9c8536c69d34836/simplejson-3.20.2-cp311-cp311-win32.whl", hash = "sha256:47d8927e5ac927fdd34c99cc617938abb3624b06ff86e8e219740a86507eb961", size = 74158, upload-time = "2025-09-26T16:27:58.265Z" }, + { url = "https://files.pythonhosted.org/packages/35/fd/3d1158ecdc573fdad81bf3cc78df04522bf3959758bba6597ba4c956c74d/simplejson-3.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba4edf3be8e97e4713d06c3d302cba1ff5c49d16e9d24c209884ac1b8455520c", size = 75911, upload-time = "2025-09-26T16:27:59.292Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9e/1a91e7614db0416885eab4136d49b7303de20528860ffdd798ce04d054db/simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6", size = 93523, upload-time = "2025-09-26T16:28:00.356Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2b/d2413f5218fc25608739e3d63fe321dfa85c5f097aa6648dbe72513a5f12/simplejson-3.20.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8fe6de652fcddae6dec8f281cc1e77e4e8f3575249e1800090aab48f73b4259", size = 75844, upload-time = "2025-09-26T16:28:01.756Z" }, + { url = "https://files.pythonhosted.org/packages/ad/f1/efd09efcc1e26629e120fef59be059ce7841cc6e1f949a4db94f1ae8a918/simplejson-3.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25ca2663d99328d51e5a138f22018e54c9162438d831e26cfc3458688616eca8", size = 75655, upload-time = "2025-09-26T16:28:03.037Z" }, + { url = "https://files.pythonhosted.org/packages/97/ec/5c6db08e42f380f005d03944be1af1a6bd501cc641175429a1cbe7fb23b9/simplejson-3.20.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a6b2816b6cab6c3fd273d43b1948bc9acf708272074c8858f579c394f4cbc9", size = 150335, upload-time = "2025-09-26T16:28:05.027Z" }, + { url = "https://files.pythonhosted.org/packages/81/f5/808a907485876a9242ec67054da7cbebefe0ee1522ef1c0be3bfc90f96f6/simplejson-3.20.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac20dc3fcdfc7b8415bfc3d7d51beccd8695c3f4acb7f74e3a3b538e76672868", size = 158519, upload-time = "2025-09-26T16:28:06.5Z" }, + { url = "https://files.pythonhosted.org/packages/66/af/b8a158246834645ea890c36136584b0cc1c0e4b83a73b11ebd9c2a12877c/simplejson-3.20.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0804d04564e70862ef807f3e1ace2cc212ef0e22deb1b3d6f80c45e5882c6b", size = 148571, upload-time = "2025-09-26T16:28:07.715Z" }, + { url = "https://files.pythonhosted.org/packages/20/05/ed9b2571bbf38f1a2425391f18e3ac11cb1e91482c22d644a1640dea9da7/simplejson-3.20.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979ce23ea663895ae39106946ef3d78527822d918a136dbc77b9e2b7f006237e", size = 152367, upload-time = "2025-09-26T16:28:08.921Z" }, + { url = "https://files.pythonhosted.org/packages/81/2c/bad68b05dd43e93f77994b920505634d31ed239418eb6a88997d06599983/simplejson-3.20.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2ba921b047bb029805726800819675249ef25d2f65fd0edb90639c5b1c3033c", size = 150205, upload-time = "2025-09-26T16:28:10.086Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/90c7fc878061adafcf298ce60cecdee17a027486e9dce507e87396d68255/simplejson-3.20.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:12d3d4dc33770069b780cc8f5abef909fe4a3f071f18f55f6d896a370fd0f970", size = 151823, upload-time = "2025-09-26T16:28:11.329Z" }, + { url = "https://files.pythonhosted.org/packages/ab/27/b85b03349f825ae0f5d4f780cdde0bbccd4f06c3d8433f6a3882df887481/simplejson-3.20.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aff032a59a201b3683a34be1169e71ddda683d9c3b43b261599c12055349251e", size = 158997, upload-time = "2025-09-26T16:28:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/71/ad/d7f3c331fb930638420ac6d236db68e9f4c28dab9c03164c3cd0e7967e15/simplejson-3.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e590e133b06773f0dc9c3f82e567463df40598b660b5adf53eb1c488202544", size = 154367, upload-time = "2025-09-26T16:28:14.393Z" }, + { url = "https://files.pythonhosted.org/packages/f0/46/5c67324addd40fa2966f6e886cacbbe0407c03a500db94fb8bb40333fcdf/simplejson-3.20.2-cp312-cp312-win32.whl", hash = "sha256:8d7be7c99939cc58e7c5bcf6bb52a842a58e6c65e1e9cdd2a94b697b24cddb54", size = 74285, upload-time = "2025-09-26T16:28:15.931Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c9/5cc2189f4acd3a6e30ffa9775bf09b354302dbebab713ca914d7134d0f29/simplejson-3.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:2c0b4a67e75b945489052af6590e7dca0ed473ead5d0f3aad61fa584afe814ab", size = 75969, upload-time = "2025-09-26T16:28:17.017Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9e/f326d43f6bf47f4e7704a4426c36e044c6bedfd24e072fb8e27589a373a5/simplejson-3.20.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90d311ba8fcd733a3677e0be21804827226a57144130ba01c3c6a325e887dd86", size = 93530, upload-time = "2025-09-26T16:28:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/35/28/5a4b8f3483fbfb68f3f460bc002cef3a5735ef30950e7c4adce9c8da15c7/simplejson-3.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feed6806f614bdf7f5cb6d0123cb0c1c5f40407ef103aa935cffaa694e2e0c74", size = 75846, upload-time = "2025-09-26T16:28:19.12Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4d/30dfef83b9ac48afae1cf1ab19c2867e27b8d22b5d9f8ca7ce5a0a157d8c/simplejson-3.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b1d8d7c3e1a205c49e1aee6ba907dcb8ccea83651e6c3e2cb2062f1e52b0726", size = 75661, upload-time = "2025-09-26T16:28:20.219Z" }, + { url = "https://files.pythonhosted.org/packages/09/1d/171009bd35c7099d72ef6afd4bb13527bab469965c968a17d69a203d62a6/simplejson-3.20.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552f55745044a24c3cb7ec67e54234be56d5d6d0e054f2e4cf4fb3e297429be5", size = 150579, upload-time = "2025-09-26T16:28:21.337Z" }, + { url = "https://files.pythonhosted.org/packages/61/ae/229bbcf90a702adc6bfa476e9f0a37e21d8c58e1059043038797cbe75b8c/simplejson-3.20.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2da97ac65165d66b0570c9e545786f0ac7b5de5854d3711a16cacbcaa8c472d", size = 158797, upload-time = "2025-09-26T16:28:22.53Z" }, + { url = "https://files.pythonhosted.org/packages/90/c5/fefc0ac6b86b9108e302e0af1cf57518f46da0baedd60a12170791d56959/simplejson-3.20.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f59a12966daa356bf68927fca5a67bebac0033cd18b96de9c2d426cd11756cd0", size = 148851, upload-time = "2025-09-26T16:28:23.733Z" }, + { url = "https://files.pythonhosted.org/packages/43/f1/b392952200f3393bb06fbc4dd975fc63a6843261705839355560b7264eb2/simplejson-3.20.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133ae2098a8e162c71da97cdab1f383afdd91373b7ff5fe65169b04167da976b", size = 152598, upload-time = "2025-09-26T16:28:24.962Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b4/d6b7279e52a3e9c0fa8c032ce6164e593e8d9cf390698ee981ed0864291b/simplejson-3.20.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7977640af7b7d5e6a852d26622057d428706a550f7f5083e7c4dd010a84d941f", size = 150498, upload-time = "2025-09-26T16:28:26.114Z" }, + { url = "https://files.pythonhosted.org/packages/62/22/ec2490dd859224326d10c2fac1353e8ad5c84121be4837a6dd6638ba4345/simplejson-3.20.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b530ad6d55e71fa9e93e1109cf8182f427a6355848a4ffa09f69cc44e1512522", size = 152129, upload-time = "2025-09-26T16:28:27.552Z" }, + { url = "https://files.pythonhosted.org/packages/33/ce/b60214d013e93dd9e5a705dcb2b88b6c72bada442a97f79828332217f3eb/simplejson-3.20.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bd96a7d981bf64f0e42345584768da4435c05b24fd3c364663f5fbc8fabf82e3", size = 159359, upload-time = "2025-09-26T16:28:28.667Z" }, + { url = "https://files.pythonhosted.org/packages/99/21/603709455827cdf5b9d83abe726343f542491ca8dc6a2528eb08de0cf034/simplejson-3.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f28ee755fadb426ba2e464d6fcf25d3f152a05eb6b38e0b4f790352f5540c769", size = 154717, upload-time = "2025-09-26T16:28:30.288Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f9/dc7f7a4bac16cf7eb55a4df03ad93190e11826d2a8950052949d3dfc11e2/simplejson-3.20.2-cp313-cp313-win32.whl", hash = "sha256:472785b52e48e3eed9b78b95e26a256f59bb1ee38339be3075dad799e2e1e661", size = 74289, upload-time = "2025-09-26T16:28:31.809Z" }, + { url = "https://files.pythonhosted.org/packages/87/10/d42ad61230436735c68af1120622b28a782877146a83d714da7b6a2a1c4e/simplejson-3.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:a1a85013eb33e4820286139540accbe2c98d2da894b2dcefd280209db508e608", size = 75972, upload-time = "2025-09-26T16:28:32.883Z" }, + { url = "https://files.pythonhosted.org/packages/08/b7/4f729a6dbbe7c67471a966341d9064af34867a2beda8629e34fc43ab1219/simplejson-3.20.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6c3a98e21e5f098e4f982ef302ebb1e681ff16a5d530cfce36296bea58fe2396", size = 74569, upload-time = "2025-09-26T16:28:46.938Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7a/493efa50d44c3f768f3dbf04f12b26568d7d52a4c846cdc2caf9dcf1d0a0/simplejson-3.20.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10cf9ca1363dc3711c72f4ec7c1caed2bbd9aaa29a8d9122e31106022dc175c6", size = 126805, upload-time = "2025-09-26T16:28:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/c3/07/50812d2734e3ea17828fee54007d37209ae3043f4f02484d6f383b3c88f6/simplejson-3.20.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:106762f8aedf3fc3364649bfe8dc9a40bf5104f872a4d2d86bae001b1af30d30", size = 135586, upload-time = "2025-09-26T16:28:49.257Z" }, + { url = "https://files.pythonhosted.org/packages/ba/38/6c1634665a294aea97ab0958fa6fab360dd22feed71ce302cdf6f768ff29/simplejson-3.20.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b21659898b7496322e99674739193f81052e588afa8b31b6a1c7733d8829b925", size = 123877, upload-time = "2025-09-26T16:28:50.41Z" }, + { url = "https://files.pythonhosted.org/packages/7e/af/7008c9aa859d81f422df6c51c599a9fcfe1017bcf6860ef09e3de6e66a61/simplejson-3.20.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fa1db6a02bca88829f2b2057c76a1d2dc2fccb8c5ff1199e352f213e9ec719", size = 127118, upload-time = "2025-09-26T16:28:51.597Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f2/9f3d1c17fb8ad046d0ac503929d0783bf5e4a26a6a400b90efc72cd81c18/simplejson-3.20.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:156139d94b660448ec8a4ea89f77ec476597f752c2ff66432d3656704c66b40e", size = 129079, upload-time = "2025-09-26T16:28:52.784Z" }, + { url = "https://files.pythonhosted.org/packages/99/b7/9c5d802015e6f20c0bddf3092cad0ea6ec3a9e1605d78b8e80334ff4949c/simplejson-3.20.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:b2620ac40be04dff08854baf6f4df10272f67079f61ed1b6274c0e840f2e2ae1", size = 129521, upload-time = "2025-09-26T16:28:53.993Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/da977546d9ebde4dd78453d07f2ebdc38338aa7b6b7c65d640432fdc0718/simplejson-3.20.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:9ccef5b5d3e3ac5d9da0a0ca1d2de8cf2b0fb56b06aa0ab79325fa4bcc5a1d60", size = 137530, upload-time = "2025-09-26T16:28:55.386Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d1/12ab0ac38c8a8a5db8b5b247006246066b15a549cc7c6b05d38a609584e1/simplejson-3.20.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f526304c2cc9fd8b8d18afacb75bc171650f83a7097b2c92ad6a431b5d7c1b72", size = 130660, upload-time = "2025-09-26T16:28:56.986Z" }, + { url = "https://files.pythonhosted.org/packages/0a/94/3d71054d8e5e3822b093a3004fea1faddf5c9b10209dfff436828284c709/simplejson-3.20.2-cp37-cp37m-win32.whl", hash = "sha256:e0f661105398121dd48d9987a2a8f7825b8297b3b2a7fe5b0d247370396119d5", size = 73940, upload-time = "2025-09-26T16:28:58.134Z" }, + { url = "https://files.pythonhosted.org/packages/45/7f/0d0b432c7c3770b8b9dcb1be4e7c652ae4a201b035d6110151a7599a06ac/simplejson-3.20.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dab98625b3d6821e77ea59c4d0e71059f8063825a0885b50ed410e5c8bd5cb66", size = 75735, upload-time = "2025-09-26T16:28:59.239Z" }, + { url = "https://files.pythonhosted.org/packages/2a/13/f290da83da1083051b1665e2508a70821fc1a62c4b6d73f5c7baadcba26c/simplejson-3.20.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b8205f113082e7d8f667d6cd37d019a7ee5ef30b48463f9de48e1853726c6127", size = 92074, upload-time = "2025-09-26T16:29:00.409Z" }, + { url = "https://files.pythonhosted.org/packages/99/d9/d23e9f96762224870af95adafcd5d4426f5285b046ed331b034c6d5a8554/simplejson-3.20.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fc8da64929ef0ff16448b602394a76fd9968a39afff0692e5ab53669df1f047f", size = 74761, upload-time = "2025-09-26T16:29:01.574Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/92bc0c1da0e805d4894ffe15a76af733e276d27eede5361c8be1c028e692/simplejson-3.20.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfe704864b5fead4f21c8d448a89ee101c9b0fc92a5f40b674111da9272b3a90", size = 75047, upload-time = "2025-09-26T16:29:02.704Z" }, + { url = "https://files.pythonhosted.org/packages/bc/42/1ae6f9735d8fe47a638c5a342b835a2108ae7d7f79e7f83224d72c87cc81/simplejson-3.20.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ca7cbe7d2f423b97ed4e70989ef357f027a7e487606628c11b79667639dc84", size = 136635, upload-time = "2025-09-26T16:29:03.894Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/7e087b061d6f94e6ba41c2e589267b9349fd3abb27ce59080c1c89fe9785/simplejson-3.20.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cec1868b237fe9fb2d466d6ce0c7b772e005aadeeda582d867f6f1ec9710cad", size = 146012, upload-time = "2025-09-26T16:29:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/08/a1/69a6e4ec69b585724cc9bee2d7f725c155d3ab8f9d3925b67c709a6e5a19/simplejson-3.20.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:792debfba68d8dd61085ffb332d72b9f5b38269cda0c99f92c7a054382f55246", size = 133135, upload-time = "2025-09-26T16:29:06.475Z" }, + { url = "https://files.pythonhosted.org/packages/6b/92/a75df930e2ff29e37654b65fa6eebef53812fa7258a9df9c7ddbf60610d7/simplejson-3.20.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e022b2c4c54cb4855e555f64aa3377e3e5ca912c372fa9e3edcc90ebbad93dce", size = 136834, upload-time = "2025-09-26T16:29:07.663Z" }, + { url = "https://files.pythonhosted.org/packages/86/6f/2de88bea65e0fdb91cc55624bd77e2eaa5c3acccc59287b058b376acc9a2/simplejson-3.20.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5de26f11d5aca575d3825dddc65f69fdcba18f6ca2b4db5cef16f41f969cef15", size = 137298, upload-time = "2025-09-26T16:29:10.122Z" }, + { url = "https://files.pythonhosted.org/packages/44/2b/dd9ec681115aa65620d57c88eb741bd7e7bc303ac6247554d854ee5168e6/simplejson-3.20.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:e2162b2a43614727ec3df75baeda8881ab129824aa1b49410d4b6c64f55a45b4", size = 138287, upload-time = "2025-09-26T16:29:11.42Z" }, + { url = "https://files.pythonhosted.org/packages/92/ee/8f45174d2988ec5242ab3c9229693ed6b839a4eb77ee42d7c470cc5846ab/simplejson-3.20.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e11a1d6b2f7e72ca546bdb4e6374b237ebae9220e764051b867111df83acbd13", size = 147295, upload-time = "2025-09-26T16:29:12.723Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/ab88e99111307eba64bcfbef45e8aa57240a19e019c2dc29269806d2f4a0/simplejson-3.20.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:daf7cd18fe99eb427fa6ddb6b437cfde65125a96dc27b93a8969b6fe90a1dbea", size = 138857, upload-time = "2025-09-26T16:29:13.927Z" }, + { url = "https://files.pythonhosted.org/packages/2e/55/58f29500ee6f6bd78bfff4a0893cf9f050d3caf315f175e6263b6e8c0764/simplejson-3.20.2-cp38-cp38-win32.whl", hash = "sha256:da795ea5f440052f4f497b496010e2c4e05940d449ea7b5c417794ec1be55d01", size = 74281, upload-time = "2025-09-26T16:29:15.119Z" }, + { url = "https://files.pythonhosted.org/packages/69/c0/1ffd3fe5be619474f955fb8c1ec0a875a6abf3381a32c904c2061c646ba5/simplejson-3.20.2-cp38-cp38-win_amd64.whl", hash = "sha256:6a4b5e7864f952fcce4244a70166797d7b8fd6069b4286d3e8403c14b88656b6", size = 75976, upload-time = "2025-09-26T16:29:16.226Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2d/7c4968c60ddc8b504b77301cc80d6e75cd0269b81a779b01d66d8f36dcb8/simplejson-3.20.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3bf76512ccb07d47944ebdca44c65b781612d38b9098566b4bb40f713fc4047", size = 94039, upload-time = "2025-09-26T16:29:17.406Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e4/d96b56fb87f245240b514c1fe552e76c17e09f0faa1f61137b2296f81529/simplejson-3.20.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:214e26acf2dfb9ff3314e65c4e168a6b125bced0e2d99a65ea7b0f169db1e562", size = 75893, upload-time = "2025-09-26T16:29:18.534Z" }, + { url = "https://files.pythonhosted.org/packages/09/4f/be411eeb52ab21d6d4c00722b632dd2bd430c01a47dfed3c15ef5ad7ee6e/simplejson-3.20.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2fb1259ca9c385b0395bad59cdbf79535a5a84fb1988f339a49bfbc57455a35a", size = 76104, upload-time = "2025-09-26T16:29:19.66Z" }, + { url = "https://files.pythonhosted.org/packages/66/6f/3bd0007b64881a90a058c59a4869b1b4f130ddb86a726f884fafc67e5ef7/simplejson-3.20.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34e028a2ba8553a208ded1da5fa8501833875078c4c00a50dffc33622057881", size = 138261, upload-time = "2025-09-26T16:29:20.822Z" }, + { url = "https://files.pythonhosted.org/packages/15/5d/b6d0b71508e503c759a0a7563cb2c28716ec8af9828ca9f5b59023011406/simplejson-3.20.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b538f9d9e503b0dd43af60496780cb50755e4d8e5b34e5647b887675c1ae9fee", size = 146397, upload-time = "2025-09-26T16:29:22.363Z" }, + { url = "https://files.pythonhosted.org/packages/19/24/40b3e5a3ca5e6f80cc1c639fcd5565ae087e72e8656dea780f02302ddc97/simplejson-3.20.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab998e416ded6c58f549a22b6a8847e75a9e1ef98eb9fbb2863e1f9e61a4105b", size = 134020, upload-time = "2025-09-26T16:29:23.615Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8c/8fc2c2734ac9e514124635b25ca8f7e347db1ded4a30417ee41e78e6d61c/simplejson-3.20.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a8f1c307edf5fbf0c6db3396c5d3471409c4a40c7a2a466fbc762f20d46601a", size = 137598, upload-time = "2025-09-26T16:29:24.835Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d9/15036d7f43c6208fb0fbc827f9f897c1f577fba02aeb7a8a223581da4925/simplejson-3.20.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5a7bbac80bdb82a44303f5630baee140aee208e5a4618e8b9fde3fc400a42671", size = 139770, upload-time = "2025-09-26T16:29:26.244Z" }, + { url = "https://files.pythonhosted.org/packages/73/cc/18374fb9dfcb4827b692ca5a33bdb607384ca06cdb645e0b863022dae8a3/simplejson-3.20.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5ef70ec8fe1569872e5a3e4720c1e1dcb823879a3c78bc02589eb88fab920b1f", size = 139884, upload-time = "2025-09-26T16:29:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a2/1526d4152806670124dd499ff831726a92bd7e029e8349c4affa78ea8845/simplejson-3.20.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:cb11c09c99253a74c36925d461c86ea25f0140f3b98ff678322734ddc0f038d7", size = 148166, upload-time = "2025-09-26T16:29:29.789Z" }, + { url = "https://files.pythonhosted.org/packages/a4/77/fc16d41b5f67a2591c9b6ff7b0f6aed2b2aed1b6912bb346b61279697638/simplejson-3.20.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:66f7c78c6ef776f8bd9afaad455e88b8197a51e95617bcc44b50dd974a7825ba", size = 140778, upload-time = "2025-09-26T16:29:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/4a/97/a26ef6b7387349623c042f329df70a4f3baf3a365fe6d1154d73da1dcf5a/simplejson-3.20.2-cp39-cp39-win32.whl", hash = "sha256:619ada86bfe3a5aa02b8222ca6bfc5aa3e1075c1fb5b3263d24ba579382df472", size = 74339, upload-time = "2025-09-26T16:29:32.648Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b7/94c6049a99e3c04eed2064e91295370b7429e2361188e35a78df562312e0/simplejson-3.20.2-cp39-cp39-win_amd64.whl", hash = "sha256:44a6235e09ca5cc41aa5870a952489c06aa4aee3361ae46daa947d8398e57502", size = 76067, upload-time = "2025-09-26T16:29:34.184Z" }, + { url = "https://files.pythonhosted.org/packages/05/5b/83e1ff87eb60ca706972f7e02e15c0b33396e7bdbd080069a5d1b53cf0d8/simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017", size = 57309, upload-time = "2025-09-26T16:29:35.312Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "snapshot-phantomjs" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/eb/45df473390e917623ad92b48a8f9c0041c000b6aa2061474e0f69bd97e2e/snapshot-phantomjs-0.0.3.tar.gz", hash = "sha256:3c94bc4b81147e4b44b3f745e3fbe52db4536729cdd464cbfdd2edbfb0789947", size = 4586, upload-time = "2019-04-15T17:51:55.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/8f/6405ee67604ec430607a76f78071b6151dcdd69cdda96d623485e2591e0c/snapshot_phantomjs-0.0.3-py2.py3-none-any.whl", hash = "sha256:ddfb60f8edee4f9c88bd9e6055364f44915e0db7e282ed2710129ff151ed1044", size = 3696, upload-time = "2019-04-15T17:51:57.366Z" }, +] + +[[package]] +name = "snapshot-pyppeteer" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nest-asyncio" }, + { name = "pyppeteer", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pyppeteer", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/7d/063e0b8d0d0017ccffe23f0d22451fae53f2d7843e9609658a7e019a1ae9/snapshot_pyppeteer-0.0.2-py3-none-any.whl", hash = "sha256:c47c2d4243c97c5f0124c56892927dd094f02e357bcdd30f0c4689ae3aa83b58", size = 3872, upload-time = "2019-11-13T08:20:57.23Z" }, +] + +[[package]] +name = "snapshot-selenium" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "selenium", version = "4.11.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "selenium", version = "4.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "selenium", version = "4.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/f0/09681e5f3674c3beb3cc0fbf39a50c7b57cd9ffc556257bc4aedf6e33820/snapshot-selenium-0.0.2.tar.gz", hash = "sha256:c5def5a05380beefe20b9f4ac6af8021175db66acc5d7a739ff82b513938c6d0", size = 4045, upload-time = "2019-04-12T12:02:28.729Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/d9/89e6835708320f8700246a5683133bf99f2743eaaa218e6f36f31fb7f7e7/snapshot_selenium-0.0.2-py2.py3-none-any.whl", hash = "sha256:0612e3db63370eac9a962736b10a81b2bad79946b773ede608b51302c9bb199c", size = 2963, upload-time = "2019-04-12T12:02:30.798Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164, upload-time = "2022-02-08T10:54:04.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757, upload-time = "2022-02-08T10:54:02.017Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "trio" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "attrs", version = "24.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "cffi", version = "1.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8' and implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.8'" }, + { name = "idna", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "outcome", marker = "python_full_version < '3.8'" }, + { name = "sniffio", marker = "python_full_version < '3.8'" }, + { name = "sortedcontainers", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/b0/5ec370ef69832f3d6d79069af7097dcec0a8c68fa898822e49ad621c4af0/trio-0.22.2.tar.gz", hash = "sha256:3887cf18c8bcc894433420305468388dac76932e9668afa1c49aa3806b6accb3", size = 487602, upload-time = "2023-07-12T23:09:17.591Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dd/b61fa61b186d3267ef3903048fbee29132963ae762fb70b08d4a3cd6f7aa/trio-0.22.2-py3-none-any.whl", hash = "sha256:f43da357620e5872b3d940a2e3589aa251fd3f881b65a608d742e00809b1ec38", size = 400217, upload-time = "2023-07-12T23:09:15.884Z" }, +] + +[[package]] +name = "trio" +version = "0.27.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "attrs", version = "25.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*' and implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.8.*'" }, + { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "outcome", marker = "python_full_version == '3.8.*'" }, + { name = "sniffio", marker = "python_full_version == '3.8.*'" }, + { name = "sortedcontainers", marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/d1/a83dee5be404da7afe5a71783a33b8907bacb935a6dc8c69ab785e4a3eed/trio-0.27.0.tar.gz", hash = "sha256:1dcc95ab1726b2da054afea8fd761af74bad79bd52381b84eae408e983c76831", size = 568064, upload-time = "2024-10-16T23:29:23.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/83/ec3196c360afffbc5b342ead48d1eb7393dd74fa70bca75d33905a86f211/trio-0.27.0-py3-none-any.whl", hash = "sha256:68eabbcf8f457d925df62da780eff15ff5dc68fd6b367e2dde59f7aaf2a0b884", size = 481734, upload-time = "2024-10-16T23:29:21.459Z" }, +] + +[[package]] +name = "trio" +version = "0.31.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "attrs", version = "25.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*' and implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.9.*'" }, + { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "outcome", marker = "python_full_version == '3.9.*'" }, + { name = "sniffio", marker = "python_full_version == '3.9.*'" }, + { name = "sortedcontainers", marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/8f/c6e36dd11201e2a565977d8b13f0b027ba4593c1a80bed5185489178e257/trio-0.31.0.tar.gz", hash = "sha256:f71d551ccaa79d0cb73017a33ef3264fde8335728eb4c6391451fe5d253a9d5b", size = 605825, upload-time = "2025-09-09T15:17:15.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/5b/94237a3485620dbff9741df02ff6d8acaa5fdec67d81ab3f62e4d8511bf7/trio-0.31.0-py3-none-any.whl", hash = "sha256:b5d14cd6293d79298b49c3485ffd9c07e3ce03a6da8c7dfbe0cb3dd7dc9a4774", size = 512679, upload-time = "2025-09-09T15:17:13.821Z" }, +] + +[[package]] +name = "trio" +version = "0.32.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "attrs", version = "25.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "idna", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "outcome", marker = "python_full_version >= '3.10'" }, + { name = "sniffio", marker = "python_full_version >= '3.10'" }, + { name = "sortedcontainers", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/ce/0041ddd9160aac0031bcf5ab786c7640d795c797e67c438e15cfedf815c8/trio-0.32.0.tar.gz", hash = "sha256:150f29ec923bcd51231e1d4c71c7006e65247d68759dd1c19af4ea815a25806b", size = 605323, upload-time = "2025-10-31T07:18:17.466Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/bf/945d527ff706233636c73880b22c7c953f3faeb9d6c7e2e85bfbfd0134a0/trio-0.32.0-py3-none-any.whl", hash = "sha256:4ab65984ef8370b79a76659ec87aa3a30c5c7c83ff250b4de88c29a8ab6123c5", size = 512030, upload-time = "2025-10-31T07:18:15.885Z" }, +] + +[[package]] +name = "trio-websocket" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.8'" }, + { name = "trio", version = "0.22.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "wsproto", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/36/abad2385853077424a11b818d9fd8350d249d9e31d583cb9c11cd4c85eda/trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f", size = 26511, upload-time = "2023-09-26T23:24:58.753Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/be/a9ae5f50cad5b6f85bd2574c2c923730098530096e170c1ce7452394d7aa/trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638", size = 17408, upload-time = "2023-09-26T23:24:56.788Z" }, +] + +[[package]] +name = "trio-websocket" +version = "0.12.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version >= '3.8' and python_full_version < '3.11'" }, + { name = "outcome", marker = "python_full_version >= '3.8'" }, + { name = "trio", version = "0.27.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "trio", version = "0.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "trio", version = "0.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "wsproto", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.10'" }, + { name = "wsproto", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/3c/8b4358e81f2f2cfe71b66a267f023a91db20a817b9425dd964873796980a/trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae", size = 33549, upload-time = "2025-02-25T05:16:58.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/19/eb640a397bba49ba49ef9dbe2e7e5c04202ba045b6ce2ec36e9cadc51e04/trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6", size = 21221, upload-time = "2025-02-25T05:16:57.545Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2", size = 72876, upload-time = "2023-07-02T14:20:55.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", size = 33232, upload-time = "2023-07-02T14:20:53.275Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "urllib3" +version = "1.26.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380, upload-time = "2024-08-29T15:43:11.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225, upload-time = "2024-08-29T15:43:08.921Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] + +[[package]] +name = "websockets" +version = "10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/dc/549a807a53c13fd4a8dac286f117a7a71260defea9ec0c05d6027f2ae273/websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3", size = 84877, upload-time = "2022-10-25T20:12:37.712Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/88/81c08fb3418c5aedf3776333f29443599729509a4f673d6598dd769d3d6b/websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48", size = 100633, upload-time = "2022-10-25T20:10:29.571Z" }, + { url = "https://files.pythonhosted.org/packages/68/bd/c8bd8354fc629863a2db39c9182d40297f47dfb2ed3e178bc83041ce044b/websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab", size = 97870, upload-time = "2022-10-25T20:10:33.577Z" }, + { url = "https://files.pythonhosted.org/packages/20/7a/bd0ce7ac1cfafc76c84d6e8051bcbd0f7def8e45207230833bd6ff77a41d/websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c", size = 97945, upload-time = "2022-10-25T20:10:34.736Z" }, + { url = "https://files.pythonhosted.org/packages/25/a7/4e32f8edfc26339d8d170fe539e0b83a329c42d974dacfe07a0566390aef/websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032", size = 107446, upload-time = "2022-10-25T20:10:36.154Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4b/cab8fed34c3a29d4594ff77234f6e6b45feb35331f1c12fccf92ca5486dd/websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4", size = 106455, upload-time = "2022-10-25T20:10:37.774Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6f/2388f9304cdaa0215b6388f837c6dbfe6d63ac1bba8c196e3b14eea1831e/websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50", size = 106763, upload-time = "2022-10-25T20:10:39.035Z" }, + { url = "https://files.pythonhosted.org/packages/0c/56/b2d373ed19b4e7b6c5c7630d598ba10473fa6131e67e69590214ab18bc09/websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8", size = 111489, upload-time = "2022-10-25T20:10:40.132Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f6/83da14582fbb0496c47a4c039bd6e802886a0c300e9795c0f839fd1498e3/websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1", size = 110721, upload-time = "2022-10-25T20:10:41.174Z" }, + { url = "https://files.pythonhosted.org/packages/37/02/ef21ca4698c2fd950250e5ac397fd07b0c9f16bbd073d0ea64c25baef9c1/websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331", size = 111343, upload-time = "2022-10-25T20:10:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6f/60e5f6e114b6077683d74da5df0d4af647a9e6d2a18b4698f577b2cb7c14/websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a", size = 100918, upload-time = "2022-10-25T20:10:43.785Z" }, + { url = "https://files.pythonhosted.org/packages/1e/76/163a18626001465a309bf74b6aeb301d7092e304637fe00f89d7efc75c44/websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089", size = 101442, upload-time = "2022-10-25T20:10:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/60/3a/6dccbe2725d13c398b90cbebeea684cda7792e6d874f96417db900556ad0/websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4", size = 100656, upload-time = "2022-10-25T20:10:46.423Z" }, + { url = "https://files.pythonhosted.org/packages/d1/60/0a6cb94e25b981e428c1cdcc2b0a406ac6e1dfc78d8a81c8a4ee7510e853/websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f", size = 97896, upload-time = "2022-10-25T20:10:47.511Z" }, + { url = "https://files.pythonhosted.org/packages/cc/19/2f003f9f81c0fab2eabb81d8fc2fce5fb5b5714f1b4abfe897cb209e031d/websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c", size = 97947, upload-time = "2022-10-25T20:10:51.144Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3c/fc1725524e48f624df77f5998b1c7070fdddec3ae67a2ffbc99ffd116269/websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46", size = 108024, upload-time = "2022-10-25T20:10:52.237Z" }, + { url = "https://files.pythonhosted.org/packages/68/ec/3267f8bbe8a4a5e181ab3fc67cc137f0966ab9e9a4da14ffc603f320b9e6/websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96", size = 107020, upload-time = "2022-10-25T20:10:55.856Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5d/d0b039f0db0bb1fea93437721cf3cd8a244ad02a86960c38a3853d5e1fab/websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa", size = 107398, upload-time = "2022-10-25T20:10:56.983Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/9489869aa591e6a8941b0af2302f8383e199e90477559a510713d41bfa45/websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c", size = 112842, upload-time = "2022-10-25T20:10:58.191Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f0/195097822f8edc4ffa355f6463a1890928577517382c0baededc760f9397/websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106", size = 112118, upload-time = "2022-10-25T20:10:59.279Z" }, + { url = "https://files.pythonhosted.org/packages/19/a3/02ce75ffca3ef147cc0f44647c67acb3171b5a09910b5b9f083b5ca395a6/websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9", size = 112714, upload-time = "2022-10-25T20:11:02.298Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fc/a818cddc63589e12d5eff9b51a59aad82e2adf35279493248a3742c41f85/websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8", size = 100918, upload-time = "2022-10-25T20:11:05.475Z" }, + { url = "https://files.pythonhosted.org/packages/27/bb/6327e8c7d4dd7d5b450b409a461be278968ce05c54da13da581ac87661db/websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882", size = 101444, upload-time = "2022-10-25T20:11:06.603Z" }, + { url = "https://files.pythonhosted.org/packages/88/97/d70e2d528b9ffe759134e5db6b1424b61cd61fd1c4471b178c76e01f41af/websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb", size = 97789, upload-time = "2022-10-25T20:11:07.639Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a501ed176c69b51ca83f4186bad80bba9b59ab354fd8954d7d36cb2ec47f/websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc", size = 107383, upload-time = "2022-10-25T20:11:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/dbffb63e7da0ada24e9ef8802c439169e0ed9a7ef8f6049874e6cbfc7919/websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033", size = 106459, upload-time = "2022-10-25T20:11:13.529Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/dea889793d2d0958be254fc86dac528d97de9354d16fcdbcbad259750014/websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41", size = 106724, upload-time = "2022-10-25T20:11:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/09/35/2b8ed52dc995507476ebbb7a91a0c5ed80fd80fa0a840f422ac25c722dbf/websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df", size = 112123, upload-time = "2022-10-25T20:11:19.386Z" }, + { url = "https://files.pythonhosted.org/packages/e9/48/a0751eafbeab06866fc70a66f7dfa08422cb96113af9138e526e7b106f14/websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea", size = 111384, upload-time = "2022-10-25T20:11:21.079Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f0/437187175182beed10246f53ef9793a5f6e087ce71ee25b64fdb12e396e0/websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c", size = 112017, upload-time = "2022-10-25T20:11:24.088Z" }, + { url = "https://files.pythonhosted.org/packages/1d/06/5ecd0434cf35f92ca9ce80e38a3ac9bf5422ace9488693c3900e2f1c7fa0/websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038", size = 98614, upload-time = "2022-10-25T20:11:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/ab/41/ed2fecb228c1f25cea03fce4a22a86f7771a10875d5762e777e943bb7d68/websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28", size = 101444, upload-time = "2022-10-25T20:11:30.58Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/3bdc2ea97d7da70d9f184051dcd40f27c849ded517ea9bab70df677a6b23/websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94", size = 100627, upload-time = "2022-10-25T20:11:33.577Z" }, + { url = "https://files.pythonhosted.org/packages/29/33/dd88aefeabc9dddb4f48c9e15c6c2554dfb6b4cf8d8f1b4de4d12ba997de/websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63", size = 97868, upload-time = "2022-10-25T20:11:36.818Z" }, + { url = "https://files.pythonhosted.org/packages/47/58/69435f1479acb56b3678905b5f2be57908a201c28465d4368d91f52cad76/websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf", size = 97938, upload-time = "2022-10-25T20:11:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/d7c73e62cf19f068850ddab548837329dab9c023567f5834747f61cdc747/websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6", size = 107628, upload-time = "2022-10-25T20:11:42.78Z" }, + { url = "https://files.pythonhosted.org/packages/71/93/5a4f408177e43d84274e1c08cbea3e50ad80db654dc25a0bba79dbdc00b4/websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8", size = 106696, upload-time = "2022-10-25T20:11:44.423Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a5/e4535867a96bb07000c54172e1be82cd0b3a95339244cac1d400f8ba9b64/websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b", size = 106983, upload-time = "2022-10-25T20:11:48.188Z" }, + { url = "https://files.pythonhosted.org/packages/33/3a/72c9d733d676447da2c89a35c694f779a9a360cff51ee0f90bb562d80cd4/websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c", size = 111314, upload-time = "2022-10-25T20:11:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/e6/94/cb97e5a9d019e473a37317a740852850ef09e14c02621dd86a898ec90f7a/websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b", size = 110559, upload-time = "2022-10-25T20:11:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/b4/91/c460f5164af303b31f58362935f7b8ed1750e3b8fbcb900da4b0661532a8/websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56", size = 111189, upload-time = "2022-10-25T20:11:55.811Z" }, + { url = "https://files.pythonhosted.org/packages/f9/15/ab0e9155700d3037ffe4a146a719f3e68ee025c9d45d6a39b027e928db52/websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a", size = 100919, upload-time = "2022-10-25T20:11:58.839Z" }, + { url = "https://files.pythonhosted.org/packages/63/f2/ec4c59b4f91936eb2a5ddcf2f7e57184acbce5122d5d83911c5a47f25144/websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6", size = 101439, upload-time = "2022-10-25T20:12:02.123Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8b/854b3625cc5130e4af8a10a7502c2f6c16d1bd107ff009394127a2f8abb3/websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f", size = 100629, upload-time = "2022-10-25T20:12:03.158Z" }, + { url = "https://files.pythonhosted.org/packages/47/4d/f2e28f112302d3bc794b74ae64656255161d8223f4d47bd17d40cbb3629e/websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112", size = 97868, upload-time = "2022-10-25T20:12:04.294Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/df17197565e8874f0a77f8211304169ad4f39ffa3e8c008a7b0bf187a238/websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f", size = 97944, upload-time = "2022-10-25T20:12:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/93/7b/72134e4c75002e311c072f0665fe45f7321d614c5c65181888faddd616e9/websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d", size = 107227, upload-time = "2022-10-25T20:12:07.663Z" }, + { url = "https://files.pythonhosted.org/packages/d6/7c/79ea4e7f56dfe7f703213000bbbd29b70cef2666698d98b66ce1af43caee/websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4", size = 106228, upload-time = "2022-10-25T20:12:09.016Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f9/f64ec37da654351b212e5534b0e31703ed80d2a6acb6b8c1b1373fafa876/websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0", size = 106533, upload-time = "2022-10-25T20:12:10.143Z" }, + { url = "https://files.pythonhosted.org/packages/36/8f/6dd75723ea67d54dec3a597ad781642c0febe8d51f233b95347981c0e549/websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c", size = 111209, upload-time = "2022-10-25T20:12:11.366Z" }, + { url = "https://files.pythonhosted.org/packages/62/76/c2411e634979cc6e812ef2a96aa295545cfcbc9566b298db09f3f4639d62/websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269", size = 110459, upload-time = "2022-10-25T20:12:12.8Z" }, + { url = "https://files.pythonhosted.org/packages/86/8e/390e0e3db702c55d31ca3999c622bb3b8b480c306c1bdee6a2da44b13b1b/websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b", size = 111081, upload-time = "2022-10-25T20:12:14.05Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1e/8f34d7ee924dc7a624c1e14f43209484cb5eccb58e892285d45551729a95/websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588", size = 100916, upload-time = "2022-10-25T20:12:15.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/00/9776e2626a30e3455a830665e50cf40f5d34a4134272b3138a637afa38a7/websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74", size = 101439, upload-time = "2022-10-25T20:12:16.647Z" }, + { url = "https://files.pythonhosted.org/packages/00/15/611ddaca66937f77aa5021e97c9bec61e6a30668b75db3707713b69b3b88/websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193", size = 97732, upload-time = "2022-10-25T20:12:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1a/2e4afd95abd33bd6ad77042270f8eee3697e07cdd749c068bff08bba2022/websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342", size = 99733, upload-time = "2022-10-25T20:12:18.856Z" }, + { url = "https://files.pythonhosted.org/packages/90/e1/22e43e9a1fbc9ddf4a0317b231e2e28eddfbe8804b7ca4a9f7fba7033b17/websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179", size = 99337, upload-time = "2022-10-25T20:12:20.098Z" }, + { url = "https://files.pythonhosted.org/packages/4a/39/3b6b64f775f1f4f5de6eb909d72f3f794f453730b5b3176fa5021ff334ba/websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485", size = 99286, upload-time = "2022-10-25T20:12:21.539Z" }, + { url = "https://files.pythonhosted.org/packages/c6/41/07f39745017af5381aeb6c1d8c6509aa1861193c948648d4aaf4d0637915/websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631", size = 101482, upload-time = "2022-10-25T20:12:23.083Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ba/74b4b92cc41ffc4cfa791fb9f8e8ab7c4d9bf84e54a5bef12ab23eb54880/websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0", size = 97731, upload-time = "2022-10-25T20:12:24.263Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/155c3582fd69b60d9c490fb0e64e37269c55d5873cbcb37f83e2d3feb078/websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393", size = 99731, upload-time = "2022-10-25T20:12:25.827Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/145d2883dfeffedf541a7c95bb26f8d8b5ddca84a7c8f671ec3b878ae7cd/websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576", size = 99338, upload-time = "2022-10-25T20:12:27.211Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cb/d394efe7b0ee6cdeffac28a1cb054e42f9f95974885ca3bcd6fceb0acde1/websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f", size = 99286, upload-time = "2022-10-25T20:12:28.395Z" }, + { url = "https://files.pythonhosted.org/packages/03/e2/7784912651a299a5e060656e6368946ae4c1da63f01236f7d650e8070cf8/websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1", size = 101483, upload-time = "2022-10-25T20:12:29.856Z" }, + { url = "https://files.pythonhosted.org/packages/e0/8d/7bffabd3f10a88cd68080669b33f407471283becf7e5cb4f0143b117211d/websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4", size = 97729, upload-time = "2022-10-25T20:12:31.135Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5c/7dc1f604688f43168ef17313055e048c755a29afde821f7e0b19bd3a180f/websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf", size = 99731, upload-time = "2022-10-25T20:12:32.604Z" }, + { url = "https://files.pythonhosted.org/packages/a0/92/aa8d1ba3a7e3e6cf6d5d1c929530a40138667ea60454bf5c0fff3b93cae2/websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3", size = 99337, upload-time = "2022-10-25T20:12:33.871Z" }, + { url = "https://files.pythonhosted.org/packages/2e/dd/521f0574bed6d08ce5e0acd5893ae418c0a81ef55eb4c960aedac9cbd929/websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13", size = 99288, upload-time = "2022-10-25T20:12:35.077Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/07f31d9f9e142b38cde8d3ea0c8ea1bacf9bc366f2f573eca57086e9f2a6/websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72", size = 101482, upload-time = "2022-10-25T20:12:36.224Z" }, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +dependencies = [ + { name = "h11", version = "0.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, +] + +[[package]] +name = "wsproto" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b8701c2c19a14c913c120b882d50b014ca0d38083c2c/wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294", size = 50116, upload-time = "2025-11-20T18:18:01.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" }, +] + +[[package]] +name = "zipp" +version = "3.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine != 'aarch64' and platform_machine != 'arm64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'aarch64'", + "python_full_version >= '3.7.1' and python_full_version < '3.8' and platform_machine == 'arm64'", + "python_full_version < '3.7.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", size = 18454, upload-time = "2023-02-25T02:17:22.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", size = 6758, upload-time = "2023-02-25T02:17:20.807Z" }, +] + +[[package]] +name = "zipp" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version >= '3.8' and python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199, upload-time = "2024-09-13T13:44:16.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200, upload-time = "2024-09-13T13:44:14.38Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +]