`
72 | - 其中常用的`type`包括 docs(文档、日志修改)、feat(新功能)、fix(问题修复)、refactor(代码重构)等,请根据实际情况选择。
73 | - 请用简短精确的英文描述编写 description
74 |
75 | ### Step4:合并修改
76 |
77 | - 一个常见的问题是远程的 upstream (@visactor/py-vchart) 有了新的更新, 从而会导致我们提交的 Pull Request 时会导致冲突。 因此我们可以在提交前先把远程其他开发者的 commit 和我们的 commit 合并。使用以下代码切换到 develop 分支:
78 |
79 | ```
80 | git checkout develop
81 | ```
82 |
83 | - 使用以下代码拉出远程的最新代码:
84 |
85 | ```
86 | git pull upstream develop
87 | ```
88 |
89 | - 切换回自己的开发分支:
90 |
91 | ```
92 | git checkout docs/add-funnel-demo
93 | ```
94 |
95 | - 把 develop 的 commit 合并到自己分支:
96 |
97 | ```
98 | git rebase develop
99 | ```
100 |
101 | - 把更新代码提交到自己的分支中:
102 |
103 | ```
104 | git push origin docs/add-funnel-demo
105 | ```
106 |
107 | ### Step5:提交 Pull Request
108 |
109 | 你可以在你的 github 代码仓库页面点击 `Compare & pull request` 按钮。
110 |
111 | 
112 |
113 | 或通过 `contribute` 按钮创建:
114 |
115 |
116 |
117 |
118 |
119 | 按照模板填写本次提交的修改内容:
120 |
121 | - 勾选这是什么类型的修改
122 |
123 |
124 |
125 |
126 | - 填写关联的 issue
127 |
128 |
129 |
130 |
131 |
132 | - 若有复杂变更,请说明背景和解决方案
133 |
134 |
135 |
136 |
137 |
138 | 相关信息填写完成后,点击 Create pull request 提交。
139 |
140 | ## **轻松步入 py-vchart 开源贡献之旅**
141 |
142 | "**good first issue**" 是一个在开源社区常见的标签,这个标签的目的是帮助新贡献者找到适合入门的问题。
143 |
144 | py-vchart 的入门问题,你可以通过 [issue 列表](https://github.com/VisActor/py-vchart/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) 查看,目前包括两类:
145 |
146 | - Demo 案例制作
147 | - 简单功能开发
148 |
149 | 如果你当前**有时间和意愿**参与到社区贡献,可以在 issue 里看一看 **good first issue**,选择一个感兴趣、适合自己的认领。
150 |
151 | 相信你一定是一个有始有终的同学,所以,当你了解并决定认领一个 issue 后,请在 issue 下留言告知大家。
152 |
153 | ### Demo Task 开发指南
154 |
155 | 待补充...
156 |
157 | ## 拥抱 VisActor 社区
158 |
159 | 在你为 VisActor 贡献代码之余,我们鼓励你参与其他让社区更加繁荣的事情,比如:
160 |
161 | 1. 为项目的发展、功能规划 等提建议。
162 | 2. 创作文章、视频,开办讲座来宣传 VisActor。
163 | 3. 撰写推广计划,同团队一同执行。
164 |
165 | VisActor 也在努力帮助参与社区建设的同学一同成长,我们计划(但不限于,期待大家更多的建议)提供如下帮助:
166 |
167 | 1. 以 VisActor 为基础的数据可视化研发培训,帮助参与的同学在编程技能、可视化理论、架构设计等多个方面快速成长。
168 | 2. 定期评选“代码贡献奖”和“社区推广奖”。
169 | 3. 组织社区成员参与开源活动。
170 |
171 | ## 常见问题
172 |
173 | 待补充...
174 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 VisActor
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include pyvchart/datasets/*.json
2 | include pyvchart/render/templates/*
3 | include README.md
4 | include README.en.md
5 |
--------------------------------------------------------------------------------
/README.en.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | pyvchart
5 |
6 | Python ❤️ VChart = pyvchart
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | [中文 README](README.md) | [English README](README.en.md) | [日本語(にほんご)README](README.jp.md)
26 |
27 | ## 📣 Introduction
28 |
29 | [VisActor/VChart](https://github.com/VisActor/VChart) is a core chart component library of the open-source visualization solution VisActor by ByteDance. It is based on the visualization grammar library VGrammar and the rendering engine VRender, providing not only data presentation but also support for animation orchestration for narrative scenarios, rich interaction capabilities, and customizable chart styles. The simple and easy-to-use configuration greatly reduces the learning cost for users. Python, with its expressive syntax, is well-suited for data processing and AI scenarios. When data analysis and modeling meet data visualization, [pyecharts](https://github.com/pyecharts/pyecharts) and [py-vchart](https://github.com/VisActor/py-vchart) were born.
30 |
31 | ## ✨ Features
32 |
33 | * API design similar to [pyecharts](https://github.com/pyecharts/pyecharts), smooth and fluent usage, supports method chaining
34 | * Includes all charts from VChart, everything you need
35 | * Supports mainstream Notebook environments, Jupyter Notebook, JupyterLab (**Coming soon...**)
36 | * Can be easily integrated into mainstream Web frameworks such as Flask, Sanic, Django (**Coming soon...**)
37 | * Highly flexible configuration options, allowing for the creation of beautiful charts with ease
38 | * Detailed documentation and examples to help developers get started quickly
39 |
40 | ## 🔰 Installation
41 |
42 | **pip installation**
43 | ```shell
44 | # Install
45 | $ pip install py-vchart -U
46 | ```
47 |
48 |
49 | **Source code installation**
50 | ```shell
51 | # Install
52 | $ git clone https://github.com/VisActor/py-vchart.git
53 | $ cd py-vchart
54 | $ pip install -r requirements.txt
55 | $ python setup.py install
56 | ```
57 |
58 |
59 | ## 📝 Usage
60 |
61 | Usage examples are here:[Examples](https://github.com/pyvchart/chart-examples)
62 |
63 | ## ⛏ Code Quality
64 |
65 | ### Unit Testing
66 |
67 | ```shell
68 | $ pip install -r test/requirements.txt
69 | $ make
70 | ```
71 |
72 |
73 | ### Integration Testing
74 |
75 | Using GitHub Actions for continuous integration.
76 |
77 | ### Code Style
78 |
79 | Using [flake8](http://flake8.pycqa.org/en/latest/index.html), [Codecov](https://codecov.io/), and [pylint](https://www.pylint.org/) to improve code quality.
80 |
81 | ## 😉 Author
82 |
83 | pyvchart is mainly developed and maintained by the following developers
84 |
85 | * [@sunhailin-Leo](https://github.com/sunhailin-Leo)
86 | * [@FunctionRun](https://github.com/FunctionRun)
87 |
88 | More contributor information can be found at [pyvchart/graphs/contributors](https://github.com/pyvchart/pyvchart/graphs/contributors)
89 |
90 | ## 💡 Contribution [](https://github.com/VisActor/py-vchart/blob/main/CONTRIBUTING.md#your-first-pull-request)
91 |
92 | If you wish to contribute, please read the [Code of Conduct](./CODE_OF_CONDUCT.md) and [Contribution Guidelines](./CONTRIBUTING.md).
93 |
94 | A small stream can become a vast ocean!
95 |
96 | We look forward to more developers joining the development of pyvchart. We will ensure that PRs are reviewed promptly and replies are timely. However, when submitting a PR, please ensure:
97 |
98 | 1. All unit tests pass. If it's a new feature, please add corresponding unit tests.
99 | 2. Follow the development guidelines and format the code using black and isort (`$ pip install -r requirements-dev.txt`).
100 | 3. Update relevant documentation if necessary.
101 |
102 | We also warmly welcome developers to provide more examples and help improve the documentation. ~~The documentation project is located at [pyvchart/website](https://github.com/pyvchart/website)~~ (Documentation is in preparation...)
103 |
104 |
105 |
106 | ## 📃 License
107 |
108 | MIT [©VisActor](https://github.com/VisActor)
--------------------------------------------------------------------------------
/README.jp.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | pyvchart
5 |
6 | Python ❤️ VChart = pyvchart
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | [日本語 README](README.md) | [英語 README](README.en.md) | [日本語(にほんご)README](README.jp.md)
26 |
27 | ## 📣 はじめに
28 |
29 | [VisActor/VChart](https://github.com/VisActor/VChart) は、字節ジャンプトがオープンソース化したビジュアル化ソリューション VisActor のコアチャートコンポーネントライブラリです。これはビジュアル化文法ライブラリ VGrammar とレンダリングエンジン VRender を基に構築されており、データの表示だけでなく、ナレーシナリオ向けのアニメーション編成、豊富なインタラクティブ機能、カスタマイズ可能なチャートスタイルもサポートしています。シンプルで使いやすい設定により、ユーザーの学習コストが大幅に削減されます。一方、Python は表現力豊かな言語であり、データ処理やAIなどの分野で非常に適しています。データ分析やモデリングがデータビジュアライゼーションと組み合わさったときに、[pyecharts](https://github.com/pyecharts/pyecharts) と [py-vchart](https://github.com/VisActor/py-vchart) が生まれました。
30 |
31 | ## ✨ 特徴
32 |
33 | * [pyecharts](https://github.com/pyecharts/pyecharts) に似た API 設計、スムーズでフローリングした使用感、メソッドチェーンのサポート
34 | * VChart のすべてのチャートを含む、必要なものが揃っている
35 | * Jupyter Notebook、JupyterLab (**近日公開予定**) などの主流の Notebook 環境をサポート
36 | * Flask、Sanic、Django (**近日公開予定**) などの主流の Web フレームワークへの容易な統合
37 | * 高度に柔軟な設定オプション、美しいチャートを簡単に作成できる
38 | * 開発者を手助けするための詳細なドキュメントと例
39 |
40 | ## 🔰 インストール
41 |
42 | **pip インストール**
43 | ```shell
44 | # インストール
45 | $ pip install py-vchart -U
46 | ```
47 |
48 |
49 | **ソースコードインストール**
50 | ```shell
51 | # バージョン
52 | $ git clone https://github.com/VisActor/py-vchart.git
53 | $ cd py-vchart
54 | $ pip install -r requirements.txt
55 | $ python setup.py install
56 | ```
57 |
58 |
59 | ## 📝 使用方法
60 |
61 | 使用例はここにあります:[Examples](https://github.com/pyvchart/chart-examples)
62 |
63 | ## ⛏ コード品質
64 |
65 | ### ユニットテスト
66 |
67 | ```shell
68 | $ pip install -r test/requirements.txt
69 | $ make
70 | ```
71 |
72 |
73 | ### 統合テスト
74 |
75 | GitHub Actions を使用して継続的インテグレーション環境を構築しています。
76 |
77 | ### コードスタイル
78 |
79 | [flake8](http://flake8.pycqa.org/en/latest/index.html), [Codecov](https://codecov.io/), [pylint](https://www.pylint.org/) を使用してコード品質を向上させています。
80 |
81 | ## 😉 作者
82 |
83 | pyvchart は以下の開発者が開発およびメンテナンスを行っています
84 |
85 | * [@sunhailin-Leo](https://github.com/sunhailin-Leo)
86 | * [@FunctionRun](https://github.com/FunctionRun)
87 |
88 | 他の貢献者の情報は [pyvchart/graphs/contributors](https://github.com/pyvchart/pyvchart/graphs/contributors) で確認できます
89 |
90 | ## 💡 コントリビューション [](https://github.com/VisActor/py-vchart/blob/main/CONTRIBUTING.md#your-first-pull-request)
91 |
92 | コントリビュートを希望する場合は、[行動規範](./CODE_OF_CONDUCT.md) と [コントリビューションガイドライン](./CONTRIBUTING.ja-JP.md) をお読みください。
93 |
94 | 細流は大海となる!
95 |
96 | より多くの開発者が pyvchart の開発に参加することを楽しみにしています。PR は迅速にレビューされ、タイムリーな返信が行われます。ただし、PR を提出する際には以下の点にご注意ください:
97 |
98 | 1. 全てのユニットテストがパスすること。新機能の場合、対応するユニットテストを追加してください。
99 | 2. 開発ガイドラインに従い、black および isort を使用してコードをフォーマットしてください(`$ pip install -r requirements-dev.txt`)。
100 | 3. 必要であれば、関連ドキュメントを更新してください。
101 |
102 | また、開発者の方々には、より多くのサンプルを提供し、ドキュメントの改善に協力していただくことを歓迎します。~~ドキュメントプロジェクトは [pyvchart/website](https://github.com/pyvchart/website) にあります~~ (ドキュメントは準備中...)
103 |
104 |
105 |
106 | ## 📃 ライセンス
107 |
108 | MIT [©VisActor](https://github.com/VisActor)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | pyvchart
5 |
6 | Python ❤️ VChart = pyvchart
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | [中文 README](README.md) | [English README](README.en.md) | [日本語(にほんご)README](README.jp.md)
26 |
27 | ## 📣 简介
28 |
29 | [VisActor/VChart](https://github.com/VisActor/VChart) 是一个由字节跳动开源可视化解决方案 VisActor 的核心图表组件库。它基于它基于可视化语法库 VGrammar 和渲染引擎 VRender 进行封装,在满足数据呈现的同时,还支持面向叙事场景的动画编排、丰富的交互能力和定制化的图表风格,简单易用的配置大大降低了用户的学习成本。而 Python 是一门富有表达力的语言,非常适合用于数据处理、AI 等场景。当数据分析,建模遇上数据可视化时,[pyecharts](https://github.com/pyecharts/pyecharts) 和 [py-vchart](https://github.com/VisActor/py-vchart) 诞生了。
30 |
31 | ## ✨ 特性
32 |
33 | * [pyecharts](https://github.com/pyecharts/pyecharts) like 的 API 设计,使用如丝滑般流畅,支持链式调用
34 | * 囊括了 VChart 的所有图表,应有尽有
35 | * 支持主流 Notebook 环境,Jupyter Notebook、JupyterLab (**Coming soon...**)
36 | * 可轻松集成至 Flask,Sanic,Django 等主流 Web 框架 (**Coming soon...**)
37 | * 高度灵活的配置项,可轻松搭配出精美的图表
38 | * 详细的文档和示例,帮助开发者更快的上手项目
39 |
40 | ## 🔰 安装
41 |
42 | **pip 安装**
43 | ```shell
44 | # 安装
45 | $ pip install py-vchart -U
46 |
47 | ```
48 |
49 | **源码安装**
50 | ```shell
51 | # 源码安装
52 | $ git clone https://github.com/VisActor/py-vchart.git
53 | $ cd py-vchart
54 | $ pip install -r requirements.txt
55 | $ python setup.py install
56 | ```
57 |
58 | ## 📝 使用
59 |
60 | 使用案例在此处:[Examples](https://github.com/pyvchart/chart-examples)
61 |
62 | ## ⛏ 代码质量
63 |
64 | ### 单元测试
65 |
66 | ```shell
67 | $ pip install -r test/requirements.txt
68 | $ make
69 | ```
70 |
71 | ### 集成测试
72 |
73 | 使用 Github Actions 持续集成环境。
74 |
75 | ### 代码规范
76 |
77 | 使用 [flake8](http://flake8.pycqa.org/en/latest/index.html), [Codecov](https://codecov.io/) 以及 [pylint](https://www.pylint.org/) 提升代码质量。
78 |
79 | ## 😉 Author
80 |
81 | pyvchart 主要由以下几位开发者开发维护
82 |
83 | * [@sunhailin-Leo](https://github.com/sunhailin-Leo)
84 | * [@FunctionRun](https://github.com/FunctionRun)
85 |
86 | 更多贡献者信息可以访问 [pyvchart/graphs/contributors](https://github.com/pyvchart/pyvchart/graphs/contributors)
87 |
88 | ## 💡 贡献 [](https://github.com/VisActor/py-vchart/blob/main/CONTRIBUTING.md#your-first-pull-request)
89 |
90 | 如想参与贡献,请先阅读[行为准则](./CODE_OF_CONDUCT.md) 和[贡献指南](./CONTRIBUTING.zh-CN.md)。
91 |
92 | 细流成河,终成大海!
93 |
94 | 期待能有更多的开发者参与到 pyvchart 的开发中来,我们会保证尽快 Reivew PR 并且及时回复。但提交 PR 请确保
95 |
96 | 1. 通过所有单元测试,如若是新功能,请为其新增单元测试
97 | 2. 遵守开发规范,使用 black 以及 isort 格式化代码($ pip install -r requirements-dev.txt)
98 | 3. 如若需要,请更新相对应的文档
99 |
100 | 我们也非常欢迎开发者能为 pyvchart 提供更多的示例,共同来完善文档,~~文档项目位于 [pyvchart/website](https://github.com/pyvchart/website)~~ (文档在准备中...)
101 |
102 |
103 |
104 | ## 📃 License
105 |
106 | MIT [©VisActor](https://github.com/VisActor)
--------------------------------------------------------------------------------
/pyvchart/__init__.py:
--------------------------------------------------------------------------------
1 | from pyvchart._version import __author__, __version__
2 |
3 | # for compatible older version
4 | from pyvchart.render.engine import render_chart
5 |
--------------------------------------------------------------------------------
/pyvchart/_version.py:
--------------------------------------------------------------------------------
1 | __version__ = "2.0.0"
2 | __author__ = "sunhailin-Leo"
3 |
--------------------------------------------------------------------------------
/pyvchart/charts/__init__.py:
--------------------------------------------------------------------------------
1 | # basic charts
2 | from ..charts.basic_charts.area import Area
3 | from ..charts.basic_charts.bar import Bar
4 | from ..charts.basic_charts.boxplot import Boxplot
5 | from ..charts.basic_charts.circle_packing import CirclePacking
6 | from ..charts.basic_charts.circular_progress import CircularProgress
7 | from ..charts.basic_charts.common import Common
8 | from ..charts.basic_charts.correlation import Correlation
9 | from ..charts.basic_charts.dot import Dot
10 | from ..charts.basic_charts.funnel import Funnel
11 | from ..charts.basic_charts.gauge import Gauge
12 | from ..charts.basic_charts.heatmap import HeatMap
13 | from ..charts.basic_charts.histogram import Histogram
14 | from ..charts.basic_charts.line import Line
15 | from ..charts.basic_charts.linear_progress import LinearProgress
16 | from ..charts.basic_charts.link import Link
17 | from ..charts.basic_charts.liquid import Liquid
18 | from ..charts.basic_charts.map import Map
19 | from ..charts.basic_charts.mosaic import Mosaic
20 | from ..charts.basic_charts.pie import Pie
21 | from ..charts.basic_charts.pictogram import Pictogram
22 | from ..charts.basic_charts.radar import Radar
23 | from ..charts.basic_charts.range_area import RangeArea
24 | from ..charts.basic_charts.range_column import RangeColumn
25 | from ..charts.basic_charts.rose import Rose
26 | from ..charts.basic_charts.sankey import Sankey
27 | from ..charts.basic_charts.scatter import Scatter
28 | from ..charts.basic_charts.sequence import Sequence
29 | from ..charts.basic_charts.sunburst import Sunburst
30 | from ..charts.basic_charts.treemap import Treemap
31 | from ..charts.basic_charts.venn import Venn
32 | from ..charts.basic_charts.waterfall import Waterfall
33 | from ..charts.basic_charts.wordcloud import WordCloud
34 |
35 | # 3d charts
36 | from ..charts.three_axis_charts.bar3D import Bar3D
37 | from ..charts.three_axis_charts.funnel3D import Funnel3D
38 | from ..charts.three_axis_charts.wordCloud3D import WordCloud3D
39 |
--------------------------------------------------------------------------------
/pyvchart/charts/base.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import uuid
3 |
4 | import simplejson as json
5 | from jinja2 import Environment
6 |
7 | from ..commons import utils
8 | from ..globals import CurrentConfig
9 | from ..options import InitOpts, RenderOpts
10 | from ..options.series_options import BasicOpts
11 | from ..render import engine
12 | from ..types import Optional, Sequence, Union
13 | from .mixins import ChartMixin
14 |
15 |
16 | class Base(ChartMixin):
17 | """
18 | `Base` is the root class for all graphical class, it provides
19 | part of the initialization parameters and common methods
20 | """
21 |
22 | def __init__(
23 | self,
24 | init_opts: Union[InitOpts, dict] = InitOpts(),
25 | render_opts: Union[RenderOpts, dict] = RenderOpts(),
26 | ):
27 | _opts = init_opts
28 | if isinstance(init_opts, InitOpts):
29 | _opts = init_opts.opts
30 |
31 | _render_opts = render_opts
32 | if isinstance(render_opts, RenderOpts):
33 | _render_opts = render_opts.opts
34 |
35 | self.width = _opts.get("width", "900px")
36 | self.height = _opts.get("height", "500px")
37 | self.horizontal_center = (
38 | "text-align:center; margin: auto"
39 | if _opts.get("is_horizontal_center", False)
40 | else ""
41 | )
42 | self.page_title = _opts.get("page_title", CurrentConfig.PAGE_TITLE)
43 | self.fill_bg = _opts.get("fill_bg", False)
44 | self.bg_color = _opts.get("bg_color")
45 |
46 | self.options: dict = {}
47 | self.init_options: dict = _opts
48 | self.render_options: dict = _render_opts
49 | self.chart_id = _render_opts.get("dom") or uuid.uuid4().hex
50 | self.render_options.update(dom=self.chart_id)
51 | self._embed_js = _opts.get("is_embed_js")
52 |
53 | self.js_host: Optional[str] = _opts.get("js_host") or CurrentConfig.ONLINE_HOST
54 | self.js_functions: utils.OrderedSet = utils.OrderedSet()
55 | self.js_dependencies: utils.OrderedSet = utils.OrderedSet("vchart")
56 | self.js_events: utils.OrderedSet = utils.OrderedSet()
57 | self.options.update(backgroundColor=self.bg_color)
58 |
59 | self._is_geo_chart: bool = False
60 |
61 | self._render_cache: dict = dict()
62 |
63 | def get_chart_id(self) -> str:
64 | return self.chart_id
65 |
66 | def get_render_options(self) -> dict:
67 | return utils.remove_key_with_none_value(self.render_options)
68 |
69 | def get_options(self) -> dict:
70 | return utils.remove_key_with_none_value(self.options)
71 |
72 | def dump_render_options(self):
73 | return utils.replace_placeholder(
74 | json.dumps(self.get_render_options(), default=default, ignore_nan=True)
75 | )
76 |
77 | def dump_options(self) -> str:
78 | return utils.replace_placeholder(
79 | json.dumps(self.get_options(), indent=4, default=default, ignore_nan=True)
80 | )
81 |
82 | def dump_options_with_quotes(self) -> str:
83 | return utils.replace_placeholder_with_quotes(
84 | json.dumps(self.get_options(), indent=4, default=default, ignore_nan=True)
85 | )
86 |
87 | def render(
88 | self,
89 | path: str = "render.html",
90 | template_name: str = "simple_chart.html",
91 | env: Optional[Environment] = None,
92 | **kwargs,
93 | ) -> str:
94 | self._prepare_render()
95 | return engine.render(self, path, template_name, env, **kwargs)
96 |
97 | def render_embed(
98 | self,
99 | template_name: str = "simple_chart.html",
100 | env: Optional[Environment] = None,
101 | **kwargs,
102 | ) -> str:
103 | self._prepare_render()
104 | return engine.render_embed(self, template_name, env, **kwargs)
105 |
106 | def render_notebook(self):
107 | self.chart_id = uuid.uuid4().hex
108 | self._prepare_render()
109 | return engine.render_notebook(
110 | self, "nb_jupyter_notebook.html", "nb_jupyter_lab.html"
111 | )
112 |
113 | def _prepare_render(self):
114 | self.render_contents = self.dump_render_options()
115 | self.json_contents = self.dump_options()
116 | self._render_cache.clear()
117 | if self._embed_js:
118 | self._render_cache["javascript"] = (
119 | self.load_javascript().load_javascript_contents()
120 | )
121 |
122 |
123 | def default(o):
124 | if isinstance(o, (datetime.date, datetime.datetime)):
125 | return o.isoformat()
126 | if isinstance(o, utils.JsCode):
127 | return (
128 | o.replace("\\n|\\t", "").replace(r"\\n", "\n").replace(r"\\t", "\t").js_code
129 | )
130 | if isinstance(o, BasicOpts):
131 | # if isinstance(o.opts, Sequence):
132 | # return [utils.remove_key_with_none_value(item) for item in o.opts]
133 | # else:
134 | return utils.remove_key_with_none_value(o.opts)
135 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisActor/py-vchart/6157feaead72b2f9ab2eab0f83bfaad286c6d8a4/pyvchart/charts/basic_charts/__init__.py
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/area.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Area(RectChart):
8 |
9 | def set_3d_mode(self):
10 | self.add_js_funcs("VChart.register3DPlugin();")
11 |
12 | return self
13 |
14 | def set_area_spec(
15 | self,
16 | direction: str = "vertical",
17 | is_sort_data_by_axis: types.Optional[bool] = None,
18 | series_mark: str = "area",
19 | area_opts: types.Optional[opts.AreaOpts] = None,
20 | line_opts: opts.LineOpts = None,
21 | point_opts: opts.PointOpts = None,
22 | label_opts: opts.LabelOpts = None,
23 | area_label_opts: types.Optional[opts.LabelOpts] = None,
24 | total_label_opts: types.Optional[opts.LabelOpts] = None,
25 | sampling: types.Optional[str] = None,
26 | sampling_factor: types.Optional[types.Numeric] = 1,
27 | is_mark_overlap: types.Optional[bool] = False,
28 | point_dis: types.Optional[types.Numeric] = None,
29 | point_dis_mul: types.Optional[types.Numeric] = None,
30 | ):
31 | self.options.update(
32 | {
33 | "type": ChartType.AREA,
34 | "direction": direction,
35 | "sortDataByAxis": is_sort_data_by_axis,
36 | "seriesMark": series_mark,
37 | "area": area_opts,
38 | "line": line_opts,
39 | "point": point_opts,
40 | "label": label_opts,
41 | "areaLabel": area_label_opts,
42 | "totalLabel": total_label_opts,
43 | "sampling": sampling,
44 | "samplingFactor": sampling_factor,
45 | "markOverlap": is_mark_overlap,
46 | "pointDis": point_dis,
47 | "pointDisMul": point_dis_mul,
48 | }
49 | )
50 |
51 | return self
52 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/bar.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Bar(RectChart):
8 | def set_bar_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | data_id: types.Optional[str] = None,
13 | bar_opts: types.Optional[opts.BarOpts] = None,
14 | bar_background_opts: types.Optional[opts.BarBackgroundOpts] = None,
15 | label_opts: types.Optional[opts.LabelOpts] = None,
16 | bar_width: types.Optional[types.Union[str, types.Numeric]] = None,
17 | bar_min_width: types.Optional[types.Union[str, types.Numeric]] = None,
18 | bar_max_width: types.Optional[types.Union[str, types.Numeric]] = None,
19 | bar_gap_in_group: types.Optional[types.Union[str, types.Numeric]] = None,
20 | bar_min_height: types.Optional[types.Union[str, types.Numeric]] = None,
21 | stack_corner_radius: types.Optional[
22 | types.Union[types.Numeric, types.Sequence[types.Numeric], types.JSFunc]
23 | ] = None,
24 | total_label_opts: types.Optional[opts.LabelOpts] = None,
25 | sampling: types.Optional[str] = None,
26 | sampling_factor: types.Optional[types.Numeric] = None,
27 | is_auto_band_size: types.Optional[bool] = None,
28 | ):
29 | self.options.update(
30 | {
31 | "type": ChartType.BAR,
32 | "direction": direction,
33 | "sortDataByAxis": is_sort_data_by_axis,
34 | "dataId": data_id,
35 | "bar": bar_opts,
36 | "barBackground": bar_background_opts,
37 | "label": label_opts,
38 | "barWidth": bar_width,
39 | "barMinWidth": bar_min_width,
40 | "barMaxWidth": bar_max_width,
41 | "barGapInGroup": bar_gap_in_group,
42 | "barMinHeight": bar_min_height,
43 | "stackCornerRadius": stack_corner_radius,
44 | "totalLabel": total_label_opts,
45 | "sampling": sampling,
46 | "samplingFactor": sampling_factor,
47 | "autoBandSize": is_auto_band_size,
48 | }
49 | )
50 |
51 | return self
52 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/boxplot.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Boxplot(RectChart):
8 | def set_boxplot_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | min_field: types.Optional[str] = None,
13 | q1_field: types.Optional[str] = None,
14 | median_field: types.Optional[str] = None,
15 | q3_field: types.Optional[str] = None,
16 | max_field: types.Optional[str] = None,
17 | outliers_field: types.Optional[str] = None,
18 | boxplot_opts: types.Optional[opts.BoxplotOpts] = None,
19 | outliers_style_fill: types.Optional[str] = None,
20 | outliers_style_size: types.Optional[int] = None,
21 | ):
22 | self.options.update(
23 | {
24 | "type": ChartType.BOXPLOT,
25 | "direction": direction,
26 | "sortDataByAxis": is_sort_data_by_axis,
27 | "minField": min_field,
28 | "q1Field": q1_field,
29 | "medianField": median_field,
30 | "q3Field": q3_field,
31 | "maxField": max_field,
32 | "outliersField": outliers_field,
33 | "boxPlot": boxplot_opts,
34 | "outliersStyle": {
35 | "fill": outliers_style_fill,
36 | "size": outliers_style_size,
37 | },
38 | }
39 | )
40 |
41 | return self
42 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/circle_packing.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class CirclePacking(Chart):
8 | def set_circle_packing_spec(
9 | self,
10 | category_field: types.Optional[str] = None,
11 | value_field: types.Optional[str] = None,
12 | layout_padding: types.Optional[
13 | types.Union[types.Numeric, types.Sequence[types.Numeric]]
14 | ] = None,
15 | is_drill: types.Optional[bool] = None,
16 | drill_field: types.Optional[str] = None,
17 | label_opts: types.Optional[opts.LabelOpts] = None,
18 | circle_packing_opts: types.Optional[opts.CirclePackingOpts] = None,
19 | ):
20 | self.options.update(
21 | {
22 | "type": ChartType.CIRCLE_PACKING,
23 | "categoryField": category_field,
24 | "valueField": value_field,
25 | "layoutPadding": layout_padding,
26 | "drill": is_drill,
27 | "drillField": drill_field,
28 | "label": label_opts,
29 | "circlePacking": circle_packing_opts,
30 | }
31 | )
32 |
33 | return self
34 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/circular_progress.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class CircularProgress(Chart):
8 | def set_circular_progress_spec(
9 | self,
10 | radius: types.Optional[types.Numeric] = None,
11 | outer_radius: types.Optional[types.Numeric] = None,
12 | inner_radius: types.Optional[types.Numeric] = None,
13 | corner_radius: types.Optional[types.Numeric] = None,
14 | start_angle: types.Optional[types.Numeric] = None,
15 | end_angle: types.Optional[types.Numeric] = None,
16 | center_x: types.Optional[types.Union[types.Numeric, str]] = None,
17 | center_y: types.Optional[types.Union[types.Numeric, str]] = None,
18 | category_field: types.Optional[types.Union[str, types.Sequence[str]]] = None,
19 | value_field: types.Optional[str] = None,
20 | radius_field: types.Optional[str] = None,
21 | max_value: types.Optional[types.Numeric] = None,
22 | is_round_cap: types.Optional[bool] = None,
23 | progress_opts: types.Optional[opts.ProgressOpts] = None,
24 | track_opts: types.Optional[opts.TrackOpts] = None,
25 | layout_radius: types.Optional[
26 | types.Union[str, types.Numeric, types.JSFunc]
27 | ] = None,
28 | ):
29 | self.options.update(
30 | {
31 | "type": ChartType.CIRCULAR_PROGRESS,
32 | "radius": radius,
33 | "outerRadius": outer_radius,
34 | "innerRadius": inner_radius,
35 | "cornerRadius": corner_radius,
36 | "startAngle": start_angle,
37 | "endAngle": end_angle,
38 | "centerX": center_x,
39 | "centerY": center_y,
40 | "categoryField": category_field,
41 | "valueField": value_field,
42 | "radiusField": radius_field,
43 | "maxValue": max_value,
44 | "roundCap": is_round_cap,
45 | "progress": progress_opts,
46 | "track": track_opts,
47 | "layoutRadius": layout_radius,
48 | }
49 | )
50 |
51 | return self
52 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/common.py:
--------------------------------------------------------------------------------
1 | from ... import types
2 | from ...charts.chart import Chart
3 | from ...globals import ChartType
4 |
5 |
6 | class Common(Chart):
7 | def set_common_spec(
8 | self,
9 | series: types.Optional[types.Sequence[dict]] = None,
10 | is_auto_band_size: types.Optional[bool] = None,
11 | label_layout: types.Optional[str] = "series",
12 | ):
13 | self.options.update(
14 | {
15 | "type": ChartType.COMMON,
16 | "series": series,
17 | "autoBandSize": is_auto_band_size,
18 | "labelLayout": label_layout,
19 | }
20 | )
21 |
22 | return self
23 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/correlation.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Correlation(Chart):
8 | def set_correlation_spec(
9 | self,
10 | category_field: types.Optional[str] = None,
11 | value_field: types.Optional[str] = None,
12 | size_field: types.Optional[str] = None,
13 | size_range: types.Optional[types.Sequence] = None,
14 | center_x: types.Optional[types.Union[types.Numeric, str]] = None,
15 | center_y: types.Optional[types.Union[types.Numeric, str]] = None,
16 | inner_radius: types.Optional[types.Numeric] = None,
17 | outer_radius: types.Optional[types.Numeric] = None,
18 | start_angle: types.Optional[types.Numeric] = -90,
19 | end_angle: types.Optional[types.Numeric] = 270,
20 | center_point_opts: types.Optional[opts.CorrelationCenterPointOpts] = None,
21 | ripple_point_opts: types.Optional[opts.CorrelationRipplePointOpts] = None,
22 | center_label_opts: types.Optional[opts.LabelOpts] = None,
23 | node_point_opts: types.Optional[opts.CorrelationNodePointOpts] = None,
24 | label_opts: types.Optional[opts.LabelOpts] = None,
25 | layout_radius: types.Optional[
26 | types.Union[str, types.Numeric, types.JSFunc]
27 | ] = None,
28 | ):
29 | self.options.update(
30 | {
31 | "type": ChartType.CORRELATION,
32 | "categoryField": category_field,
33 | "valueField": value_field,
34 | "sizeField": size_field,
35 | "sizeRange": size_range,
36 | "centerX": center_x,
37 | "centerY": center_y,
38 | "innerRadius": inner_radius,
39 | "outerRadius": outer_radius,
40 | "startAngle": start_angle,
41 | "endAngle": end_angle,
42 | "centerPoint": center_point_opts,
43 | "ripplePoint": ripple_point_opts,
44 | "centerLabel": center_label_opts,
45 | "nodePoint": node_point_opts,
46 | "label": label_opts,
47 | "layoutRadius": layout_radius,
48 | }
49 | )
50 |
51 | return self
52 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/dot.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Dot(RectChart):
8 | def set_dot_spec(
9 | self,
10 | name: types.Optional[str] = None,
11 | is_support3d: types.Optional[bool] = None,
12 | id_: types.Union[str, int] = None,
13 | data_index: types.Optional[types.Numeric] = None,
14 | data_id: types.Optional[str] = None,
15 | region_index: types.Union[int, types.Sequence[int]] = None,
16 | region_id: types.Union[
17 | int, str, types.Sequence[int], types.Sequence[str]
18 | ] = None,
19 | is_morph_enable: types.Optional[bool] = None,
20 | morph_key: types.Optional[str] = None,
21 | morph_element_key: types.Optional[str] = None,
22 | direction: str = "vertical",
23 | is_sort_data_by_axis: types.Optional[bool] = None,
24 | series_group_field: types.Optional[str] = None,
25 | dot_type_field: types.Optional[str] = None,
26 | title_field: types.Optional[str] = None,
27 | sub_title_field: types.Optional[str] = None,
28 | high_light_series_group: types.Optional[str] = None,
29 | dot_opts: types.Optional[opts.DotOpts] = None,
30 | title_opts: types.Optional[opts.DotTitleOpts] = None,
31 | sub_title_opts: types.Optional[opts.DotSubTitleOpts] = None,
32 | symbol_opts: types.Optional[opts.DotSymbolOpts] = None,
33 | grid_opts: types.Optional[opts.DotGridOpts] = None,
34 | left_append_padding: types.Optional[types.Numeric] = None,
35 | clip_height: types.Optional[types.Numeric] = None,
36 | ):
37 | self.options.update(
38 | {
39 | "type": ChartType.DOT,
40 | "name": name,
41 | "support3d": is_support3d,
42 | "id_": id_,
43 | "dataIndex": data_index,
44 | "dataId": data_id,
45 | "regionIndex": region_index,
46 | "regionId": region_id,
47 | "morph": {
48 | "enable": is_morph_enable,
49 | "key": morph_key,
50 | "elementKey": morph_element_key,
51 | },
52 | "direction": direction,
53 | "sortDataByAxis": is_sort_data_by_axis,
54 | "seriesGroupField": series_group_field,
55 | "dotTypeField": dot_type_field,
56 | "titleField": title_field,
57 | "subTitleField": sub_title_field,
58 | "highLightSeriesGroup": high_light_series_group,
59 | "dot": dot_opts,
60 | "title": title_opts,
61 | "subTitle": sub_title_opts,
62 | "symbol": symbol_opts,
63 | "grid": grid_opts,
64 | "leftAppendPadding": left_append_padding,
65 | "clipHeight": clip_height,
66 | }
67 | )
68 |
69 | return self
70 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/funnel.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Funnel(Chart):
8 | def set_funnel_spec(
9 | self,
10 | category_field: types.Optional[str] = None,
11 | value_field: types.Optional[str] = None,
12 | funnel_orient: types.Optional[str] = "top",
13 | funnel_align: types.Optional[str] = "center",
14 | height_ratio: types.Optional[types.Numeric] = 0.5,
15 | shape: types.Optional[str] = "trapezoid",
16 | is_transform: types.Optional[bool] = None,
17 | is_cone: types.Optional[bool] = None,
18 | gap: types.Optional[types.Numeric] = None,
19 | max_size: types.Optional[types.Union[types.Numeric, str]] = "80%",
20 | min_size: types.Optional[types.Union[types.Numeric, str]] = None,
21 | funnel_opts: types.Optional[opts.FunnelOpts] = None,
22 | funnel_transform_opts: types.Optional[opts.FunnelTransformOpts] = None,
23 | label_opts: types.Optional[opts.LabelOpts] = None,
24 | transform_label_opts: types.Optional[opts.LabelOpts] = None,
25 | outer_label_opts: types.Optional[opts.LabelOpts] = None,
26 | ):
27 | self.options.update(
28 | {
29 | "type": ChartType.FUNNEL,
30 | "categoryField": category_field,
31 | "valueField": value_field,
32 | "funnelOrient": funnel_orient,
33 | "funnelAlign": funnel_align,
34 | "heightRatio": height_ratio,
35 | "shape": shape,
36 | "isTransform": is_transform,
37 | "isCone": is_cone,
38 | "gap": gap,
39 | "maxSize": max_size,
40 | "minSize": min_size,
41 | "funnel": funnel_opts,
42 | "transform": funnel_transform_opts,
43 | "label": label_opts,
44 | "transformLabel": transform_label_opts,
45 | "outerLabel": outer_label_opts,
46 | }
47 | )
48 |
49 | return self
50 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/gauge.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Gauge(Chart):
8 | def set_gauge_spec(
9 | self,
10 | category_field: types.Optional[types.Union[str, types.Sequence[str]]] = None,
11 | value_field: types.Optional[types.Union[str, types.Sequence[str]]] = None,
12 | outer_radius: types.Optional[types.Numeric] = None,
13 | inner_radius: types.Optional[types.Numeric] = None,
14 | start_angle: types.Optional[types.Numeric] = -90,
15 | end_angle: types.Optional[types.Numeric] = 270,
16 | center_x: types.Optional[types.Union[types.Numeric, str]] = None,
17 | center_y: types.Optional[types.Union[types.Numeric, str]] = None,
18 | corner_radius: types.Optional[types.Numeric] = None,
19 | round_cap: types.Optional[types.Union[bool, types.Sequence[bool]]] = None,
20 | radius_field: types.Optional[str] = None,
21 | pointer_opts: types.Optional[opts.GaugePointerOpts] = None,
22 | pin_opts: types.Optional[opts.GaugePinOpts] = None,
23 | pin_background_opts: types.Optional[opts.GaugePinBackgroundOpts] = None,
24 | gauge_opts: types.Optional[opts.GaugeOpts] = None,
25 | layout_radius: types.Optional[
26 | types.Union[str, types.Numeric, types.JSFunc]
27 | ] = None,
28 | ):
29 | self.options.update(
30 | {
31 | "type": ChartType.GAUGE,
32 | "categoryField": category_field,
33 | "valueField": value_field,
34 | "outerRadius": outer_radius,
35 | "innerRadius": inner_radius,
36 | "startAngle": start_angle,
37 | "endAngle": end_angle,
38 | "centerX": center_x,
39 | "centerY": center_y,
40 | "cornerRadius": corner_radius,
41 | "roundCap": round_cap,
42 | "radiusField": radius_field,
43 | "pointer": pointer_opts,
44 | "pin": pin_opts,
45 | "pinBackground": pin_background_opts,
46 | "gauge": gauge_opts,
47 | "layoutRadius": layout_radius,
48 | }
49 | )
50 |
51 | return self
52 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/heatmap.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class HeatMap(RectChart):
8 | def set_heatmap_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | region_id: types.Optional[str] = None,
13 | value_field: types.Optional[str] = None,
14 | cell_opts: types.Optional[opts.HeatMapCellOpts] = None,
15 | cell_background_opts: types.Optional[opts.HeatMapCellBackgroundOpts] = None,
16 | label_opts: types.Optional[opts.LabelOpts] = None,
17 | ):
18 | self.options.update(
19 | {
20 | "type": ChartType.HEATMAP,
21 | "direction": direction,
22 | "sortDataByAxis": is_sort_data_by_axis,
23 | "regionId": region_id,
24 | "valueField": value_field,
25 | "cell": cell_opts,
26 | "cellBackground": cell_background_opts,
27 | "label": label_opts,
28 | }
29 | )
30 |
31 | return self
32 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/histogram.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Histogram(RectChart):
8 | def set_histogram_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | bar_opts: types.Optional[opts.BarOpts] = None,
13 | bar_background_opts: types.Optional[opts.BarBackgroundOpts] = None,
14 | label_opts: types.Optional[opts.LabelOpts] = None,
15 | bar_width: types.Optional[types.Union[str, types.Numeric]] = None,
16 | bar_min_width: types.Optional[types.Union[str, types.Numeric]] = None,
17 | bar_max_width: types.Optional[types.Union[str, types.Numeric]] = None,
18 | bar_gap_in_group: types.Optional[types.Union[str, types.Numeric]] = None,
19 | bar_min_height: types.Optional[types.Union[str, types.Numeric]] = None,
20 | stack_corner_radius: types.Optional[
21 | types.Union[types.Numeric, types.Sequence[types.Numeric], types.JSFunc]
22 | ] = None,
23 | total_label_opts: types.Optional[opts.LabelOpts] = None,
24 | sampling: types.Optional[str] = None,
25 | sampling_factor: types.Optional[types.Numeric] = 1,
26 | x2_field: types.Optional[types.Union[str, types.Sequence[str]]] = None,
27 | y2_field: types.Optional[types.Union[str, types.Sequence[str]]] = None,
28 | ):
29 | self.options.update(
30 | {
31 | "type": ChartType.HISTOGRAM,
32 | "sortDataByAxis": is_sort_data_by_axis,
33 | "bar": bar_opts,
34 | "barBackground": bar_background_opts,
35 | "label": label_opts,
36 | "barWidth": bar_width,
37 | "barMinWidth": bar_min_width,
38 | "barMaxWidth": bar_max_width,
39 | "barGapInGroup": bar_gap_in_group,
40 | "barMinHeight": bar_min_height,
41 | "stackCornerRadius": stack_corner_radius,
42 | "totalLabel": total_label_opts,
43 | "sampling": sampling,
44 | "samplingFactor": sampling_factor,
45 | "x2Field": x2_field,
46 | "y2Field": y2_field,
47 | }
48 | )
49 |
50 | return self
51 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/line.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Line(RectChart):
8 | def set_line_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | series_mark: str = "line",
13 | line_opts: types.Optional[opts.LineOpts] = None,
14 | point_opts: types.Optional[opts.PointOpts] = None,
15 | label_opts: types.Optional[opts.LabelOpts] = None,
16 | line_label_opts: types.Optional[opts.LineCurveLabelOpts] = None,
17 | sampling: types.Optional[str] = None,
18 | sampling_factor: types.Optional[types.Numeric] = 1,
19 | is_mark_overlap: types.Optional[bool] = False,
20 | point_dis: types.Optional[types.Numeric] = None,
21 | point_dis_mul: types.Optional[types.Numeric] = None,
22 | ):
23 | self.options.update(
24 | {
25 | "type": ChartType.LINE,
26 | "direction": direction,
27 | "sortDataByAxis": is_sort_data_by_axis,
28 | "seriesMark": series_mark,
29 | "line": line_opts,
30 | "point": point_opts,
31 | "label": label_opts,
32 | "lineLabel": line_label_opts,
33 | "sampling": sampling,
34 | "samplingFactor": sampling_factor,
35 | "markOverlap": is_mark_overlap,
36 | "pointDis": point_dis,
37 | "pointDisMul": point_dis_mul,
38 | }
39 | )
40 |
41 | return self
42 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/linear_progress.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class LinearProgress(RectChart):
8 | def set_linear_progress_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | progress_opts: types.Optional[opts.ProgressOpts] = None,
13 | track_opts: types.Optional[opts.TrackOpts] = None,
14 | corner_radius: types.Optional[types.Numeric] = None,
15 | band_width: types.Optional[types.Numeric] = None,
16 | ):
17 | self.options.update(
18 | {
19 | "type": ChartType.LINEAR_PROGRESS,
20 | "direction": direction,
21 | "sortDataByAxis": is_sort_data_by_axis,
22 | "progress": progress_opts,
23 | "track": track_opts,
24 | "cornerRadius": corner_radius,
25 | "bandWidth": band_width,
26 | }
27 | )
28 |
29 | return self
30 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/link.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Link(RectChart):
8 | def set_link_spec(
9 | self,
10 | name: types.Optional[str] = None,
11 | is_support3d: types.Optional[bool] = None,
12 | id_: types.Union[str, int] = None,
13 | data_index: types.Optional[types.Numeric] = None,
14 | data_id: types.Optional[str] = None,
15 | region_index: types.Union[int, types.Sequence[int]] = None,
16 | region_id: types.Union[
17 | int, str, types.Sequence[int], types.Sequence[str]
18 | ] = None,
19 | is_morph_enable: types.Optional[bool] = None,
20 | morph_key: types.Optional[str] = None,
21 | morph_element_key: types.Optional[str] = None,
22 | direction: str = "vertical",
23 | is_sort_data_by_axis: types.Optional[bool] = None,
24 | from_field: types.Optional[str] = None,
25 | to_field: types.Optional[str] = None,
26 | dot_series_index: types.Optional[types.Numeric] = None,
27 | dot_type_field: types.Optional[str] = None,
28 | link_opts: types.Optional[opts.LinkOpts] = None,
29 | arrow_opts: types.Optional[opts.ArrowOpts] = None,
30 | ):
31 | self.options.update(
32 | {
33 | "type": ChartType.LINK,
34 | "name": name,
35 | "support3d": is_support3d,
36 | "id_": id_,
37 | "dataIndex": data_index,
38 | "dataId": data_id,
39 | "regionIndex": region_index,
40 | "regionId": region_id,
41 | "morph": {
42 | "enable": is_morph_enable,
43 | "key": morph_key,
44 | "elementKey": morph_element_key,
45 | },
46 | "direction": direction,
47 | "sortDataByAxis": is_sort_data_by_axis,
48 | "fromField": from_field,
49 | "toField": to_field,
50 | "dotSeriesIndex": dot_series_index,
51 | "dotTypeField": dot_type_field,
52 | "link": link_opts,
53 | "arrow": arrow_opts,
54 | }
55 | )
56 | return self
57 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/liquid.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType, RegisterFunctionType
5 |
6 |
7 | class Liquid(RectChart):
8 |
9 | def __init__(
10 | self,
11 | init_opts: types.Init = opts.InitOpts(),
12 | render_opts: types.RenderInit = opts.RenderOpts(),
13 | ):
14 | super().__init__(init_opts=init_opts, render_opts=render_opts)
15 | # register liquid chart.
16 | self.add_js_funcs(RegisterFunctionType.Liquid)
17 |
18 | def set_liquid_spec(
19 | self,
20 | direction: str = "vertical",
21 | is_sort_data_by_axis: types.Optional[bool] = None,
22 | value_field: types.Optional[str] = None,
23 | mask_shape: types.Optional[str] = "circle",
24 | outline_margin: types.Optional[
25 | types.Union[types.JSFunc, types.Numeric, types.Sequence[types.Numeric]]
26 | ] = None,
27 | outline_padding: types.Optional[
28 | types.Union[types.JSFunc, types.Numeric, types.Sequence[types.Numeric]]
29 | ] = None,
30 | is_indicator_smart_invert: bool = False,
31 | is_reverse: bool = False,
32 | liquid_opts: types.Optional[opts.LiquidOpts] = None,
33 | liquid_background_opts: types.Optional[opts.LiquidBackgroundOpts] = None,
34 | ):
35 | self.options.update(
36 | {
37 | "type": ChartType.LIQUID,
38 | "direction": direction,
39 | "sortDataByAxis": is_sort_data_by_axis,
40 | "valueField": value_field,
41 | "maskShape": mask_shape,
42 | "outlineMargin": outline_margin,
43 | "outlinePadding": outline_padding,
44 | "indicatorSmartInvert": is_indicator_smart_invert,
45 | "reverse": is_reverse,
46 | "liquid": liquid_opts,
47 | "liquidBackground": liquid_background_opts,
48 | }
49 | )
50 |
51 | return self
52 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/map.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Map(Chart):
8 | def __init__(
9 | self,
10 | init_opts: types.Init = opts.InitOpts(),
11 | render_opts: types.RenderInit = opts.RenderOpts(),
12 | ):
13 | super().__init__(init_opts=init_opts, render_opts=render_opts)
14 | self._is_geo_chart = True
15 |
16 | def register_map(self, map_name: str, map_geojson: str):
17 | self.add_js_funcs(f"VChart.default.registerMap('{map_name}', {map_geojson})")
18 |
19 | return self
20 |
21 | def set_map_spec(
22 | self,
23 | map_: types.Optional[str] = None,
24 | name_field: types.Optional[str] = None,
25 | value_field: types.Optional[str] = None,
26 | name_property: types.Optional[str] = None,
27 | name_map: types.Optional[dict] = None,
28 | default_fill_color: types.Optional[str] = "#f3f3f3",
29 | centroid_property: types.Optional[str] = None,
30 | is_show_default_name: types.Optional[bool] = None,
31 | area_opts: types.Optional[opts.AreaOpts] = None,
32 | label_opts: types.Optional[opts.LabelOpts] = None,
33 | map_label_opts: types.Optional[opts.MapLabelOpts] = None,
34 | ):
35 | self.options.update(
36 | {
37 | "type": ChartType.MAP,
38 | "map": map_,
39 | "nameField": name_field,
40 | "valueField": value_field,
41 | "nameProperty": name_property,
42 | "nameMap": name_map,
43 | "defaultFillColor": default_fill_color,
44 | "centroidProperty": centroid_property,
45 | "showDefaultName": is_show_default_name,
46 | "area": area_opts,
47 | "label": label_opts,
48 | "mapLabel": map_label_opts,
49 | }
50 | )
51 |
52 | return self
53 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/mosaic.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType, RegisterFunctionType
5 |
6 |
7 | class Mosaic(RectChart):
8 | def __init__(
9 | self,
10 | init_opts: types.Init = opts.InitOpts(),
11 | render_opts: types.RenderInit = opts.RenderOpts(),
12 | ):
13 | super().__init__(init_opts=init_opts, render_opts=render_opts)
14 | # register mosaic chart.
15 | self.add_js_funcs(RegisterFunctionType.Mosaic)
16 |
17 | def set_mosaic_spec(
18 | self,
19 | direction: str = "vertical",
20 | is_sort_data_by_axis: types.Optional[bool] = None,
21 | bar_opts: types.Optional[opts.BarOpts] = None,
22 | bar_background_opts: types.Optional[opts.BarBackgroundOpts] = None,
23 | label_opts: types.Optional[opts.LabelOpts] = None,
24 | bar_width: types.Optional[types.Union[str, types.Numeric]] = None,
25 | bar_min_width: types.Optional[types.Union[str, types.Numeric]] = None,
26 | bar_max_width: types.Optional[types.Union[str, types.Numeric]] = None,
27 | bar_gap_in_group: types.Optional[types.Union[str, types.Numeric]] = None,
28 | bar_min_height: types.Optional[types.Union[str, types.Numeric]] = None,
29 | stack_corner_radius: types.Optional[
30 | types.Union[types.Numeric, types.Sequence[types.Numeric], types.JSFunc]
31 | ] = None,
32 | total_label_opts: types.Optional[opts.LabelOpts] = None,
33 | sampling: types.Optional[str] = None,
34 | sampling_factor: types.Optional[types.Numeric] = None,
35 | is_auto_band_size: types.Optional[bool] = None,
36 | ):
37 | self.options.update(
38 | {
39 | "type": ChartType.MOSAIC,
40 | "direction": direction,
41 | "sortDataByAxis": is_sort_data_by_axis,
42 | "bar": bar_opts,
43 | "barBackground": bar_background_opts,
44 | "label": label_opts,
45 | "barWidth": bar_width,
46 | "barMinWidth": bar_min_width,
47 | "barMaxWidth": bar_max_width,
48 | "barGapInGroup": bar_gap_in_group,
49 | "barMinHeight": bar_min_height,
50 | "stackCornerRadius": stack_corner_radius,
51 | "totalLabel": total_label_opts,
52 | "sampling": sampling,
53 | "samplingFactor": sampling_factor,
54 | "autoBandSize": is_auto_band_size,
55 | }
56 | )
57 |
58 | return self
59 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/pictogram.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType, RegisterFunctionType
5 |
6 |
7 | class Pictogram(Chart):
8 | def __init__(
9 | self,
10 | init_opts: types.Init = opts.InitOpts(),
11 | render_opts: types.RenderInit = opts.RenderOpts(),
12 | ):
13 | super().__init__(init_opts=init_opts, render_opts=render_opts)
14 | # register pictogram chart.
15 | self.add_js_funcs(RegisterFunctionType.Pictogram)
16 |
17 | def register_svg(self, name: str, svg_path: str):
18 | # hard code here
19 | self.add_js_funcs(f"VChart.default.registerSVG('{name}', `{svg_path}`)")
20 |
21 | return self
22 |
23 | def set_pictogram_spec(
24 | self,
25 | name_field: types.Optional[str] = None,
26 | value_field: types.Optional[str] = None,
27 | svg: types.Optional[str] = None,
28 | default_fill_color: types.Optional[str] = None,
29 | pictogram_opts: types.Optional[str] = None,
30 | ):
31 | self.options.update(
32 | {
33 | "type": ChartType.PICTOGRAM,
34 | "nameField": name_field,
35 | "valueField": value_field,
36 | "svg": svg,
37 | "defaultFillColor": default_fill_color,
38 | "pictogram": pictogram_opts,
39 | }
40 | )
41 |
42 | return self
43 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/pie.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Pie(Chart):
8 | def set_pie_spec(
9 | self,
10 | value_field: types.Union[str, types.Sequence[str]] = None,
11 | outer_radius: types.Optional[types.Numeric] = None,
12 | inner_radius: types.Optional[types.Numeric] = None,
13 | start_angle: types.Optional[types.Numeric] = -90,
14 | end_angle: types.Optional[types.Numeric] = 270,
15 | center_x: types.Optional[types.Union[types.Numeric, str]] = None,
16 | center_y: types.Optional[types.Union[types.Numeric, str]] = None,
17 | category_field: types.Optional[str] = None,
18 | center_offset: types.Optional[types.Numeric] = None,
19 | layout_radius: types.Optional[
20 | types.Union[str, types.Numeric, types.JSFunc]
21 | ] = None,
22 | corner_radius: types.Optional[types.Numeric] = None,
23 | pad_angle: types.Optional[types.Numeric] = None,
24 | min_angle: types.Optional[types.Numeric] = None,
25 | pie_opts: types.Optional[opts.PieOpts] = None,
26 | label_opts: types.Optional[opts.LabelOpts] = None,
27 | is_show_empty_circle: types.Optional[bool] = None,
28 | empty_circle_style_opts: types.Optional[opts.BaseStyleOpts] = None,
29 | is_show_all_zero: types.Optional[bool] = None,
30 | is_support_negative: types.Optional[bool] = None,
31 | ):
32 | self.options.update(
33 | {
34 | "type": ChartType.PIE,
35 | "valueField": value_field,
36 | "outerRadius": outer_radius,
37 | "innerRadius": inner_radius,
38 | "startAngle": start_angle,
39 | "endAngle": end_angle,
40 | "centerX": center_x,
41 | "centerY": center_y,
42 | "categoryField": category_field,
43 | "centerOffset": center_offset,
44 | "layoutRadius": layout_radius,
45 | "cornerRadius": corner_radius,
46 | "padAngle": pad_angle,
47 | "minAngle": min_angle,
48 | "pie": pie_opts,
49 | "label": label_opts,
50 | "emptyPlaceholder": {
51 | "showEmptyCircle": is_show_empty_circle,
52 | "emptyCircle": empty_circle_style_opts,
53 | },
54 | "showAllZero": is_show_all_zero,
55 | "supportNegative": is_support_negative,
56 | }
57 | )
58 |
59 | return self
60 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/radar.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Radar(Chart):
8 | def set_radar_spec(
9 | self,
10 | category_field: types.Optional[types.Union[str, types.Sequence[str]]] = None,
11 | value_field: types.Optional[types.Union[str, types.Sequence[str]]] = None,
12 | outer_radius: types.Optional[types.Numeric] = 0.6,
13 | inner_radius: types.Optional[types.Numeric] = 0,
14 | start_angle: types.Optional[types.Numeric] = -90,
15 | end_angle: types.Optional[types.Numeric] = 270,
16 | center_x: types.Optional[types.Union[types.Numeric, str]] = None,
17 | center_y: types.Optional[types.Union[types.Numeric, str]] = None,
18 | series_mark: str = "area",
19 | point_opts: types.Optional[opts.PointOpts] = None,
20 | line_opts: types.Optional[opts.LineOpts] = None,
21 | area_opts: types.Optional[opts.AreaOpts] = None,
22 | label_opts: types.Optional[opts.LabelOpts] = None,
23 | is_mark_overlap: types.Optional[bool] = False,
24 | point_dis: types.Optional[types.Numeric] = None,
25 | point_dis_mul: types.Optional[types.Numeric] = None,
26 | layout_radius: types.Optional[
27 | types.Union[str, types.Numeric, types.JSFunc]
28 | ] = None,
29 | ):
30 | self.options.update(
31 | {
32 | "type": ChartType.RADAR,
33 | "categoryField": category_field,
34 | "valueField": value_field,
35 | "outerRadius": outer_radius,
36 | "innerRadius": inner_radius,
37 | "startAngle": start_angle,
38 | "endAngle": end_angle,
39 | "centerX": center_x,
40 | "centerY": center_y,
41 | "seriesMark": series_mark,
42 | "point": point_opts,
43 | "line": line_opts,
44 | "area": area_opts,
45 | "label": label_opts,
46 | "markOverlap": is_mark_overlap,
47 | "pointDis": point_dis,
48 | "pointDisMul": point_dis_mul,
49 | "layoutRadius": layout_radius,
50 | }
51 | )
52 |
53 | return self
54 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/range_area.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class RangeArea(RectChart):
8 | def set_range_area_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | series_mark: types.Optional[str] = None,
13 | area_opts: types.Optional[opts.AreaOpts] = None,
14 | line_opts: types.Optional[opts.LineOpts] = None,
15 | point_opts: types.Optional[opts.PointOpts] = None,
16 | label_opts: types.Optional[opts.LabelOpts] = None,
17 | area_label_opts: types.Optional[opts.LabelOpts] = None,
18 | total_label_opts: types.Optional[opts.LabelOpts] = None,
19 | sampling: types.Optional[str] = None,
20 | sampling_factor: types.Optional[types.Numeric] = None,
21 | is_mark_overlap: types.Optional[bool] = False,
22 | point_dis: types.Optional[types.Numeric] = None,
23 | point_dis_mul: types.Optional[types.Numeric] = None,
24 | min_field: types.Optional[str] = None,
25 | max_field: types.Optional[str] = None,
26 | ):
27 | self.options.update(
28 | {
29 | "type": ChartType.RANGE_AREA,
30 | "direction": direction,
31 | "sortDataByAxis": is_sort_data_by_axis,
32 | "seriesMark": series_mark,
33 | "area": area_opts,
34 | "line": line_opts,
35 | "point": point_opts,
36 | "label": label_opts,
37 | "areaLabel": area_label_opts,
38 | "totalLabel": total_label_opts,
39 | "sampling": sampling,
40 | "samplingFactor": sampling_factor,
41 | "markOverlap": is_mark_overlap,
42 | "pointDis": point_dis,
43 | "pointDisMul": point_dis_mul,
44 | "minField": min_field,
45 | "maxField": max_field,
46 | }
47 | )
48 |
49 | return self
50 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/range_column.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class RangeColumn(RectChart):
8 | def set_range_column_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | bar_opts: types.Optional[opts.BarOpts] = None,
13 | bar_background_opts: types.Optional[opts.BarBackgroundOpts] = None,
14 | label_opts: types.Optional[opts.LabelOpts] = None,
15 | min_field: types.Optional[str] = None,
16 | max_field: types.Optional[str] = None,
17 | bar_width: types.Optional[types.Union[str, types.Numeric]] = None,
18 | bar_min_width: types.Optional[types.Union[str, types.Numeric]] = None,
19 | bar_max_width: types.Optional[types.Union[str, types.Numeric]] = None,
20 | bar_gap_in_group: types.Optional[types.Union[str, types.Numeric]] = None,
21 | bar_min_height: types.Optional[types.Union[str, types.Numeric]] = None,
22 | stack_corner_radius: types.Optional[
23 | types.Union[types.Numeric, types.Sequence[types.Numeric], types.JSFunc]
24 | ] = None,
25 | ):
26 | self.options.update(
27 | {
28 | "type": ChartType.RANGE_COLUMN,
29 | "direction": direction,
30 | "sortDataByAxis": is_sort_data_by_axis,
31 | "bar": bar_opts,
32 | "barBackground": bar_background_opts,
33 | "label": label_opts,
34 | "minField": min_field,
35 | "maxField": max_field,
36 | "barWidth": bar_width,
37 | "barMinWidth": bar_min_width,
38 | "barMaxWidth": bar_max_width,
39 | "barGapInGroup": bar_gap_in_group,
40 | "barMinHeight": bar_min_height,
41 | "stackCornerRadius": stack_corner_radius,
42 | }
43 | )
44 |
45 | return self
46 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/rose.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Rose(Chart):
8 | def set_rose_spec(
9 | self,
10 | category_field: types.Optional[types.Union[str, types.Sequence[str]]] = None,
11 | value_field: types.Optional[types.Union[str, types.Sequence[str]]] = None,
12 | outer_radius: types.Optional[types.Numeric] = None,
13 | inner_radius: types.Optional[types.Numeric] = None,
14 | start_angle: types.Optional[types.Numeric] = -90,
15 | end_angle: types.Optional[types.Numeric] = 270,
16 | center_x: types.Optional[types.Union[types.Numeric, str]] = None,
17 | center_y: types.Optional[types.Union[types.Numeric, str]] = None,
18 | rose_opts: types.Optional[opts.RoseOpts] = None,
19 | label_opts: types.Optional[opts.LabelOpts] = None,
20 | layout_radius: types.Optional[
21 | types.Union[str, types.Numeric, types.JSFunc]
22 | ] = None,
23 | ):
24 | self.options.update(
25 | {
26 | "type": ChartType.ROSE,
27 | "categoryField": category_field,
28 | "valueField": value_field,
29 | "outerRadius": outer_radius,
30 | "innerRadius": inner_radius,
31 | "startAngle": start_angle,
32 | "endAngle": end_angle,
33 | "centerX": center_x,
34 | "centerY": center_y,
35 | "rose": rose_opts,
36 | "label": label_opts,
37 | "layoutRadius": layout_radius,
38 | }
39 | )
40 |
41 | return self
42 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/sankey.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Sankey(Chart):
8 | def set_sankey_spec(
9 | self,
10 | node_opts: types.Optional[opts.SankeyNodeOpts] = None,
11 | link_opts: types.Optional[opts.SankeyLinkOpts] = None,
12 | category_field: types.Optional[str] = None,
13 | value_field: types.Optional[str] = None,
14 | source_field: types.Optional[str] = None,
15 | target_field: types.Optional[str] = None,
16 | direction: str = "horizontal",
17 | node_align: types.Optional[str] = None,
18 | cross_node_align: types.Optional[str] = None,
19 | is_inverse: types.Optional[bool] = None,
20 | node_gap: types.Optional[types.Numeric] = None,
21 | node_width: types.Optional[
22 | types.Union[str, types.Numeric, types.JSFunc]
23 | ] = None,
24 | link_width: types.Optional[types.Union[types.Numeric, types.JSFunc]] = None,
25 | min_step_width: types.Optional[types.Numeric] = None,
26 | min_node_height: types.Optional[types.Numeric] = None,
27 | max_node_height: types.Optional[types.Numeric] = None,
28 | min_link_height: types.Optional[types.Numeric] = None,
29 | max_link_height: types.Optional[types.Numeric] = None,
30 | iterations: types.Optional[types.Numeric] = None,
31 | node_key: types.Optional[types.Union[str, types.Numeric, types.JSFunc]] = None,
32 | link_sort_by: types.Optional[types.JSFunc] = None,
33 | node_sort_by: types.Optional[types.JSFunc] = None,
34 | set_node_layer: types.Optional[types.JSFunc] = None,
35 | is_drop_isolated_node: types.Optional[bool] = None,
36 | node_height: types.Optional[types.Union[types.Numeric, types.JSFunc]] = None,
37 | link_height: types.Optional[types.Union[types.Numeric, types.JSFunc]] = None,
38 | is_equal_node_height: types.Optional[bool] = None,
39 | link_overlap: types.Optional[str] = None,
40 | emphasis_opts: types.Optional[opts.SankeyEmphasisOpts] = None,
41 | overflow: types.Optional[str] = None,
42 | label_opts: types.Optional[opts.LabelOpts] = None,
43 | ):
44 | self.options.update(
45 | {
46 | "type": ChartType.SANKEY,
47 | "node": node_opts,
48 | "link": link_opts,
49 | "categoryField": category_field,
50 | "valueField": value_field,
51 | "sourceField": source_field,
52 | "targetField": target_field,
53 | "direction": direction,
54 | "nodeAlign": node_align,
55 | "crossNodeAlign": cross_node_align,
56 | "inverse": is_inverse,
57 | "nodeGap": node_gap,
58 | "nodeWidth": node_width,
59 | "linkWidth": link_width,
60 | "minStepWidth": min_step_width,
61 | "minNodeHeight": min_node_height,
62 | "maxNodeHeight": max_node_height,
63 | "minLinkHeight": min_link_height,
64 | "maxLinkHeight": max_link_height,
65 | "iterations": iterations,
66 | "nodeKey": node_key,
67 | "linkSortBy": link_sort_by,
68 | "nodeSortBy": node_sort_by,
69 | "setNodeLayer": set_node_layer,
70 | "dropIsolatedNode": is_drop_isolated_node,
71 | "nodeHeight": node_height,
72 | "linkHeight": link_height,
73 | "equalNodeHeight": is_equal_node_height,
74 | "linkOverlap": link_overlap,
75 | "emphasis": emphasis_opts,
76 | "overflow": overflow,
77 | "label": label_opts,
78 | }
79 | )
80 |
81 | return self
82 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/scatter.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Scatter(RectChart):
8 | def set_scatter_spec(
9 | self,
10 | label_opts: types.Optional[opts.LabelOpts] = None,
11 | point_opts: types.Optional[opts.PointOpts] = None,
12 | size_field: types.Optional[str] = None,
13 | size: types.Union[types.Numeric, types.Sequence, dict, types.JSFunc] = None,
14 | shape_field: types.Union[
15 | types.Numeric, types.Sequence, dict, types.JSFunc
16 | ] = None,
17 | shape: types.Union[types.Numeric, types.Sequence, dict, types.JSFunc] = None,
18 | ):
19 | self.options.update(
20 | {
21 | "type": ChartType.SCATTER,
22 | "label": label_opts,
23 | "point": point_opts,
24 | "sizeField": size_field,
25 | "size": size,
26 | "shapeField": shape_field,
27 | "shape": shape,
28 | }
29 | )
30 |
31 | return self
32 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/sequence.py:
--------------------------------------------------------------------------------
1 | from ... import types
2 | from ...charts.chart import Chart
3 | from ...globals import ChartType
4 | from .. import Bar, Dot, Link
5 |
6 |
7 | class Sequence(Chart):
8 | def set_sequence_spec(
9 | self,
10 | append_padding: types.Optional[types.Numeric] = None,
11 | series: types.Optional[
12 | types.Sequence[types.Union[Bar, Dot, Link, dict]]
13 | ] = None,
14 | ):
15 | self.options.update(
16 | {
17 | "type": ChartType.SEQUENCE,
18 | "appendPadding": append_padding,
19 | "series": series,
20 | }
21 | )
22 |
23 | return self
24 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/sunburst.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Sunburst(Chart):
8 | def set_sunburst_spec(
9 | self,
10 | category_field: types.Optional[str] = None,
11 | value_field: types.Optional[str] = None,
12 | center_x: types.Optional[types.Union[types.Numeric, str]] = None,
13 | center_y: types.Optional[types.Union[types.Numeric, str]] = None,
14 | offset_x: types.Optional[types.Numeric] = None,
15 | offset_y: types.Optional[types.Numeric] = None,
16 | start_angle: types.Optional[types.Numeric] = -90,
17 | end_angle: types.Optional[types.Numeric] = 270,
18 | inner_radius: types.Optional[types.Numeric] = None,
19 | outer_radius: types.Optional[types.Numeric] = None,
20 | gap: types.Optional[
21 | types.Union[types.Numeric, types.Sequence[types.Numeric]]
22 | ] = None,
23 | label_layout_align: types.Optional[str] = None,
24 | label_layout_rotate: types.Optional[str] = None,
25 | label_layout_offset: types.Optional[types.Numeric] = None,
26 | is_label_auto_visible_enable: types.Optional[bool] = None,
27 | label_auto_visible_circumference: types.Optional[types.Numeric] = None,
28 | is_drill: types.Optional[bool] = None,
29 | drill_field: types.Optional[str] = None,
30 | label_opts: types.Optional[opts.LabelOpts] = None,
31 | sunburst_opts: types.Optional[opts.SunburstOpts] = None,
32 | ):
33 | self.options.update(
34 | {
35 | "type": ChartType.SUNBURST,
36 | "categoryField": category_field,
37 | "valueField": value_field,
38 | "centerX": center_x,
39 | "centerY": center_y,
40 | "offsetX": offset_x,
41 | "offsetY": offset_y,
42 | "startAngle": start_angle,
43 | "endAngle": end_angle,
44 | "innerRadius": inner_radius,
45 | "outerRadius": outer_radius,
46 | "gap": gap,
47 | "labelLayout": {
48 | "align": label_layout_align,
49 | "rotate": label_layout_rotate,
50 | "offset": label_layout_offset,
51 | },
52 | "labelAutoVisible": {
53 | "enable": is_label_auto_visible_enable,
54 | "circumference": label_auto_visible_circumference,
55 | },
56 | "drill": is_drill,
57 | "drillField": drill_field,
58 | "label": label_opts,
59 | "sunburst": sunburst_opts,
60 | }
61 | )
62 |
63 | return self
64 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/treemap.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Treemap(Chart):
8 | def set_treemap_spec(
9 | self,
10 | category_field: types.Optional[str] = None,
11 | value_field: types.Optional[str] = None,
12 | aspect_ratio: types.Optional[types.Numeric] = 1.618,
13 | split_type: types.Optional[str] = "binary",
14 | gap_width: types.Optional[
15 | types.Union[types.Numeric, types.Sequence[types.Numeric]]
16 | ] = None,
17 | node_padding: types.Optional[
18 | types.Union[types.Numeric, types.Sequence[types.Numeric]]
19 | ] = None,
20 | max_depth: types.Optional[types.Numeric] = None,
21 | min_visible_area: types.Optional[types.Numeric] = None,
22 | min_children_visible_area: types.Optional[types.Numeric] = None,
23 | is_roam: types.Optional[bool] = None,
24 | is_drill: types.Optional[bool] = None,
25 | drill_field: types.Optional[str] = None,
26 | leaf_opts: types.Optional[opts.TreeMapLeafOpts] = None,
27 | non_leaf_opts: types.Optional[opts.TreeMapNonLeafOpts] = None,
28 | label_opts: types.Optional[opts.LabelOpts] = None,
29 | non_leaf_label_opts: types.Optional[opts.LabelOpts] = None,
30 | ):
31 | self.options.update(
32 | {
33 | "type": ChartType.TREEMAP,
34 | "categoryField": category_field,
35 | "valueField": value_field,
36 | "aspectRatio": aspect_ratio,
37 | "splitType": split_type,
38 | "gapWidth": gap_width,
39 | "nodePadding": node_padding,
40 | "maxDepth": max_depth,
41 | "minVisibleArea": min_visible_area,
42 | "minChildrenVisibleArea": min_children_visible_area,
43 | "roam": is_roam,
44 | "drill": is_drill,
45 | "drillField": drill_field,
46 | "leaf": leaf_opts,
47 | "nonLeaf": non_leaf_opts,
48 | "label": label_opts,
49 | "nonLeafLabel": non_leaf_label_opts,
50 | }
51 | )
52 |
53 | return self
54 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/venn.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType, RegisterFunctionType
5 |
6 |
7 | class Venn(Chart):
8 | def __init__(
9 | self,
10 | init_opts: types.Init = opts.InitOpts(),
11 | render_opts: types.RenderInit = opts.RenderOpts(),
12 | ):
13 | super().__init__(init_opts=init_opts, render_opts=render_opts)
14 | # register venn chart.
15 | self.add_js_funcs(RegisterFunctionType.Venn)
16 |
17 | def set_venn_spec(
18 | self,
19 | category_field: types.Optional[str] = None,
20 | value_field: types.Optional[str] = None,
21 | circle_opts: types.Optional[opts.VennCircleOpts] = None,
22 | overlap_opts: types.Optional[opts.VennOverlapOpts] = None,
23 | label_opts: types.Optional[opts.LabelOpts] = None,
24 | overlap_label_opts: types.Optional[opts.LabelOpts] = None,
25 | ):
26 | self.options.update(
27 | {
28 | "type": ChartType.VENN,
29 | "categoryField": category_field,
30 | "valueField": value_field,
31 | "circle": circle_opts,
32 | "overlap": overlap_opts,
33 | "label": label_opts,
34 | "overlapLabel": overlap_label_opts,
35 | }
36 | )
37 |
38 | return self
39 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/waterfall.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Waterfall(RectChart):
8 | def set_waterfall_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | bar_opts: types.Optional[opts.BarOpts] = None,
13 | bar_background_opts: types.Optional[opts.BarBackgroundOpts] = None,
14 | bar_width: types.Optional[types.Union[str, types.Numeric]] = None,
15 | bar_min_width: types.Optional[types.Union[str, types.Numeric]] = None,
16 | bar_max_width: types.Optional[types.Union[str, types.Numeric]] = None,
17 | total_opts: types.Optional[types.Sequence[types.WaterfallTotal]] = None,
18 | leader_line_opts: types.Optional[opts.WaterfallLeaderLineOpts] = None,
19 | stack_label_opts: types.Optional[opts.LabelOpts] = None,
20 | label_opts: types.Optional[opts.LabelOpts] = None,
21 | ):
22 | self.options.update(
23 | {
24 | "type": ChartType.WATERFALL,
25 | "direction": direction,
26 | "sortDataByAxis": is_sort_data_by_axis,
27 | "bar": bar_opts,
28 | "barBackground": bar_background_opts,
29 | "barWidth": bar_width,
30 | "barMinWidth": bar_min_width,
31 | "barMaxWidth": bar_max_width,
32 | "total": total_opts,
33 | "leaderLine": leader_line_opts,
34 | "stackLabel": stack_label_opts,
35 | "label": label_opts,
36 | }
37 | )
38 |
39 | return self
40 |
--------------------------------------------------------------------------------
/pyvchart/charts/basic_charts/wordcloud.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class WordCloud(RectChart):
8 | def set_wordcloud_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | name_field: types.Optional[str] = None,
13 | value_field: types.Optional[str] = None,
14 | font_family_field: types.Optional[str] = None,
15 | font_weight_field: types.Optional[str] = None,
16 | font_styles_field: types.Optional[str] = None,
17 | color_hex_field: types.Optional[str] = None,
18 | color_mode: types.Optional[str] = None,
19 | color_list: types.Optional[types.Sequence] = None,
20 | rotate_angles: types.Optional[types.Sequence] = None,
21 | font_weight_range: types.Optional[types.Sequence] = None,
22 | font_size_range: types.Optional[types.Union[str, types.Sequence]] = None,
23 | mask_shape: types.Optional[types.Union[str, types.JSFunc]] = None,
24 | is_random: types.Optional[bool] = None,
25 | wordcloud_config_opts: types.Optional[opts.WordCloudConfigOpts] = None,
26 | wordcloud_shape_config_opts: types.Optional[
27 | opts.WordCloudShapeConfigOpts
28 | ] = None,
29 | wordcloud_opts: types.Optional[opts.WordCloudOpts] = None,
30 | is_word_mask_visible: types.Optional[bool] = None,
31 | word_mask_style_opts: types.Optional[opts.BaseStyleOpts] = None,
32 | word_mask_state_opts: types.Optional[dict] = None,
33 | ):
34 | self.options.update(
35 | {
36 | "type": ChartType.WORDCLOUD,
37 | "direction": direction,
38 | "sortDataByAxis": is_sort_data_by_axis,
39 | "nameField": name_field,
40 | "valueField": value_field,
41 | "fontFamilyField": font_family_field,
42 | "fontWeightField": font_weight_field,
43 | "fontStylesField": font_styles_field,
44 | "colorHexField": color_hex_field,
45 | "colorMode": color_mode,
46 | "colorList": color_list,
47 | "rotateAngles": rotate_angles,
48 | "fontWeightRange": font_weight_range,
49 | "fontSizeRange": font_size_range,
50 | "maskShape": mask_shape,
51 | "random": is_random,
52 | "wordCloudConfig": wordcloud_config_opts,
53 | "wordCloudShapeConfig": wordcloud_shape_config_opts,
54 | "word": wordcloud_opts,
55 | "wordMask": {
56 | "visible": is_word_mask_visible,
57 | "style": word_mask_style_opts,
58 | "state": word_mask_state_opts,
59 | },
60 | }
61 | )
62 |
63 | return self
64 |
--------------------------------------------------------------------------------
/pyvchart/charts/mixins.py:
--------------------------------------------------------------------------------
1 | from ..render import engine
2 |
3 |
4 | class ChartMixin:
5 | def add_js_funcs(self, *fns):
6 | for fn in fns:
7 | self.js_functions.add(fn)
8 | return self
9 |
10 | def add_js_events(self, *fns):
11 | for fn in fns:
12 | self.js_events.add(fn)
13 | return self
14 |
15 | def load_javascript(self):
16 | return engine.load_javascript(self)
17 |
--------------------------------------------------------------------------------
/pyvchart/charts/three_axis_charts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisActor/py-vchart/6157feaead72b2f9ab2eab0f83bfaad286c6d8a4/pyvchart/charts/three_axis_charts/__init__.py
--------------------------------------------------------------------------------
/pyvchart/charts/three_axis_charts/bar3D.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class Bar3D(RectChart):
8 | def set_bar3d_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | bar3d_opts: types.Optional[opts.Bar3DOpts] = None,
13 | label_opts: opts.LabelOpts = None,
14 | bar_width: types.Optional[types.Union[str, types.Numeric]] = None,
15 | bar_min_width: types.Optional[types.Union[str, types.Numeric]] = None,
16 | bar_max_width: types.Optional[types.Union[str, types.Numeric]] = None,
17 | ):
18 | self.options.update(
19 | {
20 | "type": ChartType.BAR3D,
21 | "direction": direction,
22 | "sortDataByAxis": is_sort_data_by_axis,
23 | "bar3d": bar3d_opts,
24 | "label": label_opts,
25 | "barWidth": bar_width,
26 | "barMinWidth": bar_min_width,
27 | "barMaxWidth": bar_max_width,
28 | }
29 | )
30 |
31 | return self
32 |
--------------------------------------------------------------------------------
/pyvchart/charts/three_axis_charts/funnel3D.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import Chart
4 | from ...globals import ChartType
5 |
6 |
7 | class Funnel3D(Chart):
8 | def set_funnel3d_spec(
9 | self,
10 | category_field: types.Optional[str] = None,
11 | value_field: types.Optional[str] = None,
12 | funnel_orient: types.Optional[str] = "top",
13 | funnel_align: types.Optional[str] = "center",
14 | height_ratio: types.Optional[types.Numeric] = 0.5,
15 | shape: types.Optional[str] = "trapezoid",
16 | is_transform: types.Optional[bool] = None,
17 | is_cone: types.Optional[bool] = None,
18 | gap: types.Optional[types.Numeric] = None,
19 | max_size: types.Optional[types.Union[types.Numeric, str]] = "80%",
20 | min_size: types.Optional[types.Union[types.Numeric, str]] = None,
21 | funnel3d_opts: types.Optional[opts.Funnel3DOpts] = None,
22 | funnel3d_transform_opts: types.Optional[opts.FunnelTransformOpts] = None,
23 | label_opts: types.Optional[opts.LabelOpts] = None,
24 | transform_label_opts: types.Optional[opts.LabelOpts] = None,
25 | outer_label_opts: types.Optional[opts.LabelOpts] = None,
26 | ):
27 | self.options.update(
28 | {
29 | "type": ChartType.FUNNEL3D,
30 | "categoryField": category_field,
31 | "valueField": value_field,
32 | "funnelOrient": funnel_orient,
33 | "funnelAlign": funnel_align,
34 | "heightRatio": height_ratio,
35 | "shape": shape,
36 | "isTransform": is_transform,
37 | "isCone": is_cone,
38 | "gap": gap,
39 | "maxSize": max_size,
40 | "minSize": min_size,
41 | "funnel3d": funnel3d_opts,
42 | "transform": funnel3d_transform_opts,
43 | "label": label_opts,
44 | "transformLabel": transform_label_opts,
45 | "outerLabel": outer_label_opts,
46 | }
47 | )
48 |
49 | return self
50 |
--------------------------------------------------------------------------------
/pyvchart/charts/three_axis_charts/wordCloud3D.py:
--------------------------------------------------------------------------------
1 | from ... import options as opts
2 | from ... import types
3 | from ...charts.chart import RectChart
4 | from ...globals import ChartType
5 |
6 |
7 | class WordCloud3D(RectChart):
8 | def set_wordcloud3d_spec(
9 | self,
10 | direction: str = "vertical",
11 | is_sort_data_by_axis: types.Optional[bool] = None,
12 | name_field: types.Optional[str] = None,
13 | value_field: types.Optional[str] = None,
14 | font_family_field: types.Optional[str] = None,
15 | font_weight_field: types.Optional[str] = None,
16 | font_styles_field: types.Optional[str] = None,
17 | color_hex_field: types.Optional[str] = None,
18 | color_mode: types.Optional[str] = None,
19 | color_list: types.Optional[types.Sequence] = None,
20 | rotate_angles: types.Optional[types.Sequence] = None,
21 | font_weight_range: types.Optional[types.Sequence] = None,
22 | font_size_range: types.Optional[types.Union[str, types.Sequence]] = None,
23 | mask_shape: types.Optional[types.Union[str, types.JSFunc]] = None,
24 | is_random: types.Optional[bool] = None,
25 | wordcloud_config_opts: types.Optional[opts.WordCloudConfigOpts] = None,
26 | wordcloud_shape_config_opts: types.Optional[
27 | opts.WordCloudShapeConfigOpts
28 | ] = None,
29 | wordcloud_opts: types.Optional[opts.WordCloudOpts] = None,
30 | filling_word_opts: types.Optional[opts.WordCloud3DFillingWordOpts] = None,
31 | depth_3d: types.Optional[types.Numeric] = None,
32 | ):
33 | self.options.update(
34 | {
35 | "type": ChartType.WORDCLOUD3D,
36 | "direction": direction,
37 | "sortDataByAxis": is_sort_data_by_axis,
38 | "nameField": name_field,
39 | "valueField": value_field,
40 | "fontFamilyField": font_family_field,
41 | "fontWeightField": font_weight_field,
42 | "fontStylesField": font_styles_field,
43 | "colorHexField": color_hex_field,
44 | "colorMode": color_mode,
45 | "colorList": color_list,
46 | "rotateAngles": rotate_angles,
47 | "fontWeightRange": font_weight_range,
48 | "fontSizeRange": font_size_range,
49 | "maskShape": mask_shape,
50 | "random": is_random,
51 | "wordCloudConfig": wordcloud_config_opts,
52 | "wordCloudShapeConfig": wordcloud_shape_config_opts,
53 | "word": wordcloud_opts,
54 | "fillingWord": filling_word_opts,
55 | "depth3d": depth_3d,
56 | }
57 | )
58 |
59 | return self
60 |
--------------------------------------------------------------------------------
/pyvchart/commons/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisActor/py-vchart/6157feaead72b2f9ab2eab0f83bfaad286c6d8a4/pyvchart/commons/__init__.py
--------------------------------------------------------------------------------
/pyvchart/commons/utils.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from ..datasets import EXTRA, FILENAMES
4 |
5 |
6 | class JsCode:
7 | def __init__(self, js_code: str):
8 | self.js_code = "--x_x--0_0--" + js_code + "--x_x--0_0--"
9 |
10 | def replace(self, pattern: str, repl: str):
11 | self.js_code = re.sub(pattern, repl, self.js_code)
12 | return self
13 |
14 |
15 | class OrderedSet:
16 | def __init__(self, *args):
17 | self._values = dict()
18 | self.items = []
19 | for a in args:
20 | self.add(a)
21 |
22 | def add(self, *items):
23 | for item in items:
24 | if not self._values.get(item, False):
25 | self._values.update({item: True})
26 | self.items.append(item)
27 |
28 |
29 | def produce_require_dict(js_dependencies, js_host) -> dict:
30 | confs, libraries = [], []
31 | for name in js_dependencies.items:
32 | if name in FILENAMES:
33 | f, _ = FILENAMES[name]
34 | confs.append("'{}':'{}{}'".format(name, js_host, f))
35 | libraries.append("'{}'".format(name))
36 | else:
37 | for url, files in EXTRA.items():
38 | if name in files:
39 | f, _ = files[name]
40 | confs.append("'{}':'{}{}'".format(name, url, f))
41 | libraries.append("'{}'".format(name))
42 | break
43 | return dict(config_items=confs, libraries=libraries)
44 |
45 |
46 | def replace_placeholder(html: str) -> str:
47 | return re.sub('"?--x_x--0_0--"?', "", html)
48 |
49 |
50 | def replace_placeholder_with_quotes(html: str) -> str:
51 | return re.sub("--x_x--0_0--", "", html)
52 |
53 |
54 | def _expand(dict_generator):
55 | return dict(list(dict_generator))
56 |
57 |
58 | def _clean_dict(mydict):
59 | for key, value in mydict.items():
60 | if value is not None:
61 | if isinstance(value, dict):
62 | value = _expand(_clean_dict(value))
63 |
64 | elif isinstance(value, (list, tuple, set)):
65 | value = list(_clean_array(value))
66 |
67 | # Not elegant, but effective and less code-intrusive.
68 | elif type(value).__name__ in ["ndarray", "Series"]:
69 | raise ValueError(
70 | "Can't use non-native data structures "
71 | "as axis data to render chart"
72 | )
73 |
74 | elif isinstance(value, str) and not value:
75 | # delete key with empty string
76 | continue
77 |
78 | yield key, value
79 |
80 |
81 | def _clean_array(myarray):
82 | for value in myarray:
83 | if isinstance(value, dict):
84 | yield _expand(_clean_dict(value))
85 |
86 | elif isinstance(value, (list, tuple, set)):
87 | yield list(_clean_array(value))
88 |
89 | else:
90 | yield value
91 |
92 |
93 | def remove_key_with_none_value(incoming_dict):
94 | if isinstance(incoming_dict, dict):
95 | return _expand(_clean_dict(incoming_dict))
96 | elif incoming_dict:
97 | return incoming_dict
98 | else:
99 | return None
100 |
--------------------------------------------------------------------------------
/pyvchart/datasets/__init__.py:
--------------------------------------------------------------------------------
1 | import difflib
2 | import os
3 | import typing
4 | import urllib.request
5 |
6 | import simplejson as json
7 |
8 |
9 | class FuzzyDict(dict):
10 | """Provides a dictionary that performs fuzzy lookup"""
11 |
12 | def __init__(self, cutoff: float = 0.6):
13 | """Construct a new FuzzyDict instance
14 |
15 | items is an dictionary to copy items from (optional)
16 | cutoff is the match ratio below which matches should not be considered
17 | cutoff needs to be a float between 0 and 1 (where zero is no match
18 | and 1 is a perfect match)"""
19 | super(FuzzyDict, self).__init__()
20 | self.cutoff = cutoff
21 |
22 | # short wrapper around some super (dict) methods
23 | self._dict_contains = lambda key: super(FuzzyDict, self).__contains__(key)
24 | self._dict_getitem = lambda key: super(FuzzyDict, self).__getitem__(key)
25 |
26 | def _search(self, lookfor: typing.Any, stop_on_first: bool = False):
27 | """Returns the value whose key best matches lookfor
28 |
29 | if stop_on_first is True then the method returns as soon
30 | as it finds the first item
31 | """
32 |
33 | # if the item is in the dictionary then just return it
34 | if self._dict_contains(lookfor):
35 | return True, lookfor, self._dict_getitem(lookfor), 1
36 |
37 | # set up the fuzzy matching tool
38 | ratio_calc = difflib.SequenceMatcher()
39 | ratio_calc.set_seq1(lookfor)
40 |
41 | # test each key in the dictionary
42 | best_ratio = 0
43 | best_match = None
44 | best_key = None
45 | for key in self:
46 | # if the current key is not a string
47 | # then we just skip it
48 | try:
49 | # set up the SequenceMatcher with other text
50 | ratio_calc.set_seq2(key)
51 | except TypeError:
52 | continue
53 |
54 | # we get an error here if the item to look for is not a
55 | # string - if it cannot be fuzzy matched and we are here
56 | # this it is definitely not in the dictionary
57 | try:
58 | # calculate the match value
59 | ratio = ratio_calc.ratio()
60 | except TypeError:
61 | break
62 |
63 | # if this is the best ratio so far - save it and the value
64 | if ratio > best_ratio:
65 | best_ratio = ratio
66 | best_key = key
67 | best_match = self._dict_getitem(key)
68 |
69 | if stop_on_first and ratio >= self.cutoff:
70 | break
71 |
72 | return best_ratio >= self.cutoff, best_key, best_match, best_ratio
73 |
74 | def __contains__(self, item: typing.Any):
75 | return self._search(item, True)[0]
76 |
77 | def __getitem__(self, lookfor: typing.Any):
78 | matched, key, item, ratio = self._search(lookfor)
79 |
80 | if not matched:
81 | raise KeyError(
82 | "'%s'. closest match: '%s' with ratio %.3f"
83 | % (str(lookfor), str(key), ratio)
84 | )
85 |
86 | return item
87 |
88 |
89 | __HERE = os.path.abspath(os.path.dirname(__file__))
90 | with open(os.path.join(__HERE, "filename.json"), "r", encoding="utf8") as f:
91 | FILENAMES: FuzzyDict = FuzzyDict()
92 | for k, v in json.load(f).items():
93 | FILENAMES[k] = v
94 |
95 | EXTRA = {}
96 |
97 |
98 | def register_url(asset_url: str):
99 | if asset_url:
100 | registry = asset_url + "/registry.json"
101 | try:
102 | contents = urllib.request.urlopen(registry).read()
103 | contents = json.loads(contents)
104 | except Exception as e:
105 | raise e
106 | files = {}
107 | pinyin_names = set()
108 | for name, pinyin in contents["PINYIN_MAP"].items():
109 | file_name = contents["FILE_MAP"][pinyin]
110 | files[name] = [file_name, "js"]
111 | pinyin_names.add(pinyin)
112 |
113 | for key, file_name in contents["FILE_MAP"].items():
114 | if key not in pinyin_names:
115 | # English names
116 | files[key] = [file_name, "js"]
117 |
118 | js_folder_name = contents["JS_FOLDER"]
119 | if js_folder_name == "/":
120 | js_file_prefix = f"{asset_url}/"
121 | else:
122 | js_file_prefix = f"{asset_url}/{js_folder_name}/"
123 | EXTRA[js_file_prefix] = files
124 |
125 |
126 | def register_files(asset_files: dict):
127 | if asset_files:
128 | FILENAMES.update(asset_files)
129 |
--------------------------------------------------------------------------------
/pyvchart/datasets/filename.json:
--------------------------------------------------------------------------------
1 | {
2 | "vchart": ["index.min", "js"]
3 | }
4 |
--------------------------------------------------------------------------------
/pyvchart/globals.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from jinja2 import Environment, FileSystemLoader
4 |
5 |
6 | class _FileType:
7 | SVG: str = "svg"
8 | PNG: str = "png"
9 | JPEG: str = "jpeg"
10 | HTML: str = "html"
11 |
12 |
13 | class _ChartType:
14 | AREA: str = "area"
15 | BAR: str = "bar"
16 | BAR3D: str = "bar3d"
17 | BOXPLOT: str = "boxPlot"
18 | CIRCLE_PACKING: str = "circlePacking"
19 | CIRCULAR_PROGRESS: str = "circularProgress"
20 | CORRELATION: str = "correlation"
21 | COMMON: str = "common"
22 | DOT: str = "dot"
23 | FUNNEL: str = "funnel"
24 | FUNNEL3D: str = "funnel3d"
25 | GAUGE: str = "gauge"
26 | HEATMAP: str = "heatmap"
27 | HISTOGRAM: str = "histogram"
28 | LINE: str = "line"
29 | LINEAR_PROGRESS: str = "linearProgress"
30 | LINK: str = "link"
31 | LIQUID: str = "liquid"
32 | MAP: str = "map"
33 | MOSAIC: str = "mosaic"
34 | PICTOGRAM: str = "pictogram"
35 | PIE: str = "pie"
36 | RADAR: str = "radar"
37 | RANGE_AREA: str = "rangeArea"
38 | RANGE_COLUMN: str = "rangeColumn"
39 | ROSE: str = "rose"
40 | SANKEY: str = "sankey"
41 | SEQUENCE: str = "sequence"
42 | SCATTER: str = "scatter"
43 | SUNBURST: str = "sunburst"
44 | TREEMAP: str = "treemap"
45 | VENN: str = "venn"
46 | WATERFALL: str = "waterfall"
47 | WORDCLOUD: str = "wordCloud"
48 | WORDCLOUD3D: str = "wordCloud3d"
49 |
50 |
51 | class _NotebookType:
52 | JUPYTER_NOTEBOOK = "jupyter_notebook"
53 | JUPYTER_LAB = "jupyter_lab"
54 | NTERACT = "nteract"
55 | ZEPPELIN = "zeppelin"
56 |
57 |
58 | class _OnlineHost:
59 | DEFAULT_HOST = "https://unpkg.com/@visactor/vchart@1.13.5/build/"
60 | NOTEBOOK_HOST = "http://localhost:8888/nbextensions/assets/"
61 |
62 |
63 | class _RenderSepType:
64 | SepType = os.linesep
65 |
66 |
67 | class _RegisterFunctionType:
68 | Liquid = "VChart.registerLiquidChart();"
69 | Venn = "VChart.registerVennChart();"
70 | Mosaic = "VChart.registerMosaicChart();"
71 | Pictogram = "VChart.registerPictogramChart();"
72 |
73 |
74 | FileType = _FileType()
75 | ChartType = _ChartType
76 | NotebookType = _NotebookType()
77 | OnlineHostType = _OnlineHost()
78 | RenderSepType = _RenderSepType()
79 | RegisterFunctionType = _RegisterFunctionType()
80 |
81 |
82 | class _CurrentConfig:
83 | PAGE_TITLE = "Awesome-pyvchart"
84 | ONLINE_HOST = OnlineHostType.DEFAULT_HOST
85 | NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK
86 | GLOBAL_ENV = Environment(
87 | keep_trailing_newline=True,
88 | trim_blocks=True,
89 | lstrip_blocks=True,
90 | loader=FileSystemLoader(
91 | os.path.join(
92 | os.path.abspath(os.path.dirname(__file__)), "render", "templates"
93 | )
94 | ),
95 | )
96 |
97 |
98 | CurrentConfig = _CurrentConfig()
99 |
--------------------------------------------------------------------------------
/pyvchart/options/__init__.py:
--------------------------------------------------------------------------------
1 | # flake8: noqa
2 |
3 | from .charts_options import (
4 | AreaOpts,
5 | ArrowOpts,
6 | AnimationOpts,
7 | BarOpts,
8 | BarBackgroundOpts,
9 | Bar3DOpts,
10 | BaseDataOpts,
11 | BoxplotOpts,
12 | CirclePackingOpts,
13 | CorrelationCenterPointOpts,
14 | CorrelationRipplePointOpts,
15 | CorrelationNodePointOpts,
16 | DotOpts,
17 | DotGridOpts,
18 | DotSubTitleOpts,
19 | DotSymbolOpts,
20 | DotTitleOpts,
21 | FunnelOpts,
22 | Funnel3DOpts,
23 | FunnelTransformOpts,
24 | GaugeSegmentOpts,
25 | GaugeTrackOpts,
26 | GaugeOpts,
27 | GaugePinOpts,
28 | GaugePinBackgroundOpts,
29 | GaugePointerOpts,
30 | HeatMapCellOpts,
31 | HeatMapCellBackgroundOpts,
32 | InteractionOpts,
33 | LineOpts,
34 | LineCurveLabelOpts,
35 | LinkOpts,
36 | LiquidOpts,
37 | LiquidBackgroundOpts,
38 | MapLabelOpts,
39 | PictogramOpts,
40 | PieOpts,
41 | PointOpts,
42 | ProgressOpts,
43 | RoseOpts,
44 | SankeyEmphasisOpts,
45 | SankeyLinkOpts,
46 | SankeyNodeOpts,
47 | SunburstOpts,
48 | TrackOpts,
49 | TreeMapLeafOpts,
50 | TreeMapNonLeafOpts,
51 | WaterfallLeaderLineOpts,
52 | WaterfallTotalCustomOpts,
53 | WaterfallTotalEndOpts,
54 | WaterfallTotalFieldOpts,
55 | WordCloudOpts,
56 | WordCloudConfigOpts,
57 | WordCloudShapeConfigOpts,
58 | WordCloud3DFillingWordOpts,
59 | VennCircleOpts,
60 | VennOverlapOpts,
61 | TextConfigTextOpts,
62 | TextConfigImageOpts,
63 | )
64 |
65 | from .global_options import (
66 | BaseHtmlOrReactOpts,
67 | BaseBorderOpts,
68 | BaseStateOpts,
69 | BaseStyleOpts,
70 | BaseSymbolOpts,
71 | BaseTitleTextStyleOpts,
72 | ColorOpts,
73 | IndicatorTitleOpts,
74 | IndicatorContentOpts,
75 | IndicatorOpts,
76 | InitOpts,
77 | PaddingOpts,
78 | RegionOpts,
79 | Render3DOpts,
80 | RenderOpts,
81 | TextConfigTextOpts,
82 | TextConfigImageOpts,
83 | TitleTextCharacterOpts,
84 | TitleOpts,
85 | LabelFilterByGroupOpts,
86 | LabelOpts,
87 | LabelRichTextConfigOpts,
88 | LabelRichStyleOpts,
89 | LabelStyleOpts,
90 | LabelSmartInvertOpts,
91 | LegendTitleOpts,
92 | LegendBackgroundOpts,
93 | BaseLegendOpts,
94 | DiscreteLegendItemOpts,
95 | DiscreteLegendPagerOpts,
96 | DiscreteLegendOpts,
97 | ColorLegendOpts,
98 | SizeLegendOpts,
99 | BaseMarkOpts,
100 | MarkCoordinatesOpts,
101 | MarkLineOpts,
102 | MarkAreaOpts,
103 | MarkPointItemLineOpts,
104 | MarkPointItemContentOpts,
105 | MarkPointOpts,
106 | CrossHairFieldOpts,
107 | CrossHairOpts,
108 | AxesTickOpts,
109 | AxesSubTickOpts,
110 | AxesGridOpts,
111 | AxesBackgroundOpts,
112 | AxesLabelOpts,
113 | AxesDomainLineOpts,
114 | BaseAxesOpts,
115 | AxesBreakOpts,
116 | AxesLayerOpts,
117 | AxesLinearOpts,
118 | AxesBandOpts,
119 | AxesTimeOpts,
120 | AxesLogOpts,
121 | AxesSymlogOpts,
122 | TooltipStylePanelOpts,
123 | TooltipStyleOpts,
124 | TooltipCustomStyleOpts,
125 | TooltipCustomStylePositionOpts,
126 | TooltipCustomOpts,
127 | TooltipOpts,
128 | LayoutColRowOpts,
129 | LayoutElementOpts,
130 | LayoutOpts,
131 | PlayerSpecOpts,
132 | PlayerOpts,
133 | PlayerSliderOpts,
134 | PlayerControllerPositionOpts,
135 | PlayerControllerOpts,
136 | ScrollBarOpts,
137 | DataZoomOpts,
138 | BrushOpts,
139 | ZoomWhenEmptyOpts,
140 | ScalesOpts,
141 | BaseCustomMarkOpts,
142 | CustomMarkSymbolOpts,
143 | CustomMarkRuleOpts,
144 | CustomMarkTextOpts,
145 | CustomMarkRectOpts,
146 | CustomMarkPathOpts,
147 | CustomMarkArcOpts,
148 | CustomMarkPolygonOpts,
149 | CustomMarkImageOpts,
150 | CustomMarkGroupOpts,
151 | ThemeOpts,
152 | HoverOpts,
153 | SelectOpts,
154 | LabelOverlapOpts,
155 | TooltipStyleShapeOpts,
156 | )
157 |
--------------------------------------------------------------------------------
/pyvchart/options/series_options.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Optional, Sequence, Tuple, Union
2 |
3 | from ..commons.utils import JsCode
4 |
5 | Numeric = Union[int, float]
6 | JSFunc = Union[str, JsCode]
7 |
8 |
9 | class BasicOpts:
10 | __slots__ = ("opts",)
11 |
12 | def update(self, **kwargs):
13 | self.opts.update(kwargs)
14 |
15 | def get(self, key: str) -> Any:
16 | return self.opts.get(key)
17 |
18 |
19 | class AnimationOpts(BasicOpts):
20 | def __init__(
21 | self,
22 | type_: Optional[str] = None,
23 | duration: Optional[Numeric] = None,
24 | delay: Optional[Numeric] = None,
25 | easing: Optional[str] = None,
26 | mode: Optional[str] = None,
27 | is_increase_effect: Optional[bool] = None,
28 | ):
29 | self.opts: dict = {
30 | "type": type_,
31 | "duration": duration,
32 | "delay": delay,
33 | "easing": easing,
34 | "mode": mode,
35 | "increaseEffect": is_increase_effect,
36 | }
37 |
--------------------------------------------------------------------------------
/pyvchart/render/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisActor/py-vchart/6157feaead72b2f9ab2eab0f83bfaad286c6d8a4/pyvchart/render/__init__.py
--------------------------------------------------------------------------------
/pyvchart/render/display.py:
--------------------------------------------------------------------------------
1 | import http.client
2 | from urllib.parse import urlparse
3 |
4 | from ..types import Optional, Sequence, Union
5 |
6 |
7 | class HTML:
8 | def __init__(self, data: Optional[str] = None):
9 | self.data = data
10 |
11 | def _repr_html_(self):
12 | return self.data
13 |
14 | def __html__(self):
15 | return self._repr_html_()
16 |
17 |
18 | _lib_t1 = """new Promise(function(resolve, reject) {
19 | var script = document.createElement("script");
20 | script.onload = resolve;
21 | script.onerror = reject;
22 | script.src = "%s";
23 | document.head.appendChild(script);
24 | }).then(() => {
25 | """
26 |
27 | _lib_t2 = """
28 | });"""
29 |
30 | _css_t = """var link = document.createElement("link");
31 | link.ref = "stylesheet";
32 | link.type = "text/css";
33 | link.href = "%s";
34 | document.head.appendChild(link);
35 | """
36 |
37 |
38 | class Javascript:
39 | def __init__(
40 | self,
41 | data: Optional[str] = None,
42 | lib: Optional[Union[str, Sequence]] = None,
43 | css: Optional[Union[str, Sequence]] = None,
44 | ):
45 | if isinstance(lib, str):
46 | lib = [lib]
47 | elif lib is None:
48 | lib = []
49 | if isinstance(css, str):
50 | css = [css]
51 | elif css is None:
52 | css = []
53 | self.lib = lib
54 | self.css = css
55 | self.data = data or ""
56 | self.javascript_contents = dict()
57 |
58 | def _repr_javascript_(self):
59 | r = ""
60 | for c in self.css:
61 | r += _css_t % c
62 | for d in self.lib:
63 | r += _lib_t1 % d
64 | r += self.data
65 | r += _lib_t2 * len(self.lib)
66 | return r
67 |
68 | def load_javascript_contents(self):
69 | for lib in self.lib:
70 | parsed_url = urlparse(lib)
71 |
72 | host: str = str(parsed_url.hostname)
73 | port: int = parsed_url.port
74 | path: str = parsed_url.path
75 |
76 | resp: Optional[http.client.HTTPResponse] = None
77 | try:
78 | conn = http.client.HTTPSConnection(host, port)
79 | conn.request("GET", path)
80 | resp = conn.getresponse()
81 | if resp.status != 200:
82 | raise RuntimeError("Cannot load JavaScript lib: %s" % lib)
83 | self.javascript_contents[lib] = resp.read().decode("utf-8")
84 | finally:
85 | if resp is not None:
86 | resp.close()
87 | return self
88 |
--------------------------------------------------------------------------------
/pyvchart/render/templates/macro:
--------------------------------------------------------------------------------
1 | {%- macro render_chart_content(c) -%}
2 |
3 |
21 | {%- endmacro %}
22 |
23 | {%- macro render_notebook_charts(charts, libraries) -%}
24 |
41 | {%- endmacro %}
42 |
43 | {%- macro render_chart_dependencies(c) -%}
44 | {% if 'javascript' in c._render_cache -%}
45 | {% set _javascript = c._render_cache.javascript %}
46 | {% for dep in c.dependencies %}
47 |
50 | {% endfor %}
51 | {%- else -%}
52 | {% for dep in c.dependencies %}
53 |
54 | {% endfor %}
55 | {%- endif %}
56 | {%- endmacro %}
57 |
58 | {%- macro render_chart_css(c) -%}
59 | {% for dep in c.css_libs %}
60 |
61 | {% endfor %}
62 | {%- endmacro %}
63 |
--------------------------------------------------------------------------------
/pyvchart/render/templates/nb_jupyter_lab.html:
--------------------------------------------------------------------------------
1 | {% import 'macro' as macro %}
2 |
3 |
4 |
5 |
6 |
7 |
8 | {% for chart in charts %}
9 | {{ macro.render_chart_content(chart) }}
10 | {% endfor %}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/pyvchart/render/templates/nb_jupyter_notebook.html:
--------------------------------------------------------------------------------
1 | {% import 'macro' as macro %}
2 |
3 |
10 |
11 | {% for chart in charts %}
12 |
13 | {% endfor %}
14 |
15 | {{ macro.render_notebook_charts(charts, libraries) }}
16 |
--------------------------------------------------------------------------------
/pyvchart/render/templates/nb_nteract.html:
--------------------------------------------------------------------------------
1 | {% import 'macro' as macro %}
2 |
3 |
4 |
5 |
6 | {{ macro.render_chart_dependencies(chart, _inner, _javascript) }}
7 |
8 |
9 | {% for c in chart %}
10 | {{ macro.render_chart_content(c) }}
11 | {% endfor %}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/pyvchart/render/templates/simple_chart.html:
--------------------------------------------------------------------------------
1 | {% import 'macro' as macro %}
2 |
3 |
4 |
5 |
6 | {{ chart.page_title }}
7 | {{ macro.render_chart_dependencies(chart) }}
8 | {{ macro.render_chart_css(chart) }}
9 |
10 |
11 | {{ macro.render_chart_content(chart) }}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/pyvchart/types.py:
--------------------------------------------------------------------------------
1 | from typing import (
2 | Any,
3 | Callable,
4 | Iterable,
5 | List,
6 | Mapping,
7 | Optional,
8 | Sequence,
9 | Tuple,
10 | Union,
11 | )
12 |
13 | from . import options as opts
14 | from .options.series_options import JsCode, JSFunc, Numeric
15 |
16 | BaseData = Union[opts.BaseDataOpts, dict]
17 | BaseHtmlOrReact = Union[opts.BaseHtmlOrReactOpts, dict]
18 | BaseBorder = Union[opts.BaseBorderOpts, dict]
19 | BaseTitleTextStyle = Union[opts.BaseTitleTextStyleOpts, dict]
20 | Color = Union[opts.ColorOpts, dict]
21 | Indicator = Union[opts.IndicatorOpts, dict]
22 | IndicatorTitle = Union[opts.IndicatorTitleOpts, dict]
23 | IndicatorContent = Union[opts.IndicatorContentOpts, dict]
24 | Init = Union[opts.InitOpts, dict]
25 | Interaction = Union[opts.InteractionOpts, dict]
26 | Padding = Union[opts.PaddingOpts, dict]
27 | Region = Union[opts.RegionOpts, dict]
28 | RenderInit = Union[opts.RenderOpts, dict]
29 | Title = Union[opts.TitleOpts, dict]
30 | LegendTitle = Union[opts.LegendTitleOpts, dict]
31 | BaseLegend = Union[opts.BaseLegendOpts, dict]
32 | DiscreteLegend = Union[opts.DiscreteLegendOpts, dict]
33 | DiscreteLegendItem = Union[opts.DiscreteLegendItemOpts, dict]
34 | DiscreteLegendPager = Union[opts.DiscreteLegendPagerOpts, dict]
35 | ColorLegend = Union[opts.ColorLegendOpts, dict]
36 | SizeLegend = Union[opts.SizeLegendOpts, dict]
37 | Legend = Union[DiscreteLegend, ColorLegend, SizeLegend]
38 | BaseMark = Union[opts.BaseMarkOpts, dict]
39 | MarkCoordinates = Union[opts.MarkCoordinatesOpts, dict]
40 | MarkLine = Union[opts.MarkLineOpts, dict]
41 | MarkArea = Union[opts.MarkAreaOpts, dict]
42 | MarkPoint = Union[opts.MarkPointOpts, dict]
43 | CrossHairField = Union[opts.CrossHairFieldOpts, dict]
44 | CrossHair = Union[opts.CrossHairOpts, dict]
45 | AxesTick = Union[opts.AxesTickOpts, dict]
46 | AxesSubTick = Union[opts.AxesSubTickOpts, dict]
47 | AxesGrid = Union[opts.AxesGridOpts, dict]
48 | AxesBackground = Union[opts.AxesBackgroundOpts, dict]
49 | AxesLabel = Union[opts.AxesLabelOpts, dict]
50 | AxesDomainLine = Union[opts.AxesDomainLineOpts, dict]
51 | BaseAxes = Union[opts.BaseAxesOpts, dict]
52 | AxesBreak = Union[opts.AxesBreakOpts, dict]
53 | AxesLayer = Union[opts.AxesLayerOpts, dict]
54 | AxesLinear = Union[opts.AxesLinearOpts, dict]
55 | AxesBand = Union[opts.AxesBandOpts, dict]
56 | AxesTime = Union[opts.AxesTimeOpts, dict]
57 | AxesLog = Union[opts.AxesLogOpts, dict]
58 | AxesSymlog = Union[opts.AxesSymlogOpts, dict]
59 | Axes = Union[AxesLinear, AxesBand, AxesTime, AxesLog, AxesSymlog]
60 | Tooltip = Union[opts.TooltipOpts, dict]
61 | LayoutColRow = Union[opts.LayoutColRowOpts, dict]
62 | LayoutElement = Union[opts.LayoutElementOpts, dict]
63 | Layout = Union[opts.LayoutOpts, dict]
64 | Player = Union[opts.PlayerOpts, dict]
65 | PlayerSlider = Union[opts.PlayerSliderOpts, dict]
66 | PlayerController = Union[opts.PlayerControllerOpts, dict]
67 | PlayerControllerPosition = Union[opts.PlayerControllerPositionOpts, dict]
68 | ScrollBar = Union[opts.ScrollBarOpts, dict]
69 | DataZoom = Union[opts.DataZoomOpts, dict]
70 | Brush = Union[opts.BrushOpts, dict]
71 | ZoomWhenEmpty = Union[opts.ZoomWhenEmptyOpts, dict]
72 | Scales = Union[opts.ScalesOpts, dict]
73 | BaseCustomMark = Union[opts.BaseCustomMarkOpts, dict]
74 | CustomMarkSymbol = Union[opts.CustomMarkSymbolOpts, dict]
75 | CustomMarkRule = Union[opts.CustomMarkRuleOpts, dict]
76 | CustomMarkText = Union[opts.CustomMarkTextOpts, dict]
77 | CustomMarkRect = Union[opts.CustomMarkRectOpts, dict]
78 | CustomMarkPath = Union[opts.CustomMarkPathOpts, dict]
79 | CustomMarkArc = Union[opts.CustomMarkArcOpts, dict]
80 | CustomMarkPolygon = Union[opts.CustomMarkPolygonOpts, dict]
81 | CustomMarkImage = Union[opts.CustomMarkImageOpts, dict]
82 | CustomMarkGroup = Union[opts.CustomMarkGroupOpts, dict]
83 | CustomMark = Union[
84 | CustomMarkSymbol,
85 | CustomMarkText,
86 | CustomMarkRect,
87 | CustomMarkPath,
88 | CustomMarkArc,
89 | CustomMarkPolygon,
90 | CustomMarkImage,
91 | CustomMarkGroup,
92 | ]
93 | Theme = Union[opts.ThemeOpts, dict]
94 | Hover = Union[opts.HoverOpts, dict]
95 | Select = Union[opts.SelectOpts, dict]
96 | WaterfallTotal = Union[
97 | opts.WaterfallTotalCustomOpts,
98 | opts.WaterfallTotalEndOpts,
99 | opts.WaterfallTotalFieldOpts,
100 | ]
101 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | black==25.1.0
2 | isort==4.3.16
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | jinja2>=2.11.3
2 | simplejson
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | from shutil import rmtree
4 |
5 | from setuptools import Command, find_packages, setup
6 |
7 | # RELEASE STEPS
8 | # $ python setup.py upload
9 |
10 |
11 | __title__ = "py-vchart"
12 | __folder_title__ = "pyvchart"
13 | __description__ = "python visualization sdk for @visactor/vchart"
14 | __url__ = "https://github.com/VisActor/py-vchart"
15 | __author_email__ = "shjkfld379978424@gmail.com"
16 | __license__ = "MIT"
17 |
18 | __requires__ = [
19 | "setuptools>=38.3",
20 | "jinja2>=2.11.3",
21 | "simplejson"
22 | ]
23 |
24 | __keywords__ = ["VChart", "charts", "plotting-tool"]
25 | # Load the package's _version.py module as a dictionary.
26 | here = os.path.abspath(os.path.dirname(__file__))
27 | about = {}
28 | with open(os.path.join(here, __folder_title__, "_version.py")) as f:
29 | exec(f.read(), about)
30 |
31 |
32 | __version__ = about["__version__"]
33 |
34 |
35 | class UploadCommand(Command):
36 | description = "Build and publish the package."
37 | user_options = []
38 |
39 | @staticmethod
40 | def status(s):
41 | print("✨✨ {0}".format(s))
42 |
43 | def initialize_options(self):
44 | pass
45 |
46 | def finalize_options(self):
47 | pass
48 |
49 | def run(self):
50 | try:
51 | self.status("Removing previous builds…")
52 | rmtree(os.path.join(here, "dist"))
53 | rmtree(os.path.join(here, "build"))
54 | rmtree(os.path.join(here, "{0}.egg-info".format(__title__)))
55 | except OSError:
56 | pass
57 |
58 | self.status("Building Source and Wheel distribution…")
59 | os.system("{0} setup.py sdist bdist_wheel".format(sys.executable))
60 |
61 | self.status("Uploading the package to PyPI via Twine…")
62 | os.system("twine upload dist/*")
63 |
64 | self.status("Pushing git tags…")
65 | os.system('git tag -a v{0} -m "release version v{0}"'.format(__version__))
66 | os.system("git push origin v{0}".format(__version__))
67 |
68 | sys.exit()
69 |
70 |
71 | setup(
72 | name=__title__,
73 | version=__version__,
74 | description=__description__,
75 | url=__url__,
76 | author=about["__author__"],
77 | author_email=__author_email__,
78 | license=__license__,
79 | packages=find_packages(exclude=("test",)),
80 | keywords=__keywords__,
81 | install_requires=__requires__,
82 | zip_safe=False,
83 | include_package_data=True,
84 | classifiers=[
85 | "Development Status :: 5 - Production/Stable",
86 | "Environment :: Console",
87 | "Intended Audience :: Developers",
88 | "License :: OSI Approved :: MIT License",
89 | "Operating System :: OS Independent",
90 | "Programming Language :: Python",
91 | "Programming Language :: Python :: 3.6",
92 | "Programming Language :: Python :: 3.7",
93 | "Programming Language :: Python :: 3.8",
94 | "Programming Language :: Python :: 3.9",
95 | "Programming Language :: Python :: 3.10",
96 | "Programming Language :: Python :: 3.11",
97 | "Programming Language :: Python :: 3.12",
98 | "Programming Language :: Python :: 3.13",
99 | "Topic :: Software Development :: Libraries",
100 | ],
101 | cmdclass={"upload": UploadCommand},
102 | )
103 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | # nose2
4 | os.system(
5 | "nose2 --with-coverage --coverage pyvchart " "--coverage-config .coveragerc -s test"
6 | )
7 |
8 | # pytest
9 | os.system("pytest --cov-branch --cov-report=xml -cov-config=.coveragerc --cov=./ test/")
10 |
11 | # flake8 for code linting
12 | os.system("flake8 --exclude=build,example,images --max-line-length=89 --ignore=F401")
13 |
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from unittest.mock import patch
3 |
4 | from pyvchart.charts.chart import Base
5 |
6 |
7 | class ConsoleOutputRedirect:
8 | """Wrapper to redirect stdout or stderr"""
9 |
10 | def __init__(self, fp):
11 | self.fp = fp
12 |
13 | def write(self, s):
14 | self.fp.write(s)
15 |
16 | def writelines(self, lines):
17 | self.fp.writelines(lines)
18 |
19 | def flush(self):
20 | self.fp.flush()
21 |
22 |
23 | stdout_redirect = ConsoleOutputRedirect(sys.stdout)
24 |
25 |
26 | def chart_base_test(chart_type: str):
27 | """decorator for chart base tests"""
28 |
29 | def decorator(test_func):
30 |
31 | @patch("pyvchart.render.engine.write_utf8_html_file")
32 | def wrapper(self, fake_writer):
33 | chart: Base = test_func(self)
34 | chart.render()
35 | _, content = fake_writer.call_args[0]
36 |
37 | self.assertGreater(len(content), 1000)
38 | self.assertEqual(chart.options.get("type"), chart_type)
39 |
40 | return None
41 |
42 | return wrapper
43 |
44 | return decorator
45 |
--------------------------------------------------------------------------------
/test/fixtures/registry.json:
--------------------------------------------------------------------------------
1 | {
2 | "JUPYTER_URL": "/nbextensions/china-cities-js",
3 | "GITHUB_URL": "https://vchart-maps.github.io/china-cities-js",
4 | "JUPYTER_ENTRY": "china-cities-js/index",
5 | "JS_FOLDER": "js",
6 | "GEOJSON_FOLDER": "geojson",
7 | "PINYIN_MAP": {
8 | "安庆": "an1_qing4"
9 | },
10 | "FILE_MAP": {
11 | "an1_qing4": "shape-with-internal-borders/an1_hui1_an1_qing4",
12 | "English Name": "shape-with-internal-borders/an1_hui1_an1_qing4"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/fixtures/registry_1.json:
--------------------------------------------------------------------------------
1 | {
2 | "JUPYTER_URL": "/nbextensions/china-cities-js",
3 | "GITHUB_URL": "https://vchart-maps.github.io/china-cities-js",
4 | "JUPYTER_ENTRY": "china-cities-js/index",
5 | "JS_FOLDER": "/",
6 | "GEOJSON_FOLDER": "geojson",
7 | "PINYIN_MAP": {
8 | "安庆": "an1_qing4"
9 | },
10 | "FILE_MAP": {
11 | "an1_qing4": "shape-with-internal-borders/an1_hui1_an1_qing4",
12 | "English Name": "shape-with-internal-borders/an1_hui1_an1_qing4"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/requirements.txt:
--------------------------------------------------------------------------------
1 | nose2
2 | codecov
3 | coverage
4 | flake8
5 | mccabe
6 | pytest
7 | pytest-cov
8 | numpy
9 | pandas
--------------------------------------------------------------------------------
/test/test_bar.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Bar
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_BAR_DATA = [
11 | {"month": "Monday", "sales": 22},
12 | {"month": "Tuesday", "sales": 13},
13 | {"month": "Wednesday", "sales": 25},
14 | {"month": "Thursday", "sales": 29},
15 | {"month": "Friday", "sales": 38},
16 | ]
17 |
18 |
19 | class TestBarChart(unittest.TestCase):
20 |
21 | @chart_base_test(chart_type=ChartType.BAR)
22 | def test_bar_base(self):
23 | c = (
24 | Bar()
25 | .set_bar_spec()
26 | .set_data(data=[opts.BaseDataOpts(values=TEST_BAR_DATA)])
27 | .set_xy_field(
28 | x_field_name="time",
29 | y_field_name="value",
30 | )
31 | )
32 | return c
33 |
--------------------------------------------------------------------------------
/test/test_bar3d.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Bar3D
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 | TEST_BAR3D_DATA = [
10 | {"month": "Monday", "sales": 22},
11 | {"month": "Tuesday", "sales": 13},
12 | {"month": "Wednesday", "sales": 25},
13 | {"month": "Thursday", "sales": 29},
14 | {"month": "Friday", "sales": 38},
15 | ]
16 |
17 |
18 | class TestBar3DChart(unittest.TestCase):
19 |
20 | @chart_base_test(chart_type=ChartType.BAR3D)
21 | def test_bar3d_base(self):
22 | c = (
23 | Bar3D()
24 | .set_data(
25 | data=[
26 | opts.BaseDataOpts(
27 | id_="bar3DData",
28 | values=TEST_BAR3D_DATA,
29 | )
30 | ]
31 | )
32 | .set_bar3d_spec(
33 | bar3d_opts=opts.Bar3DOpts(
34 | style=opts.BaseStyleOpts(
35 | length=20,
36 | ),
37 | state=opts.BaseStateOpts(
38 | selected_opts=opts.BaseStyleOpts(
39 | stroke="#000",
40 | ),
41 | ),
42 | ),
43 | )
44 | .set_xy_field(x_field_name="month", y_field_name="sales")
45 | .set_global_options(
46 | axes_opts=[
47 | opts.AxesBandOpts(
48 | base_axes_opts=opts.BaseAxesOpts(
49 | orient="bottom",
50 | tick=opts.AxesTickOpts(tick_size=20),
51 | mode="3d",
52 | ),
53 | ),
54 | opts.AxesLinearOpts(
55 | base_axes_opts=opts.BaseAxesOpts(
56 | orient="left",
57 | tick=opts.AxesTickOpts(tick_size=20),
58 | mode="3d",
59 | ),
60 | ),
61 | ]
62 | )
63 | )
64 |
65 | return c
66 |
--------------------------------------------------------------------------------
/test/test_base.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from datetime import datetime
3 | from unittest.mock import patch
4 |
5 | from pyvchart import options as opts
6 | from pyvchart.charts import Bar
7 | from pyvchart.options import InitOpts, RenderOpts
8 | from pyvchart.globals import CurrentConfig
9 | from pyvchart.commons.utils import JsCode
10 | from pyvchart.charts.base import Base, default
11 | from pyvchart.options.series_options import AnimationOpts
12 |
13 |
14 | class TestBaseClass(unittest.TestCase):
15 |
16 | def test_base_add_functions(self):
17 | c = Base()
18 | c.add_js_funcs("console.log('hello')", "console.log('hello')")
19 | self.assertEqual(1, len(c.js_functions.items))
20 | self.assertEqual(["console.log('hello')"], c.js_functions.items)
21 |
22 | def test_base_add_events(self):
23 | c = Base()
24 | c.add_js_events("console.log('hello')", "console.log('hello')")
25 | self.assertEqual(1, len(c.js_events.items))
26 | self.assertEqual(["console.log('hello')"], c.js_events.items)
27 |
28 | def test_base_init_funcs(self):
29 | c0 = Base({"width": "100px", "height": "200px"})
30 | self.assertEqual(c0.width, "100px")
31 | self.assertEqual(c0.height, "200px")
32 |
33 | c1 = Base(dict(width="110px", height="210px"))
34 | self.assertEqual(c1.width, "110px")
35 | self.assertEqual(c1.height, "210px")
36 | self.assertNotIn(c1.js_host, ["", None])
37 |
38 | @patch("pyvchart.render.engine.write_utf8_html_file")
39 | def test_render(self, fake_writer):
40 | my_render_content = "my_render_content"
41 | bar = Bar()
42 | bar.set_data(data=[opts.BaseDataOpts(values=[{"x": 1, "y": 2}])])
43 | bar.set_xy_field(x_field_name="x", y_field_name="y")
44 | bar.render(my_render_content=my_render_content)
45 | assert "test ok" == "test ok"
46 |
47 | @patch("pyvchart.render.engine.write_utf8_html_file")
48 | def test_render_js_host_none(self, fake_writer):
49 | my_render_content = "my_render_content"
50 | bar = Bar()
51 | bar.set_data(data=[opts.BaseDataOpts(values=[{"x": 1, "y": 2}])])
52 | bar.set_xy_field(x_field_name="x", y_field_name="y")
53 | # Hack to test
54 | bar.js_host = None
55 | # Render
56 | bar.render(my_render_content=my_render_content)
57 | self.assertEqual(bar.js_host, CurrentConfig.ONLINE_HOST)
58 |
59 | @patch("pyvchart.render.engine.write_utf8_html_file")
60 | def test_render_embed_js(self, _):
61 | c = Base(init_opts=InitOpts(is_embed_js=True))
62 | # Embedded JavaScript
63 | content = c.render_embed()
64 | self.assertNotIn(
65 | CurrentConfig.ONLINE_HOST, content, "Embedding JavaScript fails"
66 | )
67 | # No embedded JavaScript
68 | c._embed_js = False
69 | content = c.render_embed()
70 | self.assertIn(
71 | CurrentConfig.ONLINE_HOST, content, "Embedded JavaScript cannot be closed"
72 | )
73 |
74 | def test_base_render_options(self):
75 | c0 = Base(init_opts=InitOpts(is_embed_js=True))
76 | self.assertEqual(c0._embed_js, True)
77 |
78 | def test_base_iso_format(self):
79 | mock_time_str = "2022-04-14 14:42:00"
80 | mock_time = datetime.strptime(mock_time_str, "%Y-%m-%d %H:%M:%S")
81 | assert default(mock_time) == "2022-04-14T14:42:00"
82 |
83 | def test_base_animation_option(self):
84 | c0 = AnimationOpts(easing="cubicInOut")
85 | self.assertEqual(c0.get("easing"), "cubicInOut")
86 |
87 | c0.update(easing=None)
88 | self.assertEqual(c0.get("easing"), None)
89 |
90 | def test_base_chart_id(self):
91 | c0 = Base(render_opts=RenderOpts(dom="1234567"))
92 | self.assertEqual(c0.chart_id, "1234567")
93 |
94 | c1 = Base(render_opts=RenderOpts(dom="1234567"))
95 | self.assertEqual(c1.get_chart_id(), "1234567")
96 |
97 | def test_dump_options_with_quotes(self):
98 | bar = Bar()
99 | bar.set_data(data=[opts.BaseDataOpts(values=[{"x": 1, "y": 2}])])
100 | bar.set_xy_field(x_field_name="x", y_field_name="y")
101 | bar.set_bar_spec(
102 | label_opts=opts.LabelOpts(
103 | format_method=JsCode(
104 | "(value, datum, ctx) => { return datum['State']; }"
105 | ),
106 | )
107 | )
108 | formatter = """formatMethod": "(value, datum, ctx) => { return datum['State']; }""" # noqa
109 | self.assertIn(formatter, bar.dump_options_with_quotes())
110 |
--------------------------------------------------------------------------------
/test/test_boxplot.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Boxplot
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 | TEST_BOXPLOT_DATA = [
10 | {
11 | "x": "Sub-Saharan Africa",
12 | "y1": 8.72,
13 | "y2": 9.73,
14 | "y3": 10.17,
15 | "y4": 10.51,
16 | "y5": 11.64,
17 | },
18 | {"x": "South Asia", "y1": 9.4, "y2": 10.06, "y3": 10.75, "y4": 11.56, "y5": 12.5},
19 | {
20 | "x": "Middle East & North Africa",
21 | "y1": 9.54,
22 | "y2": 10.6,
23 | "y3": 11.05,
24 | "y4": 11.5,
25 | "y5": 11.92,
26 | },
27 | {
28 | "x": "Latin America & Caribbean",
29 | "y1": 8.74,
30 | "y2": 9.46,
31 | "y3": 10.35,
32 | "y4": 10.94,
33 | "y5": 12.21,
34 | },
35 | {
36 | "x": "East Asia & Pacific",
37 | "y1": 7.8,
38 | "y2": 8.95,
39 | "y3": 10.18,
40 | "y4": 11.57,
41 | "y5": 13.25,
42 | },
43 | {
44 | "x": "Europe & Central Asia",
45 | "y1": 9.52,
46 | "y2": 10.39,
47 | "y3": 10.93,
48 | "y4": 11.69,
49 | "y5": 12.63,
50 | },
51 | ]
52 |
53 |
54 | class TestBoxplotChart(unittest.TestCase):
55 |
56 | @chart_base_test(chart_type=ChartType.BOXPLOT)
57 | def test_boxplot_base(self):
58 | c = (
59 | Boxplot()
60 | .set_data(data=[opts.BaseDataOpts(values=TEST_BOXPLOT_DATA)])
61 | .set_boxplot_spec(
62 | min_field="y1",
63 | q1_field="y2",
64 | median_field="y3",
65 | q3_field="y4",
66 | max_field="y5",
67 | direction="vertical",
68 | boxplot_opts=opts.BoxplotOpts(style=opts.BaseStyleOpts(line_width=2)),
69 | )
70 | .set_xy_field(
71 | x_field_name="x",
72 | y_field_name=None,
73 | )
74 | )
75 | return c
76 |
--------------------------------------------------------------------------------
/test/test_circular_progress.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import CircularProgress
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_CIRCLE_PROGRESS_DATA = [
11 | {"type": "Tradition Industries", "value": 0.795, "text": "79.5%"},
12 | {"type": "Business Companies", "value": 0.25, "text": "25%"},
13 | {"type": "Customer-facing Companies", "value": 0.065, "text": "6.5%"},
14 | ]
15 |
16 |
17 | class TestCircularProgressChart(unittest.TestCase):
18 |
19 | @chart_base_test(chart_type=ChartType.CIRCULAR_PROGRESS)
20 | def test_circular_progress_base(self):
21 | c = (
22 | CircularProgress()
23 | .set_data(
24 | data=[opts.BaseDataOpts(id_="id0", values=TEST_CIRCLE_PROGRESS_DATA)]
25 | )
26 | .set_circular_progress_spec(
27 | value_field="value",
28 | category_field="type",
29 | radius=0.8,
30 | inner_radius=0.5,
31 | is_round_cap=True,
32 | corner_radius=20,
33 | progress_opts=opts.ProgressOpts(
34 | style=opts.BaseStyleOpts(
35 | inner_padding=5,
36 | outer_padding=5,
37 | )
38 | ),
39 | )
40 | .set_global_options(
41 | series_field="type",
42 | axes_opts=[
43 | opts.AxesLinearOpts(
44 | is_visible=False,
45 | base_axes_opts=opts.BaseAxesOpts(orient="angle"),
46 | ),
47 | opts.AxesBandOpts(
48 | base_axes_opts=opts.BaseAxesOpts(
49 | is_visible=False, orient="radius"
50 | )
51 | ),
52 | ],
53 | indicator_opts=opts.IndicatorOpts(
54 | is_visible=True,
55 | trigger="hover",
56 | title_opts=opts.IndicatorTitleOpts(
57 | is_visible=True,
58 | field="type",
59 | is_auto_limit=True,
60 | style=opts.BaseStyleOpts(font_size=20, fill="black"),
61 | ),
62 | content_opts=[
63 | opts.IndicatorContentOpts(
64 | is_visible=True,
65 | field="text",
66 | style=opts.BaseStyleOpts(font_size=16, fill="gray"),
67 | )
68 | ],
69 | ),
70 | legend_opts=opts.BaseLegendOpts(
71 | is_visible=True,
72 | orient="right",
73 | title_opts=opts.LegendTitleOpts(is_visible=False),
74 | ),
75 | )
76 | )
77 | return c
78 |
--------------------------------------------------------------------------------
/test/test_correlation.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Correlation
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_CORRELATION_DATA = [
11 | {"word": "输入法哪个好用", "pv": 15952, "ratio": 94, "sim": 3932},
12 | {"word": "谷歌拼音输入法", "pv": 11032, "ratio": 97, "sim": 2799},
13 | {"word": "讯飞输入法", "pv": 107908, "ratio": 102, "sim": 2645},
14 | {"word": "QQ输入法", "pv": 74912, "ratio": 99, "sim": 2189},
15 | {"word": "百度输入法", "pv": 193624, "ratio": 121, "sim": 2100},
16 | {"word": "搜狗输入法", "pv": 835168, "ratio": 88, "sim": 2050},
17 | {"word": "谷歌输入法", "pv": 14140, "ratio": 96, "sim": 1953},
18 | {"word": "手心输入法", "pv": 19236, "ratio": 97, "sim": 1870},
19 | {"word": "输入法不见了", "pv": 1968, "ratio": 109, "sim": 1705},
20 | {"word": "输入法哪个最好用", "pv": 812, "ratio": 150, "sim": 1567},
21 | {"word": "必应输入法", "pv": 4602, "ratio": 91, "sim": 1522},
22 | {"word": "章鱼输入法", "pv": 18262, "ratio": 97, "sim": 1486},
23 | {"word": "输入法下载", "pv": 34186, "ratio": 91, "sim": 1278},
24 | {"word": "拼音输入法", "pv": 7186, "ratio": 86, "sim": 1009},
25 | {"word": "SHURUFA", "pv": 13418, "ratio": 102, "sim": 924},
26 | {"word": "微软输入法", "pv": 4680, "ratio": 88, "sim": 804},
27 | {"word": "GOOGLE输入法", "pv": 2206, "ratio": 97, "sim": 800},
28 | {"word": "输入法切换不出来", "pv": 15112, "ratio": 85, "sim": 764},
29 | {"word": "章鱼输入法下载", "pv": 8204, "ratio": 135, "sim": 754},
30 | {"word": "讯飞输入法下载", "pv": 5590, "ratio": 106, "sim": 609},
31 | {"word": "输入法搜狗", "pv": 352, "ratio": 132, "sim": 593},
32 | {"word": "输入法皮肤", "pv": 2476, "ratio": 103, "sim": 540},
33 | {"word": "紫光输入法", "pv": 1582, "ratio": 86, "sim": 538},
34 | {"word": "输入法设置", "pv": 1298, "ratio": 75, "sim": 527},
35 | {"word": "搜狗输入法下载安装", "pv": 126182, "ratio": 102, "sim": 521},
36 | {"word": "微软拼音输入法", "pv": 3442, "ratio": 88, "sim": 510},
37 | {"word": "QQ拼音输入法", "pv": 24912, "ratio": 98, "sim": 478},
38 | {"word": "输入发", "pv": 150, "ratio": 125, "sim": 465},
39 | {"word": "SOUGOU输入法", "pv": 264, "ratio": 89, "sim": 452},
40 | {"word": "微软拼音", "pv": 2772, "ratio": 93, "sim": 443},
41 | ]
42 |
43 |
44 | class TestCorrelationChart(unittest.TestCase):
45 |
46 | @chart_base_test(chart_type=ChartType.CORRELATION)
47 | def test_correlation_base(self):
48 | c = (
49 | Correlation()
50 | .set_data(data=[opts.BaseDataOpts(values=TEST_CORRELATION_DATA)])
51 | .set_correlation_spec(
52 | category_field="word",
53 | value_field="sim",
54 | size_field="pv",
55 | size_range=[12, 30],
56 | inner_radius="25%",
57 | outer_radius="95%",
58 | node_point_opts=opts.CorrelationNodePointOpts(
59 | state=opts.BaseStateOpts(
60 | hover_opts=opts.BaseStyleOpts(
61 | line_width=8,
62 | stroke_opacity=0.2,
63 | ),
64 | ),
65 | ),
66 | center_point_opts=opts.CorrelationCenterPointOpts(
67 | state=opts.BaseStateOpts(
68 | hover_opts=opts.BaseStyleOpts(
69 | line_width=8,
70 | stroke_opacity=0.2,
71 | ),
72 | ),
73 | ),
74 | center_label_opts=opts.LabelOpts(
75 | is_visible=True,
76 | position="center",
77 | style_opts=opts.BaseStyleOpts(fill="white", text="输入法"),
78 | ),
79 | label_opts=opts.LabelOpts(
80 | is_visible=True,
81 | position="bottom",
82 | style_opts=opts.BaseStyleOpts(fill="black"),
83 | ),
84 | )
85 | )
86 | return c
87 |
--------------------------------------------------------------------------------
/test/test_datasets.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 | from unittest.mock import patch
4 |
5 | from pyvchart.datasets import (
6 | EXTRA,
7 | FuzzyDict,
8 | register_url,
9 | register_files,
10 | )
11 |
12 |
13 | class TestDatasets(unittest.TestCase):
14 |
15 | @patch("pyvchart.datasets.urllib.request.urlopen")
16 | def test_register_url(self, fake):
17 | current_path = os.path.dirname(__file__)
18 | fake_registry = os.path.join(current_path, "fixtures", "registry.json")
19 | file_name = ["shape-with-internal-borders/an1_hui1_an1_qing4", "js"]
20 | with open(fake_registry, encoding="utf8") as f:
21 | fake.return_value = f
22 | register_url("http://register.url/is/used")
23 | # set maxDiff
24 | self.assertEqual.__self__.maxDiff = None
25 | self.assertEqual(
26 | EXTRA["http://register.url/is/used/js/"],
27 | {
28 | "安庆": file_name,
29 | "English Name": file_name,
30 | },
31 | )
32 |
33 | fake_registry_1 = os.path.join(current_path, "fixtures", "registry_1.json")
34 | with open(fake_registry_1, encoding="utf8") as f:
35 | fake.return_value = f
36 | register_url("http://register.url/is/used")
37 | self.assertEqual(
38 | EXTRA["http://register.url/is/used/"],
39 | {
40 | "安庆": file_name,
41 | "English Name": file_name,
42 | },
43 | )
44 |
45 | def test_register_url_error(self):
46 | try:
47 | register_url("error_asset_url")
48 | except ValueError as err:
49 | self.assertIn(type(err), [ValueError])
50 |
51 | def test_fuzzy_search_dict(self):
52 | fd = FuzzyDict()
53 | fd.update({"我是北京市": [1, 2]})
54 | self.assertEqual(fd["我是北京"], [1, 2])
55 |
56 | def test_fuzzy_search_key_error(self):
57 | with self.assertRaises(KeyError):
58 | fd = FuzzyDict()
59 | fd.cutoff = 0.9
60 | _ = fd["我是北京"]
61 |
62 | def test_register_files(self):
63 | register_files(asset_files={"x": 1})
64 |
65 | def test_type_error_with_non_string_key(self):
66 | fd = FuzzyDict()
67 | fd[1] = "one"
68 | fd[2] = "two"
69 |
70 | result = fd._search("1")
71 | self.assertFalse(result[0]) # Ensure no match found
72 |
73 | def test_type_error_with_non_string_lookfor(self):
74 | fd = FuzzyDict()
75 | fd["one"] = 1
76 | fd["two"] = 2
77 |
78 | with self.assertRaises(KeyError):
79 | _ = fd[1]
80 |
81 | def test_search_stop_on_first_true_highMatch(self):
82 | fuzzy_dict = FuzzyDict()
83 | fuzzy_dict["apple"] = 1
84 | fuzzy_dict["banana"] = 2
85 | fuzzy_dict["banana"] = 3
86 | fuzzy_dict.cutoff = 0.6 # 设置一个默认的截止值用于模糊匹配
87 |
88 | result = fuzzy_dict._search("aple", stop_on_first=True)
89 | self.assertTrue(result[0]) # 找到匹配
90 | self.assertEqual(result[1], "apple") # 最佳匹配的键
91 | self.assertEqual(result[2], 1) # 匹配的值
92 | self.assertGreater(result[3], fuzzy_dict.cutoff) # 匹配度高于截止值
93 |
--------------------------------------------------------------------------------
/test/test_display.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart.render.display import HTML, Javascript
4 |
5 |
6 | class TestDisplay(unittest.TestCase):
7 |
8 | def test_display_html(self):
9 | html_content = "hello world
"
10 | obj = HTML(html_content)
11 | self.assertEqual(obj.data, html_content)
12 | self.assertEqual(obj.__html__(), html_content)
13 |
14 | def test_display_javascript(self):
15 | js_content = "console.log('hello world')"
16 | obj = Javascript(js_content)
17 | self.assertEqual(obj.data, js_content)
18 | self.assertEqual(obj._repr_javascript_(), js_content)
19 |
20 | def test_display_javascript_v1(self):
21 | js_content = "console.log('hello world')"
22 | obj = Javascript(js_content, lib="test lib", css="test css")
23 | self.assertEqual(obj.data, js_content)
24 |
25 | obj_1 = Javascript(
26 | data=js_content,
27 | lib=["lib1", "lib2"],
28 | css=["css1", "css2"],
29 | )
30 | self.assertEqual(obj_1.data, js_content)
31 | self.assertIn(js_content, obj_1._repr_javascript_())
32 |
33 | def test_display_javascript_v2(self):
34 | import ssl
35 |
36 | # ssl._create_default_https_context = ssl._create_unverified_context
37 |
38 | obj = Javascript(
39 | lib=["https://unpkg.com/@visactor/vchart@1.13.5/build/index.min.js"]
40 | )
41 | obj.load_javascript_contents()
42 | self.assertIn(
43 | "vchart",
44 | obj.javascript_contents[
45 | "https://unpkg.com/@visactor/vchart@1.13.5/build/index.min.js"
46 | ],
47 | )
48 |
49 | obj_1 = Javascript(lib=["https://unpkg.com/@visactor/build/index.min.js"])
50 | try:
51 | obj_1.load_javascript_contents()
52 | except RuntimeError:
53 | pass
54 |
--------------------------------------------------------------------------------
/test/test_engine.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | from pyvchart import render_chart
5 | from pyvchart.charts import Bar
6 | from pyvchart.render.engine import RenderEngine, write_utf8_html_file
7 | from pyvchart.datasets import EXTRA, FILENAMES
8 | from pyvchart.globals import CurrentConfig, ChartType
9 |
10 |
11 | class TestEngine(unittest.TestCase):
12 |
13 | def setUp(self):
14 | self.test_file_name = "test_file.html"
15 |
16 | def test_generate_js_link(self):
17 | FILENAMES.update({"existing_dep": ("file_path", "ext")})
18 | EXTRA.update(
19 | {
20 | "https://extra_host.com": {
21 | "dep": ("extra_file_path", "extra_ext"),
22 | },
23 | "https://extra_host.com/css": {
24 | "dep1": ("extra_file_path", "css"),
25 | },
26 | }
27 | )
28 |
29 | # Bar 图
30 | chart = Bar()
31 | chart.js_host = None
32 | chart.js_dependencies.items = [
33 | "existing_dep",
34 | "dep",
35 | "dep1",
36 | ]
37 |
38 | RenderEngine.generate_js_link(chart)
39 |
40 | assert chart.js_host == CurrentConfig.ONLINE_HOST
41 |
42 | # expected_links = [
43 | # "{}file_path.ext".format(CurrentConfig.ONLINE_HOST),
44 | # "https://extra_host.comextra_file_path.extra_ext",
45 | # ]
46 | # assert chart.dependencies == expected_links
47 | #
48 | # expected_css_links = [
49 | # "https://extra_host.com/cssextra_file_path.css",
50 | # ]
51 | # assert chart.css_libs == expected_css_links
52 |
53 | def test_write_utf8_html_file(self):
54 | html_content = "Hello, World! "
55 | write_utf8_html_file(self.test_file_name, html_content)
56 |
57 | with open(self.test_file_name, "r", encoding="utf-8") as file:
58 | written_content = file.read()
59 |
60 | self.assertEqual(written_content, html_content)
61 |
62 | def test_render_chart(self):
63 | spec = {
64 | "type": 'bar',
65 | "data": [
66 | {
67 | "id": 'barData',
68 | "values": [
69 | {"month": 'Monday', "sales": 22},
70 | {"month": 'Tuesday', "sales": 13},
71 | {"month": 'Wednesday', "sales": 25},
72 | {"month": 'Thursday', "sales": 29},
73 | {"month": 'Friday', "sales": 38}
74 | ]
75 | }
76 | ],
77 | "xField": 'month',
78 | "yField": 'sales',
79 | "crosshair": {
80 | "xField": {"visible": True}
81 | }
82 | }
83 | content = render_chart(spec).__html__()
84 | self.assertIn(ChartType.BAR, content)
85 |
86 | def tearDown(self):
87 | if os.path.exists(self.test_file_name):
88 | os.remove(self.test_file_name)
89 |
--------------------------------------------------------------------------------
/test/test_funnel.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Funnel
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_FUNNEL_DATA = [
11 | {"value": 100, "name": "Step1"},
12 | {"value": 80, "name": "Step2"},
13 | {"value": 60, "name": "Step3"},
14 | {"value": 40, "name": "Step4"},
15 | {"value": 20, "name": "Step5"},
16 | ]
17 |
18 |
19 | class TestFunnelChart(unittest.TestCase):
20 |
21 | @chart_base_test(chart_type=ChartType.FUNNEL)
22 | def test_funnel_base(self):
23 | c = (
24 | Funnel()
25 | .set_data(data=[opts.BaseDataOpts(values=TEST_FUNNEL_DATA)])
26 | .set_funnel_spec(
27 | category_field="name",
28 | value_field="value",
29 | label_opts=opts.LabelOpts(is_visible=True),
30 | )
31 | .set_global_options(
32 | legend_opts=opts.BaseLegendOpts(
33 | is_visible=True,
34 | orient="bottom",
35 | ),
36 | )
37 | )
38 | return c
39 |
--------------------------------------------------------------------------------
/test/test_funnel3d.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Funnel3D
5 | from pyvchart.commons.utils import JsCode
6 | from pyvchart.globals import ChartType
7 |
8 | from test import chart_base_test
9 |
10 |
11 | TEST_FUNNEL3D_DATA = [
12 | {"value": 100, "name": "Step1"},
13 | {"value": 80, "name": "Step2"},
14 | {"value": 60, "name": "Step3"},
15 | {"value": 40, "name": "Step4"},
16 | {"value": 20, "name": "Step5"},
17 | ]
18 |
19 |
20 | class TestFunnel3DChart(unittest.TestCase):
21 |
22 | @chart_base_test(chart_type=ChartType.FUNNEL3D)
23 | def test_funnel3d_base(self):
24 | c = (
25 | Funnel3D(
26 | render_opts=opts.RenderOpts(
27 | is_disable_dirty_bounds=True,
28 | options3d_opts=opts.Render3DOpts(
29 | is_enable=True,
30 | center={"dx": 100, "dy": 100},
31 | ),
32 | ),
33 | )
34 | .set_data(data=[opts.BaseDataOpts(id_="data", values=TEST_FUNNEL3D_DATA)])
35 | .set_funnel3d_spec(
36 | category_field="name",
37 | value_field="value",
38 | min_size=50,
39 | max_size=400,
40 | label_opts=opts.LabelOpts(is_visible=True, is_support3d=True),
41 | )
42 | .set_global_options(
43 | padding_opts=opts.PaddingOpts(pos_top=30),
44 | legend_opts=opts.BaseLegendOpts(is_visible=True, orient="bottom"),
45 | )
46 | )
47 | return c
48 |
--------------------------------------------------------------------------------
/test/test_gauge.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Gauge
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 | TEST_GAUGE_DATA = [{"type": "目标A", "value": 0.6}]
10 |
11 |
12 | class TestGaugeChart(unittest.TestCase):
13 |
14 | @chart_base_test(chart_type=ChartType.GAUGE)
15 | def test_gauge_base(self):
16 | c = (
17 | Gauge()
18 | .set_data(data=[opts.BaseDataOpts(id_="id0", values=TEST_GAUGE_DATA)])
19 | .set_gauge_spec(
20 | category_field="type",
21 | value_field="value",
22 | outer_radius=0.8,
23 | inner_radius=0.5,
24 | start_angle=-180,
25 | end_angle=0,
26 | )
27 | )
28 | return c
29 |
--------------------------------------------------------------------------------
/test/test_histogram.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Histogram
5 | from pyvchart.commons.utils import JsCode
6 | from pyvchart.globals import ChartType
7 |
8 | from test import chart_base_test
9 |
10 |
11 | TEST_HISTOGRAM_DATA = [
12 | {"x0": -400, "x1": -380, "frequency": 0},
13 | {"x0": -380, "x1": -360, "frequency": 4},
14 | {"x0": -360, "x1": -340, "frequency": 7},
15 | {"x0": -340, "x1": -320, "frequency": 7},
16 | {"x0": -320, "x1": -300, "frequency": 18},
17 | {"x0": -300, "x1": -280, "frequency": 30},
18 | {"x0": -280, "x1": -260, "frequency": 33},
19 | {"x0": -260, "x1": -240, "frequency": 80},
20 | {"x0": -240, "x1": -220, "frequency": 98},
21 | {"x0": -220, "x1": -200, "frequency": 124},
22 | {"x0": -200, "x1": -180, "frequency": 161},
23 | {"x0": -180, "x1": -160, "frequency": 176},
24 | {"x0": -160, "x1": -140, "frequency": 227},
25 | {"x0": -140, "x1": -120, "frequency": 276},
26 | {"x0": -120, "x1": -100, "frequency": 321},
27 | {"x0": -100, "x1": -80, "frequency": 452},
28 | {"x0": -80, "x1": -60, "frequency": 441},
29 | {"x0": -60, "x1": -40, "frequency": 505},
30 | {"x0": -40, "x1": -20, "frequency": 521},
31 | {"x0": -20, "x1": 0, "frequency": 733},
32 | {"x0": 0, "x1": 20, "frequency": 892},
33 | {"x0": 20, "x1": 40, "frequency": 362},
34 | {"x0": 40, "x1": 60, "frequency": 267},
35 | {"x0": 60, "x1": 80, "frequency": 223},
36 | {"x0": 80, "x1": 100, "frequency": 157},
37 | {"x0": 100, "x1": 120, "frequency": 170},
38 | {"x0": 120, "x1": 140, "frequency": 124},
39 | {"x0": 140, "x1": 160, "frequency": 112},
40 | {"x0": 160, "x1": 180, "frequency": 73},
41 | {"x0": 180, "x1": 200, "frequency": 80},
42 | {"x0": 200, "x1": 220, "frequency": 49},
43 | {"x0": 220, "x1": 240, "frequency": 33},
44 | {"x0": 240, "x1": 260, "frequency": 30},
45 | {"x0": 260, "x1": 280, "frequency": 21},
46 | {"x0": 280, "x1": 300, "frequency": 9},
47 | {"x0": 300, "x1": 320, "frequency": 13},
48 | {"x0": 320, "x1": 340, "frequency": 11},
49 | {"x0": 340, "x1": 360, "frequency": 5},
50 | {"x0": 360, "x1": 380, "frequency": 4},
51 | {"x0": 380, "x1": 400, "frequency": 4},
52 | {"x0": 400, "x1": 420, "frequency": 2},
53 | {"x0": 420, "x1": 440, "frequency": 8},
54 | {"x0": 440, "x1": 460, "frequency": 2},
55 | {"x0": 460, "x1": 480, "frequency": 3},
56 | {"x0": 480, "x1": 500, "frequency": 10},
57 | {"x0": 500, "x1": 520, "frequency": 7},
58 | {"x0": 520, "x1": 540, "frequency": 14},
59 | {"x0": 540, "x1": 560, "frequency": 6},
60 | {"x0": 560, "x1": 580, "frequency": 1},
61 | {"x0": 580, "x1": 600, "frequency": 3},
62 | {"x0": 600, "x1": 620, "frequency": 0},
63 | {"x0": 620, "x1": 640, "frequency": 6},
64 | {"x0": 640, "x1": 660, "frequency": 5},
65 | {"x0": 660, "x1": 680, "frequency": 3},
66 | {"x0": 680, "x1": 700, "frequency": 2},
67 | {"x0": 700, "x1": 720, "frequency": 0},
68 | ]
69 |
70 |
71 | class TestHistogramChart(unittest.TestCase):
72 |
73 | @chart_base_test(chart_type=ChartType.HISTOGRAM)
74 | def test_histogram_base(self):
75 | c = (
76 | Histogram()
77 | .set_data(data=[opts.BaseDataOpts(values=TEST_HISTOGRAM_DATA)])
78 | .set_histogram_spec(
79 | x2_field="x1",
80 | bar_opts=opts.BarOpts(
81 | style=opts.BaseStyleOpts(stroke="white", line_width=1)
82 | ),
83 | )
84 | .set_xy_field(x_field_name="x0", y_field_name="frequency")
85 | .set_global_options(
86 | title_opts=opts.TitleOpts(text="Arrival Time Histogram"),
87 | tooltip_opts=opts.TooltipOpts(
88 | is_visible=True,
89 | mark_opts=opts.TooltipCustomOpts(
90 | title_value="frequency",
91 | content=[
92 | opts.TooltipCustomStyleOpts(
93 | key=JsCode("datum => datum['x0'] + '~' + datum['x1']"),
94 | value=JsCode("datum => datum['frequency']"),
95 | )
96 | ],
97 | ),
98 | ),
99 | )
100 | )
101 | return c
102 |
--------------------------------------------------------------------------------
/test/test_line.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Line
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_LINE_DATA = [
11 | {"date": "2023-01-01", "type": "Product A", "value": 99.9},
12 | {"date": "2023-01-01", "type": "Product B", "value": 96.6},
13 | {"date": "2023-01-01", "type": "Product C", "value": 96.2},
14 | {"date": "2023-01-02", "type": "Product A", "value": 96.7},
15 | {"date": "2023-01-02", "type": "Product B", "value": 91.1},
16 | {"date": "2023-01-02", "type": "Product C", "value": 93.4},
17 | {"date": "2023-01-03", "type": "Product A", "value": 100.2},
18 | {"date": "2023-01-03", "type": "Product B", "value": 99.4},
19 | {"date": "2023-01-03", "type": "Product C", "value": 91.7},
20 | {"date": "2023-01-04", "type": "Product A", "value": 104.7},
21 | {"date": "2023-01-04", "type": "Product B", "value": 108.1},
22 | {"date": "2023-01-04", "type": "Product C", "value": 93.1},
23 | {"date": "2023-01-05", "type": "Product A", "value": 95.6},
24 | {"date": "2023-01-05", "type": "Product B", "value": 96},
25 | {"date": "2023-01-05", "type": "Product C", "value": 92.3},
26 | {"date": "2023-01-06", "type": "Product A", "value": 95.6},
27 | {"date": "2023-01-06", "type": "Product B", "value": 89.1},
28 | {"date": "2023-01-06", "type": "Product C", "value": 92.5},
29 | {"date": "2023-01-07", "type": "Product A", "value": 95.3},
30 | {"date": "2023-01-07", "type": "Product B", "value": 89.2},
31 | {"date": "2023-01-07", "type": "Product C", "value": 95.7},
32 | {"date": "2023-01-08", "type": "Product A", "value": 96.1},
33 | {"date": "2023-01-08", "type": "Product B", "value": 97.6},
34 | {"date": "2023-01-08", "type": "Product C", "value": 99.9},
35 | {"date": "2023-01-09", "type": "Product A", "value": 96.1},
36 | {"date": "2023-01-09", "type": "Product B", "value": 100.6},
37 | {"date": "2023-01-09", "type": "Product C", "value": 103.8},
38 | {"date": "2023-01-10", "type": "Product A", "value": 101.6},
39 | {"date": "2023-01-10", "type": "Product B", "value": 108.3},
40 | {"date": "2023-01-10", "type": "Product C", "value": 108.9},
41 | ]
42 |
43 |
44 | class TestLineChart(unittest.TestCase):
45 |
46 | @chart_base_test(chart_type=ChartType.LINE)
47 | def test_line_base(self):
48 | c = (
49 | Line()
50 | .set_data(data=[opts.BaseDataOpts(values=TEST_LINE_DATA)])
51 | .set_line_spec(point_opts=opts.PointOpts(is_visible=False))
52 | .set_xy_field(x_field_name="date", y_field_name="date")
53 | .set_global_options(series_field="type")
54 | )
55 | return c
56 |
--------------------------------------------------------------------------------
/test/test_linear_progress.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import LinearProgress
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_LINEAR_PROGRESS_DATA = [
11 | {"type": "Tradition Industries", "value": 0.795, "text": "79.5%"},
12 | {"type": "Business Companies", "value": 0.25, "text": "25%"},
13 | {"type": "Customer-facing Companies", "value": 0.065, "text": "6.5%"},
14 | ]
15 |
16 |
17 | class TestLinearProgressChart(unittest.TestCase):
18 |
19 | @chart_base_test(chart_type=ChartType.LINEAR_PROGRESS)
20 | def test_linear_progress_base(self):
21 | c = (
22 | LinearProgress()
23 | .set_data(
24 | data=[opts.BaseDataOpts(id_="id0", values=TEST_LINEAR_PROGRESS_DATA)]
25 | )
26 | .set_xy_field(x_field_name="value", y_field_name="type")
27 | .set_linear_progress_spec(
28 | direction="horizontal",
29 | corner_radius=20,
30 | band_width=30,
31 | )
32 | .set_global_options(
33 | series_field="type",
34 | axes_opts=[
35 | opts.AxesBandOpts(
36 | base_axes_opts=opts.BaseAxesOpts(
37 | orient="left",
38 | label=opts.AxesLabelOpts(is_visible=True),
39 | domain_line=opts.AxesDomainLineOpts(is_visible=False),
40 | tick=opts.AxesTickOpts(is_visible=False),
41 | )
42 | ),
43 | opts.AxesLinearOpts(
44 | base_axes_opts=opts.BaseAxesOpts(
45 | orient="bottom",
46 | label=opts.AxesLabelOpts(is_visible=True),
47 | ),
48 | is_visible=False,
49 | ),
50 | ],
51 | )
52 | )
53 | return c
54 |
--------------------------------------------------------------------------------
/test/test_liquid.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Liquid
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_LIQUID_DATA = [{"value": 0.3}]
11 |
12 |
13 | class TestLiquidChart(unittest.TestCase):
14 |
15 | @chart_base_test(chart_type=ChartType.LIQUID)
16 | def test_liquid_base(self):
17 | c = (
18 | Liquid()
19 | .set_data(data=[opts.BaseDataOpts(id_="data", values=TEST_LIQUID_DATA)])
20 | .set_liquid_spec(value_field="value")
21 | .set_global_options(
22 | indicator_opts=opts.IndicatorOpts(
23 | is_visible=True,
24 | title_opts=opts.IndicatorTitleOpts(
25 | is_visible=True,
26 | style=opts.BaseTitleTextStyleOpts(text="进度"),
27 | ),
28 | content_opts=opts.IndicatorContentOpts(
29 | is_visible=True,
30 | style=opts.BaseTitleTextStyleOpts(
31 | text="30%",
32 | base_style_opts=opts.BaseStyleOpts(fill="black"),
33 | ),
34 | ),
35 | ),
36 | )
37 | )
38 | return c
39 |
--------------------------------------------------------------------------------
/test/test_mosaic.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Mosaic
5 | from pyvchart.commons.utils import JsCode
6 | from pyvchart.globals import ChartType
7 |
8 | from test import chart_base_test
9 |
10 |
11 | TEST_MOSAIC_DATA = [
12 | {"State": "WY", "Age": "Under 5 Years", "Population": 25635},
13 | {"State": "WY", "Age": "5 to 13 Years", "Population": 1890},
14 | {"State": "WY", "Age": "14 to 17 Years", "Population": 9314},
15 | {"State": "DC", "Age": "Under 5 Years", "Population": 30352},
16 | {"State": "DC", "Age": "5 to 13 Years", "Population": 20439},
17 | {"State": "DC", "Age": "14 to 17 Years", "Population": 10225},
18 | {"State": "VT", "Age": "Under 5 Years", "Population": 38253},
19 | {"State": "VT", "Age": "5 to 13 Years", "Population": 42538},
20 | {"State": "VT", "Age": "14 to 17 Years", "Population": 15757},
21 | {"State": "ND", "Age": "Under 5 Years", "Population": 51896},
22 | {"State": "ND", "Age": "5 to 13 Years", "Population": 67358},
23 | {"State": "ND", "Age": "14 to 17 Years", "Population": 18794},
24 | {"State": "AK", "Age": "Under 5 Years", "Population": 72083},
25 | {"State": "AK", "Age": "5 to 13 Years", "Population": 85640},
26 | {"State": "AK", "Age": "14 to 17 Years", "Population": 22153},
27 | ]
28 |
29 |
30 | class TestMosaicChart(unittest.TestCase):
31 |
32 | @chart_base_test(chart_type=ChartType.MOSAIC)
33 | def test_mosaic_base(self):
34 | c = (
35 | Mosaic()
36 | .set_data(data=[opts.BaseDataOpts(id_="barData", values=TEST_MOSAIC_DATA)])
37 | .set_xy_field(x_field_name="State", y_field_name="Population")
38 | .set_mosaic_spec(
39 | label_opts=[
40 | opts.LabelOpts(
41 | is_visible=True,
42 | position="bottom",
43 | style_opts=opts.BaseStyleOpts(fill="#333"),
44 | filter_by_group_opts=opts.LabelFilterByGroupOpts(
45 | field="State",
46 | type_="min",
47 | ),
48 | format_method=JsCode(
49 | "(value, datum, ctx) => { return datum['State']; }"
50 | ),
51 | overlap_opts=False,
52 | ),
53 | opts.LabelOpts(
54 | is_visible=True,
55 | position="top",
56 | style_opts=opts.BaseStyleOpts(fill="#333"),
57 | filter_by_group_opts=opts.LabelFilterByGroupOpts(
58 | field="State",
59 | type_="max",
60 | ),
61 | format_method=JsCode(
62 | "(value, datum, ctx) => {"
63 | "return `${datum['__VCHART_STACK_END']} "
64 | "(${((datum['__VCHART_MOSAIC_CAT_END_PERCENT'] - "
65 | "datum['__VCHART_MOSAIC_CAT_START_PERCENT']) * 100"
66 | ").toFixed(0)}% )`;}"
67 | ),
68 | overlap_opts=False,
69 | ),
70 | opts.LabelOpts(
71 | is_visible=True,
72 | position="center",
73 | smart_invert=True,
74 | ),
75 | ]
76 | )
77 | .set_global_options(
78 | series_field="Age",
79 | is_percent=True,
80 | legend_opts=[opts.BaseLegendOpts(is_visible=True)],
81 | axes_opts=[
82 | opts.BaseAxesOpts(
83 | orient="left",
84 | label=opts.AxesLabelOpts(
85 | format_method=JsCode(
86 | "val => { return `${(val * 100).toFixed(2)}%`; }",
87 | ),
88 | ),
89 | ),
90 | opts.BaseAxesOpts(
91 | orient="right",
92 | label=opts.AxesLabelOpts(is_visible=False),
93 | ),
94 | ],
95 | )
96 | )
97 | return c
98 |
--------------------------------------------------------------------------------
/test/test_pictogram.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import urllib.request
3 |
4 | from pyvchart import options as opts
5 | from pyvchart.charts import Pictogram
6 | from pyvchart.commons.utils import JsCode
7 | from pyvchart.globals import ChartType
8 |
9 | from test import chart_base_test
10 |
11 |
12 | TEST_PICTOGRAM_DATA = [
13 | {"name": "Yes", "value": "Love This"},
14 | {"name": "So-so"},
15 | {"name": "Forbidden"},
16 | {"name": "Horror"},
17 | ]
18 |
19 | req = urllib.request.Request(
20 | url="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/pictogram/cat.svg"
21 | )
22 | resp = urllib.request.urlopen(req)
23 | TEST_SVG_CONTENT = resp.read().decode("utf-8")
24 |
25 |
26 | class TestPictogramChart(unittest.TestCase):
27 |
28 | @chart_base_test(chart_type=ChartType.PICTOGRAM)
29 | def test_pictogram_base(self):
30 | c = (
31 | Pictogram()
32 | .set_data(data=[opts.BaseDataOpts(id_="data", values=TEST_PICTOGRAM_DATA)])
33 | .register_svg(name="cat", svg_path=TEST_SVG_CONTENT)
34 | .set_pictogram_spec(
35 | name_field="name",
36 | value_field="value",
37 | svg="cat",
38 | pictogram_opts=opts.PictogramOpts(
39 | style=opts.BaseStyleOpts(
40 | fill={
41 | "scale": "color",
42 | "field": "name",
43 | },
44 | ),
45 | state=opts.BaseStateOpts(
46 | active_opts=opts.BaseStyleOpts(
47 | fill_opacity=0.8,
48 | stroke={
49 | "scale": "color",
50 | "field": "name",
51 | },
52 | line_width=2,
53 | ),
54 | hover_opts=opts.BaseStyleOpts(
55 | fill_opacity=0.8,
56 | stroke={
57 | "scale": "color",
58 | "field": "name",
59 | },
60 | line_width=2,
61 | ),
62 | ),
63 | ),
64 | )
65 | .set_global_options(
66 | series_field="name",
67 | color_opts=opts.ColorOpts(
68 | specified={
69 | "Yes": "#009A00",
70 | "So-so": "#FEB202",
71 | "Forbidden": "#FE3E00",
72 | "Horror": "#FE2B09",
73 | "undefined": "white",
74 | }
75 | ),
76 | interaction_opts=[
77 | opts.InteractionOpts(
78 | type_="element-active-by-legend",
79 | filter_field="name",
80 | )
81 | ],
82 | region_opts=[
83 | opts.RegionOpts(roam={"blank": True}),
84 | ],
85 | title_opts=opts.TitleOpts(text="Cat Stroking For Beginners"),
86 | legend_opts=opts.BaseLegendOpts(orient="top", is_filter=False),
87 | )
88 | )
89 | return c
90 |
--------------------------------------------------------------------------------
/test/test_pie.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Pie
5 | from pyvchart.commons.utils import JsCode
6 | from pyvchart.globals import ChartType
7 |
8 | from test import chart_base_test
9 |
10 | TEST_PIE_DATA = [
11 | {"type": "oxygen", "value": "46.60"},
12 | {"type": "silicon", "value": "27.72"},
13 | {"type": "aluminum", "value": "8.13"},
14 | {"type": "iron", "value": "5"},
15 | {"type": "calcium", "value": "3.63"},
16 | {"type": "sodium", "value": "2.83"},
17 | {"type": "potassium", "value": "2.59"},
18 | {"type": "others", "value": "3.5"},
19 | ]
20 |
21 |
22 | class TestPieChart(unittest.TestCase):
23 |
24 | @chart_base_test(chart_type=ChartType.PIE)
25 | def test_pie_base(self):
26 | c = (
27 | Pie()
28 | .set_data(data=[opts.BaseDataOpts(values=TEST_PIE_DATA)])
29 | .set_pie_spec(
30 | outer_radius=0.8,
31 | value_field="value",
32 | category_field="type",
33 | label_opts=opts.LabelOpts(is_visible=True),
34 | )
35 | .set_global_options(
36 | title_opts=opts.TitleOpts(
37 | is_visible=True,
38 | text="Statistics of Surface Element Content",
39 | ),
40 | legend_opts=opts.BaseLegendOpts(
41 | is_visible=True,
42 | orient="left",
43 | ),
44 | tooltip_opts=opts.TooltipOpts(
45 | mark_opts=opts.TooltipCustomOpts(
46 | content=[
47 | opts.TooltipCustomStyleOpts(
48 | key=JsCode("datum => datum['type']"),
49 | value=JsCode("datum => datum['value'] + '%'"),
50 | )
51 | ]
52 | )
53 | ),
54 | )
55 | )
56 | return c
57 |
--------------------------------------------------------------------------------
/test/test_radar.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Radar
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_RADAR_DATA = [
11 | {"key": "Strength", "value": 5},
12 | {"key": "Speed", "value": 5},
13 | {"key": "Shooting", "value": 3},
14 | {"key": "Endurance", "value": 5},
15 | {"key": "Precision", "value": 5},
16 | {"key": "Growth", "value": 5},
17 | ]
18 |
19 |
20 | class TestRadarChart(unittest.TestCase):
21 |
22 | @chart_base_test(chart_type=ChartType.RADAR)
23 | def test_radar_base(self):
24 | c = (
25 | Radar()
26 | .set_data(data=[opts.BaseDataOpts(values=TEST_RADAR_DATA)])
27 | .set_radar_spec(
28 | category_field="key",
29 | value_field="value",
30 | point_opts=opts.PointOpts(is_visible=False),
31 | area_opts=opts.AreaOpts(
32 | is_visible=True,
33 | state=opts.BaseStateOpts(
34 | hover_opts=opts.BaseStyleOpts(fill_opacity=0.5)
35 | ),
36 | ),
37 | line_opts=opts.LineOpts(style=opts.BaseStyleOpts(line_width=4)),
38 | )
39 | .set_global_options(
40 | axes_opts=[
41 | opts.AxesLinearOpts(
42 | min_=0,
43 | max_=8,
44 | base_axes_opts=opts.BaseAxesOpts(
45 | orient="radius",
46 | z_index=100,
47 | domain_line=opts.AxesDomainLineOpts(is_visible=False),
48 | label=opts.AxesLabelOpts(
49 | is_visible=True,
50 | space=0,
51 | style_opts=opts.BaseStyleOpts(
52 | stroke="#fff",
53 | line_width=4,
54 | ),
55 | ),
56 | grid=opts.AxesGridOpts(
57 | is_smooth=False,
58 | style_opts=opts.BaseStyleOpts(
59 | line_dash=[0],
60 | ),
61 | ),
62 | ),
63 | ),
64 | opts.AxesBandOpts(
65 | base_axes_opts=opts.BaseAxesOpts(
66 | orient="angle",
67 | z_index=50,
68 | tick=opts.AxesTickOpts(is_visible=False),
69 | domain_line=opts.AxesDomainLineOpts(is_visible=False),
70 | label=opts.AxesLabelOpts(space=20),
71 | grid=opts.AxesGridOpts(
72 | style_opts=opts.BaseStyleOpts(
73 | line_dash=[0],
74 | )
75 | ),
76 | ),
77 | ),
78 | ]
79 | )
80 | )
81 | return c
82 |
--------------------------------------------------------------------------------
/test/test_range_area.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import RangeArea
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_RANGE_AREA_DATA = [
11 | {"type": "Category One", "min": 76, "max": 100},
12 | {"type": "Category Two", "min": 56, "max": 108},
13 | {"type": "Category Three", "min": 38, "max": 129},
14 | {"type": "Category Four", "min": 58, "max": 155},
15 | {"type": "Category Five", "min": 45, "max": 120},
16 | {"type": "Category Six", "min": 23, "max": 99},
17 | {"type": "Category Seven", "min": 18, "max": 56},
18 | {"type": "Category Eight", "min": 18, "max": 34},
19 | ]
20 |
21 |
22 | class TestRangeAreaChart(unittest.TestCase):
23 |
24 | @chart_base_test(chart_type=ChartType.RANGE_AREA)
25 | def test_range_area_base(self):
26 | c = (
27 | RangeArea()
28 | .set_data(data=[opts.BaseDataOpts(values=TEST_RANGE_AREA_DATA)])
29 | .set_xy_field(x_field_name="type", y_field_name=["min", "max"])
30 | .set_range_area_spec(
31 | area_opts=opts.AreaOpts(style=opts.BaseStyleOpts(fill_opacity=0.15)),
32 | )
33 | .set_global_options(
34 | is_stack=False,
35 | axes_opts=[
36 | opts.AxesLinearOpts(
37 | base_axes_opts=opts.BaseAxesOpts(
38 | orient="left",
39 | label=opts.AxesLabelOpts(is_visible=True),
40 | ),
41 | ),
42 | ],
43 | crosshair_opts=opts.CrossHairOpts(
44 | x_field=opts.CrossHairFieldOpts(is_visible=True),
45 | ),
46 | )
47 | )
48 | return c
49 |
--------------------------------------------------------------------------------
/test/test_range_column.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import RangeColumn
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_RANGE_COLUMN_DATA = [
11 | {"type": "Category One", "min": 76, "max": 100},
12 | {"type": "Category Two", "min": 56, "max": 108},
13 | {"type": "Category Three", "min": 38, "max": 129},
14 | {"type": "Category Four", "min": 58, "max": 155},
15 | {"type": "Category Five", "min": 45, "max": 120},
16 | {"type": "Category Six", "min": 23, "max": 99},
17 | {"type": "Category Seven", "min": 18, "max": 56},
18 | {"type": "Category Eight", "min": 18, "max": 34},
19 | ]
20 |
21 |
22 | class TestRangeColumnChart(unittest.TestCase):
23 |
24 | @chart_base_test(chart_type=ChartType.RANGE_COLUMN)
25 | def test_range_column_base(self):
26 | c = (
27 | RangeColumn()
28 | .set_data(data=[opts.BaseDataOpts(values=TEST_RANGE_COLUMN_DATA)])
29 | .set_range_column_spec(
30 | direction="horizontal",
31 | label_opts=opts.LabelOpts(is_visible=True),
32 | )
33 | .set_xy_field(x_field_name=["min", "max"], y_field_name="type")
34 | )
35 | return c
36 |
--------------------------------------------------------------------------------
/test/test_rose.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Rose
5 | from pyvchart.globals import ChartType
6 |
7 | from test import chart_base_test
8 |
9 |
10 | TEST_ROSE_DATA = [
11 | {"value": "159", "type": "Tradition Industries"},
12 | {"value": "50", "type": "Business Companies"},
13 | {"value": "13", "type": "Customer-facing Companies"},
14 | ]
15 |
16 |
17 | class TestRoseChart(unittest.TestCase):
18 |
19 | @chart_base_test(chart_type=ChartType.ROSE)
20 | def test_rose_base(self):
21 | c = (
22 | Rose()
23 | .set_data(data=[opts.BaseDataOpts(values=TEST_ROSE_DATA)])
24 | .set_rose_spec(
25 | outer_radius=0.8,
26 | inner_radius=0.2,
27 | category_field="type",
28 | value_field="value",
29 | label_opts=opts.LabelOpts(
30 | is_visible=True,
31 | ),
32 | )
33 | .set_global_options(
34 | series_field="type",
35 | )
36 | )
37 | return c
38 |
--------------------------------------------------------------------------------
/test/test_utils.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart.commons import utils
4 | from pyvchart.datasets import EXTRA
5 |
6 |
7 | class TestUtils(unittest.TestCase):
8 |
9 | def test_utils_produce_require_dict_with_extra(self):
10 | global EXTRA
11 | EXTRA["https://api.baidu.com"] = {
12 | "https://api.baidu.com/test.min": ["https://api.baidu.com/test.min", "css"]
13 | }
14 | cfg_0 = utils.produce_require_dict(
15 | utils.OrderedSet("https://api.baidu.com/test.min"),
16 | "https://example.com",
17 | )
18 | self.assertEqual(cfg_0["libraries"], ["'https://api.baidu.com/test.min'"])
19 |
20 | def test_js_code(self):
21 | fn = "function() { console.log('test_js_code') }"
22 | js_code = utils.JsCode(fn)
23 | self.assertEqual(js_code.js_code, "--x_x--0_0--{}--x_x--0_0--".format(fn))
24 |
25 | def test_ordered_set(self):
26 | s = utils.OrderedSet()
27 | s.add("a", "b", "c")
28 | self.assertEqual(s.items, ["a", "b", "c"])
29 |
30 | def test_utils_remove_key_with_none_value(self):
31 | mock_data = [1, 2, 3]
32 | list_res = utils.remove_key_with_none_value(mock_data)
33 | assert list_res == mock_data
34 |
35 | mock_data_none = None
36 | none_res = utils.remove_key_with_none_value(mock_data_none)
37 | assert none_res == mock_data_none
38 |
39 | def test_utils_remove_key_with_none_value_raise_value_error(self):
40 | import numpy as np
41 | import pandas as pd
42 |
43 | mock_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
44 | mock_numpy_data = np.array(mock_data)
45 | tmp_df = pd.DataFrame({"x": mock_data})
46 | mock_series_data = tmp_df["x"]
47 | try:
48 | utils.remove_key_with_none_value({"data": mock_numpy_data})
49 | except ValueError:
50 | pass
51 |
52 | try:
53 | utils.remove_key_with_none_value({"data": mock_series_data})
54 | except ValueError:
55 | pass
56 |
57 | def test_clean_dict_empty_string_removes_key(self):
58 | input_dict = {"key": ""}
59 | result = dict(utils.remove_key_with_none_value(input_dict))
60 | self.assertNotIn("key", result)
61 |
62 | def test_clean_array_value_is_list_tuple_set(self):
63 | input_ = {"x": [[1, 2, 3]]}
64 | result = utils.remove_key_with_none_value(input_)
65 | self.assertEqual(result["x"], [[1, 2, 3]])
66 |
67 | input_ = {"x": [(1, 2, 3)]}
68 | result = utils.remove_key_with_none_value(input_)
69 | self.assertEqual(result["x"], [[1, 2, 3]])
70 |
71 | input_ = {"x": [{1, 2, 3}]}
72 | result = utils.remove_key_with_none_value(input_)
73 | self.assertEqual(result["x"], [[1, 2, 3]])
74 |
--------------------------------------------------------------------------------
/test/test_venn.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Venn
5 | from pyvchart.commons.utils import JsCode
6 | from pyvchart.globals import ChartType
7 |
8 | from test import chart_base_test
9 |
10 |
11 | TEST_VENN_DATA = [
12 | {"sets": ["A"], "value": 8},
13 | {"sets": ["B"], "value": 10},
14 | {"sets": ["C"], "value": 12},
15 | {"sets": ["A", "B"], "value": 4},
16 | {"sets": ["A", "C"], "value": 4},
17 | {"sets": ["B", "C"], "value": 4},
18 | {"sets": ["A", "B", "C"], "value": 2},
19 | ]
20 |
21 |
22 | class TestVennChart(unittest.TestCase):
23 |
24 | @chart_base_test(chart_type=ChartType.VENN)
25 | def test_venn_chart(self):
26 | c = (
27 | Venn()
28 | .set_data(data=[opts.BaseDataOpts(values=TEST_VENN_DATA)])
29 | .set_venn_spec(
30 | category_field="sets",
31 | value_field="value",
32 | )
33 | .set_global_options(
34 | series_field="sets",
35 | legend_opts=[
36 | opts.BaseLegendOpts(
37 | is_visible=True, position="middle", orient="bottom"
38 | ),
39 | ],
40 | )
41 | )
42 | return c
43 |
--------------------------------------------------------------------------------
/test/test_waterfall.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import Waterfall
5 | from pyvchart.commons.utils import JsCode
6 | from pyvchart.globals import ChartType
7 |
8 | from test import chart_base_test
9 |
10 |
11 | TEST_WATERFALL_DATA = [
12 | {"x": "Feb.4", "total": True, "value": 45},
13 | {"x": "Feb.11", "y": -5},
14 | {"x": "Feb.20", "y": 2},
15 | {"x": "Feb.25", "y": -2},
16 | {"x": "Mar.4", "y": 2},
17 | {"x": "Mar.11", "y": 2},
18 | {"x": "Mar.19", "y": -2},
19 | {"x": "Mar.26", "y": 1},
20 | {"x": "Apr.1", "y": 1},
21 | {"x": "Apr.8", "y": 1},
22 | {"x": "Apr.15", "y": 2},
23 | {"x": "Apr.22", "y": 1},
24 | {"x": "Apr.29", "y": -2},
25 | {"x": "May.6", "y": -1},
26 | {"x": '"total"', "total": True},
27 | ]
28 |
29 |
30 | class TestWaterfallChart(unittest.TestCase):
31 |
32 | @chart_base_test(chart_type=ChartType.WATERFALL)
33 | def test_waterfall_base(self):
34 | c = (
35 | Waterfall()
36 | .set_data(data=[opts.BaseDataOpts(id_="id0", values=TEST_WATERFALL_DATA)])
37 | .set_xy_field(x_field_name="x", y_field_name="y")
38 | .set_waterfall_spec(
39 | stack_label_opts=opts.LabelOpts(
40 | value_type="absolute",
41 | format_method=JsCode("text => { return text + '%'; }"),
42 | ),
43 | total_opts=opts.WaterfallTotalFieldOpts(
44 | tag_field="total",
45 | value_field="value",
46 | ),
47 | )
48 | .set_global_options(
49 | legend_opts=opts.BaseLegendOpts(is_visible=True, orient="bottom"),
50 | axes_opts=[
51 | opts.AxesLinearOpts(
52 | min_=30,
53 | max_=50,
54 | base_axes_opts=opts.BaseAxesOpts(
55 | orient="left",
56 | title=opts.TitleOpts(is_visible=True, text="favorability"),
57 | label=opts.AxesLabelOpts(
58 | format_method=JsCode("v => { return v + '%'; }")
59 | ),
60 | ),
61 | ),
62 | opts.AxesBandOpts(
63 | base_axes_opts=opts.BaseAxesOpts(
64 | orient="bottom",
65 | label=opts.AxesLabelOpts(is_visible=True),
66 | title=opts.TitleOpts(is_visible=True, text="data"),
67 | ),
68 | padding_inner=0.4,
69 | ),
70 | ],
71 | )
72 | )
73 | return c
74 |
--------------------------------------------------------------------------------
/test/test_wordcloud3d.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from pyvchart import options as opts
4 | from pyvchart.charts import WordCloud3D
5 | from pyvchart.commons.utils import JsCode
6 | from pyvchart.globals import ChartType
7 |
8 | from test import chart_base_test
9 |
10 |
11 | TEST_WORDCLOUD3D_DATA = [
12 | {"challenge_name": "刘浩存", "sum_count": 957},
13 | {"challenge_name": "刘昊然", "sum_count": 942},
14 | {"challenge_name": "喜欢", "sum_count": 842},
15 | {"challenge_name": "真的", "sum_count": 828},
16 | {"challenge_name": "四海", "sum_count": 665},
17 | {"challenge_name": "好看", "sum_count": 627},
18 | {"challenge_name": "评论", "sum_count": 574},
19 | {"challenge_name": "好像", "sum_count": 564},
20 | {"challenge_name": "沈腾", "sum_count": 554},
21 | {"challenge_name": "不像", "sum_count": 540},
22 | {"challenge_name": "多少钱", "sum_count": 513},
23 | {"challenge_name": "韩寒", "sum_count": 513},
24 | {"challenge_name": "不知道", "sum_count": 499},
25 | {"challenge_name": "感觉", "sum_count": 499},
26 | {"challenge_name": "尹正", "sum_count": 495},
27 | {"challenge_name": "不看", "sum_count": 487},
28 | {"challenge_name": "奥特之父", "sum_count": 484},
29 | {"challenge_name": "阿姨", "sum_count": 482},
30 | {"challenge_name": "支持", "sum_count": 482},
31 | {"challenge_name": "父母", "sum_count": 479},
32 | {"challenge_name": "一条", "sum_count": 462},
33 | {"challenge_name": "女主", "sum_count": 456},
34 | {"challenge_name": "确实", "sum_count": 456},
35 | {"challenge_name": "票房", "sum_count": 456},
36 | {"challenge_name": "无语", "sum_count": 443},
37 | {"challenge_name": "干干净净", "sum_count": 443},
38 | {"challenge_name": "为啥", "sum_count": 426},
39 | {"challenge_name": "爱情", "sum_count": 425},
40 | {"challenge_name": "喜剧", "sum_count": 422},
41 | {"challenge_name": "春节", "sum_count": 414},
42 | {"challenge_name": "剧情", "sum_count": 414},
43 | {"challenge_name": "人生", "sum_count": 409},
44 | {"challenge_name": "风格", "sum_count": 408},
45 | {"challenge_name": "演员", "sum_count": 403},
46 | {"challenge_name": "成长", "sum_count": 403},
47 | {"challenge_name": "玩意", "sum_count": 402},
48 | {"challenge_name": "文学", "sum_count": 397},
49 | ]
50 |
51 | TEST_MASK_SHAPE = "https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/log.jpeg"
52 |
53 |
54 | class TestWordCloud3DChart(unittest.TestCase):
55 |
56 | @chart_base_test(chart_type=ChartType.WORDCLOUD3D)
57 | def test_wordcloud3d_base(self):
58 | c = (
59 | WordCloud3D(
60 | render_opts=opts.RenderOpts(
61 | is_disable_dirty_bounds=True,
62 | options3d_opts=opts.Render3DOpts(is_enable=True),
63 | )
64 | )
65 | .set_data(
66 | data=[opts.BaseDataOpts(id_="data", values=TEST_WORDCLOUD3D_DATA)]
67 | )
68 | .set_wordcloud3d_spec(
69 | name_field="challenge_name",
70 | value_field="sum_count",
71 | mask_shape=TEST_MASK_SHAPE,
72 | wordcloud_opts=opts.WordCloudOpts(
73 | style=opts.BaseStyleOpts(is_keep_dir_in_3d=False),
74 | ),
75 | filling_word_opts=opts.WordCloud3DFillingWordOpts(
76 | style=opts.BaseStyleOpts(is_keep_dir_in_3d=False),
77 | ),
78 | depth_3d=1000,
79 | )
80 | .set_global_options(
81 | series_field="challenge_name",
82 | )
83 | )
84 | return c
85 |
--------------------------------------------------------------------------------