├── .coveragerc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── docs_request.yml │ ├── feaure_request.yml │ └── others.md ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE │ └── pr_cn.md ├── dependabot.yml └── workflows │ └── python-app.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.ja-JP.md ├── CONTRIBUTING.md ├── CONTRIBUTING.zh-CN.md ├── LICENSE ├── MANIFEST.in ├── README.en.md ├── README.jp.md ├── README.md ├── pyvchart ├── __init__.py ├── _version.py ├── charts │ ├── __init__.py │ ├── base.py │ ├── basic_charts │ │ ├── __init__.py │ │ ├── area.py │ │ ├── bar.py │ │ ├── boxplot.py │ │ ├── circle_packing.py │ │ ├── circular_progress.py │ │ ├── common.py │ │ ├── correlation.py │ │ ├── dot.py │ │ ├── funnel.py │ │ ├── gauge.py │ │ ├── heatmap.py │ │ ├── histogram.py │ │ ├── line.py │ │ ├── linear_progress.py │ │ ├── link.py │ │ ├── liquid.py │ │ ├── map.py │ │ ├── mosaic.py │ │ ├── pictogram.py │ │ ├── pie.py │ │ ├── radar.py │ │ ├── range_area.py │ │ ├── range_column.py │ │ ├── rose.py │ │ ├── sankey.py │ │ ├── scatter.py │ │ ├── sequence.py │ │ ├── sunburst.py │ │ ├── treemap.py │ │ ├── venn.py │ │ ├── waterfall.py │ │ └── wordcloud.py │ ├── chart.py │ ├── mixins.py │ └── three_axis_charts │ │ ├── __init__.py │ │ ├── bar3D.py │ │ ├── funnel3D.py │ │ └── wordCloud3D.py ├── commons │ ├── __init__.py │ └── utils.py ├── datasets │ ├── __init__.py │ └── filename.json ├── globals.py ├── options │ ├── __init__.py │ ├── charts_options.py │ ├── global_options.py │ └── series_options.py ├── render │ ├── __init__.py │ ├── display.py │ ├── engine.py │ └── templates │ │ ├── jupyter_lab_old.html │ │ ├── macro │ │ ├── nb_jupyter_lab.html │ │ ├── nb_jupyter_notebook.html │ │ ├── nb_nteract.html │ │ └── simple_chart.html └── types.py ├── requirements-dev.txt ├── requirements.txt ├── setup.py ├── test.py └── test ├── __init__.py ├── fixtures ├── registry.json └── registry_1.json ├── requirements.txt ├── test_area.py ├── test_bar.py ├── test_bar3d.py ├── test_base.py ├── test_boxplot.py ├── test_chart_options.py ├── test_circle_packing.py ├── test_circular_progress.py ├── test_correlation.py ├── test_datasets.py ├── test_display.py ├── test_engine.py ├── test_funnel.py ├── test_funnel3d.py ├── test_gauge.py ├── test_global_options.py ├── test_heatmap.py ├── test_histogram.py ├── test_line.py ├── test_linear_progress.py ├── test_liquid.py ├── test_map.py ├── test_mosaic.py ├── test_pictogram.py ├── test_pie.py ├── test_radar.py ├── test_range_area.py ├── test_range_column.py ├── test_rose.py ├── test_sankey.py ├── test_scatter.py ├── test_sequence.py ├── test_sunburst.py ├── test_treemap.py ├── test_utils.py ├── test_venn.py ├── test_waterfall.py ├── test_wordcloud.py └── test_wordcloud3d.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | pyvchart/render/templates/* 4 | setup.py 5 | install.py 6 | test.py 7 | test/* 8 | 9 | [report] 10 | show_missing = True 11 | omit = 12 | pyvchart/render/templates/* 13 | setup.py 14 | install.py -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=crlf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug to @VisActor/py-vchart 3 | title: '[Bug] ' 4 | labels: [bug] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | The issue list is reserved exclusively for bug reports and feature requests. 10 | 11 | For usage questions, please use the following resources: 12 | 13 | - Read the [docs](https://www.visactor.io/) 14 | - Find in [examples](https://www.visactor.io/) 15 | - Look for / ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/visactor) 16 | 17 | For non-technical support or general questions, you can email [shjkfld379978424@gmail.com](sunhailin-Leo:shjkfld379978424@gmail.com). 18 | 19 | Also try to search for your issue - it may have already been answered or even fixed in the development branch. However, if you find that an old, closed issue still persists in the latest version, you should open a new issue using the form below instead of commenting on the old issue. 20 | 21 | # - type: checkboxes 22 | # attributes: 23 | # label: Is there an existing issue for this? 24 | # description: Please search to see if an issue already exists for the bug you encountered. 25 | # options: 26 | # - label: I have searched the existing issues 27 | # required: true 28 | 29 | - type: input 30 | attributes: 31 | label: Version 32 | description: | 33 | Check if the issue is reproducible with the latest stable version of @VisActor/py-vchart. 34 | placeholder: | 35 | e.g. 1.0.0 36 | 37 | validations: 38 | required: true 39 | 40 | - type: input 41 | attributes: 42 | label: Link to Minimal Reproduction 43 | description: | 44 | If the reproduction does not need a build setup, please provide a link to [JSFiddle](https://jsfiddle.net/pfv0azuc/4/),[CodePen](https://codepen.io/kkxxkk2019/pen/KKrMRqa) or [CodeSandbox](https://codesandbox.io/s/the-template-of-visactor-vchart-vl84ww). If it requires a build setup, you can use [CodeSandbox](https://codesandbox.io/s/the-template-of-visactor-vchart-vl84ww) or provide a GitHub repo. 45 | The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed to show the bug. 46 | Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided. [Why?](https://antfu.me/posts/why-reproductions-are-required) 47 | 48 | validations: 49 | required: true 50 | 51 | - type: textarea 52 | attributes: 53 | label: Steps to Reproduce 54 | description: | 55 | What do we need to do after opening your repo in order to make the bug happen? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code. 56 | 57 | placeholder: | 58 | 1. How do you create the view 59 | 2. What's the spec of view 60 | 3. User interactions before the error happens. 61 | validations: 62 | required: true 63 | 64 | - type: textarea 65 | attributes: 66 | label: Current Behavior 67 | description: A concise description of what you're experiencing. 68 | validations: 69 | required: true 70 | 71 | - type: textarea 72 | attributes: 73 | label: Expected Behavior 74 | description: A concise description of what you expected to happen. 75 | validations: 76 | required: true 77 | 78 | - type: textarea 79 | attributes: 80 | label: Environment 81 | description: | 82 | e.g. 83 | - **OS**: macOS Monterey 84 | - **Browser**: Chrome 96.0.4664.55 85 | - **Version** py-vchart: 2.0.0 86 | value: | 87 | - OS: 88 | - Browser: 89 | - Version: 90 | render: markdown 91 | 92 | validations: 93 | required: false 94 | 95 | - type: textarea 96 | attributes: 97 | label: Any additional comments? 98 | description: | 99 | e.g. some background/context of how you ran into this bug. 100 | 101 | validations: 102 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs_request.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Request 2 | description: Request for improvements or additions to the documentation 3 | title: '[Documentation] ' 4 | labels: [docs] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | The documentation is an important part of the project, and we welcome any suggestions for improvement. If you find any issues or have any suggestions for the documentation, please feel free to open an issue. 10 | 11 | Please provide as much detail as possible when submitting an issue, including the following information: 12 | 13 | - The title of the documentation page or section you are referring to 14 | - A description of the issue or suggestion 15 | - Any relevant code snippets or examples 16 | - Screenshots or other visual aids if applicable 17 | 18 | We will review your issue as soon as possible and work with you to improve the documentation. 19 | 20 | - type: textarea 21 | attributes: 22 | label: Documentation Title or Section 23 | description: Please enter the title or section of the documentation you are referring to. 24 | placeholder: For example, "Getting Started Guide" or "API Reference" 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | attributes: 30 | label: Issue Description or Suggestion 31 | description: Please describe the issue or suggestion you have for the documentation. 32 | placeholder: For example, "The installation instructions are not clear" or "I suggest adding a tutorial on how to use the API" 33 | validations: 34 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feaure_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Request a new feature from @VisActor/py-vchart 3 | title: '[Feature] ' 4 | labels: [new-feature] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | The issue list is reserved exclusively for bug reports and feature requests. 10 | 11 | For usage questions, please use the following resources: 12 | 13 | - Read the [docs](https://www.visactor.io/) 14 | - Find in [examples](https://www.visactor.io//) 15 | - Look for / ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/visactors) 16 | 17 | For non-technical support or general questions, you can email [shjkfld379978424@gmail.com](sunhailin-Leo:shjkfld379978424@gmail.com). 18 | 19 | Also try to search for your issue - it may have already been answered or even fixed in the development branch. However, if you find that an old, closed issue still persists in the latest version, you should open a new issue using the form below instead of commenting on the old issue. 20 | 21 | - type: textarea 22 | attributes: 23 | label: What problem does this feature solve? 24 | description: | 25 | Explain your use case, context, and rationale behind this feature request. More importantly, what is the end user experience you are trying to build that led to the need for this feature? 26 | 27 | An important design goal of @VisActor/py-vchart is keeping the API surface small and straightforward. In general, we only consider adding new features that solve a problem that cannot be easily dealt with using existing APIs (i.e. not just an alternative way of doing things that can already be done). The problem should also be common enough to justify the addition. 28 | 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | attributes: 34 | label: What does the proposed API look like? 35 | description: Describe how you propose to solve the problem and provide code samples of how the API would work once implemented. Note that you can use [Markdown](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) to format your code blocks. 36 | 37 | validations: 38 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/others.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Others 3 | about: Describe this issue's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | [[中文版模板 / Chinese template](https://github.com/VisActor/py-vchart/blob/main/.github/PULL_REQUEST_TEMPLATE/pr_cn.md?plain=1)] 10 | 11 | ### 🤔 This is a ... 12 | 13 | - [ ] New feature 14 | - [ ] Bug fix 15 | - [ ] Performance optimization 16 | - [ ] Enhancement feature 17 | - [ ] Refactoring 18 | - [ ] Update dependency 19 | - [ ] Code style optimization 20 | - [ ] Test Case 21 | - [ ] Branch merge 22 | - [ ] Release 23 | - [ ] Site / documentation update 24 | - [ ] Demo update 25 | - [ ] Workflow 26 | - [ ] Other (about what?) 27 | 28 | ### 🔗 Related issue link 29 | 30 | 34 | 35 | ### 🔗 Related PR link 36 | 37 | 38 | 39 | ### 🐞 Bugserver case id 40 | 41 | 42 | 43 | ### 💡 Background and solution 44 | 45 | 50 | 51 | ### 📝 Changelog 52 | 53 | 56 | 57 | | Language | Changelog | 58 | | ---------- | --------- | 59 | | 🇺🇸 English | | 60 | | 🇨🇳 Chinese | | 61 | 62 | ### ☑️ Self-Check before Merge 63 | 64 | ⚠️ Please check all items below before requesting a reviewing. ⚠️ 65 | 66 | - [ ] Doc is updated/provided or not needed 67 | - [ ] Demo is updated/provided or not needed 68 | - [ ] TypeScript definition is updated/provided or not needed 69 | - [ ] Changelog is provided or not needed 70 | 71 | --- 72 | 73 | 77 | 78 | ### 🚀 Summary 79 | 80 | copilot:summary 81 | 82 | ### 🔍 Walkthrough 83 | 84 | copilot:walkthrough -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pr_cn.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### 🤔 这个分支是... 10 | 11 | - [ ] 新功能 12 | - [ ] Bug fix 13 | - [ ] 性能优化 14 | - [ ] 功能增强 15 | - [ ] 依赖版本更新 16 | - [ ] 代码优化 17 | - [ ] 测试 case 更新 18 | - [ ] 分支合并 19 | - [ ] 发布 20 | - [ ] 网站/文档更新 21 | - [ ] demo 更新 22 | - [ ] Workflow 23 | - [ ] 其他 (具体是什么,请补充?) 24 | 25 | ### 🔗 相关 issue 链接 26 | 27 | 31 | 32 | ### 🔗 相关的 PR 链接 33 | 34 | 35 | 36 | ### 🐞 Bugserver 用例 id 37 | 38 | 39 | 40 | ### 💡 问题的背景&解决方案 41 | 42 | 47 | 48 | ### 📝 Changelog 49 | 50 | 53 | 54 | | Language | Changelog | 55 | | ---------- | --------- | 56 | | 🇺🇸 English | | 57 | | 🇨🇳 Chinese | | 58 | 59 | ### ☑️ 自测 60 | 61 | ⚠️ 在提交 PR 之前,请检查一下内容. ⚠️ 62 | 63 | - [ ] 文档提供了,或者更新,或者不需要 64 | - [ ] Demo 提供了,或者更新,或者不需要 65 | - [ ] Ts 类型定义提供了,或者更新,或者不需要 66 | - [ ] Changelog 提供了,或者不需要 67 | 68 | --- 69 | 70 | 74 | 75 | ### 🚀 Summary 76 | 77 | copilot:summary 78 | 79 | ### 🔍 Walkthrough 80 | 81 | copilot:walkthrough -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | commit-message: 9 | prefix: ⬆ 10 | # Python 11 | - package-ecosystem: "pip" 12 | directory: "/" 13 | schedule: 14 | interval: "monthly" 15 | commit-message: 16 | prefix: ⬆ -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | name: pyvchart CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | # Allows for matrix sub-jobs to fail without canceling the rest 9 | fail-fast: false 10 | matrix: 11 | os: [ ubuntu-latest, macos-latest, windows-latest ] 12 | python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13" ] 13 | 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip setuptools 24 | pip install -r requirements.txt 25 | pip install -r test/requirements.txt 26 | - name: Run unit test 27 | run: | 28 | python setup.py install 29 | python test.py 30 | - name: Upload coverage reports to Codecov 31 | uses: codecov/codecov-action@v5 32 | with: 33 | token: ${{ secrets.CODECOV_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # PyPI configuration file 171 | .pypirc 172 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at https://github.com/VisActor. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 44 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 45 | 46 | For answers to common questions about this code of conduct, see 47 | https://www.contributor-covenant.org/faq 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.zh-CN.md: -------------------------------------------------------------------------------- 1 | 首先为你选择加入开源贡献行列的行为点赞 👍🏻。再者,十分感谢你选择参与到 VisActor 社区,为这个开源项目做出贡献。 2 | 3 | ## py-vchart 贡献指南 4 | 5 | py-vchart 团队通常在 github 上进行开发和 issue 维护,请打开 [Github 网站](https://github.com/),点击右上角 `Sign up` 按钮,注册一个自己的账号,开启你开源之旅的第一步。 6 | 7 | 在 [py-vchart 仓库](https://github.com/VisActor/py-vchart)中,我们有一份面向所有开源贡献者的[指南](https://github.com/VisActor/py-vchart/blob/develop/CONTRIBUTING.zh-CN.md),介绍了有关版本管理、分支管理等内容,**请花几分钟时间阅读了解一下**。 8 | 9 | ## 你的第一个 Pull Request 10 | 11 | ### Step0:安装 Git 12 | 13 | Git 是一种版本控制系统,用于跟踪和管理软件开发项目中的代码变更。它帮助开发者记录和管理代码的历史记录,方便团队协作、代码版本控制、合并代码等操作。通过 Git,您可以追踪每个文件的每个版本,并轻松地在不同版本之间进行切换和比较。Git 还提供了分支管理功能,使得可以同时进行多个并行开发任务。 14 | 15 | - 访问 Git 官方网站: 16 | - 下载最新版本的 Git 安装程序。 17 | - 运行下载的安装程序,按照安装向导的提示进行安装。 18 | - 安装完成后,你可以通过命令行使用 `git version` 命令确认安装成功。 19 | 20 | ### Step1:Fork 项目 21 | 22 | - 首先需要 fork 这个项目,进入[py-vchart 项目页面](https://github.com/VisActor/py-vchart),点击右上角的 Fork 按钮 23 | 24 | ![](https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VChart/contribution-guide/fork.PNG) 25 | 26 | - 你的 github 帐号中会出现 xxxx(你的 github 用户名)/py-vchart 这个项目 27 | - 在本地电脑上使用以下命令: 得到一个 py-vchart 文件夹 28 | 29 | ``` 30 | // ssh 31 | git clone git@github.com:xxxx(你的github用户名)/py-vchart.git 32 | // https 33 | git clone https://github.com/xxxx(你的github用户名)/py-vchart.git 34 | ``` 35 | 36 | ### Step2:获取项目代码 37 | 38 | - 进入 py-vchart 文件夹,添加 py-vchart 的远程地址 39 | 40 | ``` 41 | git remote add upstream https://github.com/VisActor/py-vchart.git 42 | ``` 43 | 44 | - 获取 py-vchart 最新源码 45 | 46 | ``` 47 | git pull upstream develop 48 | ``` 49 | 50 | ### Step3:创建分支 51 | 52 | - 好了,现在可以开始贡献我们的代码了。py-vchart 默认分支为 develop 分支。无论是功能开发、bug 修复、文档编写,都请新建立一个分支,再合并到 develop 分支上。使用以下代码创建分支: 53 | 54 | ``` 55 | // 创建功能开发分支 56 | git checkout -b feat/xxxx 57 | 58 | // 创建问题修复开发分支 59 | git checkout -b fix/xxxx 60 | 61 | // 创建文档、demo分支 62 | git checkout -b docs/add-funnel-demo 63 | ``` 64 | 65 | 假设我们创建了文档修改分支 `docs/add-funnel-demo` 66 | 67 | - 现在我们可以在分支上更改代码了 68 | - 假设我们已经添加了一些代码,提交到代码库 69 | - git commit -a -m "docs: add custom funnel demo and related docs" 。VisActor 的 commit 提交信息遵循 [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 规范 70 | 71 | - `[optional scope]: ` 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 | ![](https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VChart/contribution-guide/create-PR.png) 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 | pyvchart logo 3 |

4 |

pyvchart

5 |

6 | Python ❤️ VChart = pyvchart 7 |

8 |

9 | 10 | Github Actions Status 11 | 12 | 13 | Codecov 14 | 15 |

16 |

17 | 18 | Contributions welcome 19 | 20 | 21 | License 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 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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 | pyvchart logo 3 |

4 |

pyvchart

5 |

6 | Python ❤️ VChart = pyvchart 7 |

8 |

9 | 10 | Github Actions Status 11 | 12 | 13 | Codecov 14 | 15 |

16 |

17 | 18 | Contributions welcome 19 | 20 | 21 | License 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 | ## 💡 コントリビューション [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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 | pyvchart logo 3 |

4 |

pyvchart

5 |

6 | Python ❤️ VChart = pyvchart 7 |

8 |

9 | 10 | Github Actions Status 11 | 12 | 13 | Codecov 14 | 15 |

16 |

17 | 18 | Contributions welcome 19 | 20 | 21 | License 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 | ## 💡 贡献 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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 | --------------------------------------------------------------------------------