├── .coveragerc
├── .github
└── workflows
│ └── unittest.yaml
├── .gitignore
├── .readthedocs.yaml
├── LICENSE
├── MANIFEST.in
├── README.md
├── django_echarts
├── __init__.py
├── ajax_echarts.py
├── class_typing.py
├── conf.py
├── contrib
│ ├── __init__.py
│ ├── bootstrap3
│ │ ├── __init__.py
│ │ ├── config.py
│ │ └── templates
│ │ │ ├── about.html
│ │ │ ├── base.html
│ │ │ ├── blank.html
│ │ │ ├── chart_collection.html
│ │ │ ├── chart_parametric.html
│ │ │ ├── chart_single.html
│ │ │ ├── home.html
│ │ │ ├── items_grid.html
│ │ │ ├── items_list.html
│ │ │ ├── list.html
│ │ │ ├── list_with_paginator.html
│ │ │ ├── message.html
│ │ │ ├── settings.html
│ │ │ ├── simple
│ │ │ └── page.html
│ │ │ └── widgets
│ │ │ ├── chart_info.html
│ │ │ ├── container.html
│ │ │ ├── jumbotron.html
│ │ │ ├── link_group.html
│ │ │ ├── row_container.html
│ │ │ ├── title.html
│ │ │ └── value_item.html
│ ├── bootstrap5
│ │ ├── __init__.py
│ │ ├── config.py
│ │ └── templates
│ │ │ ├── about.html
│ │ │ ├── base.html
│ │ │ ├── blank.html
│ │ │ ├── chart_collection.html
│ │ │ ├── chart_parametric.html
│ │ │ ├── chart_single.html
│ │ │ ├── detail_frontend.html
│ │ │ ├── home.html
│ │ │ ├── items_grid.html
│ │ │ ├── items_list.html
│ │ │ ├── items_simple_list.html
│ │ │ ├── list.html
│ │ │ ├── list_with_paginator.html
│ │ │ ├── message.html
│ │ │ ├── settings.html
│ │ │ ├── simple
│ │ │ └── page.html
│ │ │ └── widgets
│ │ │ ├── chart_info.html
│ │ │ ├── container.html
│ │ │ ├── jumbotron.html
│ │ │ ├── link_group.html
│ │ │ ├── row_container.html
│ │ │ ├── title.html
│ │ │ └── value_item.html
│ └── material
│ │ ├── __init__.py
│ │ ├── config.py
│ │ └── templates
│ │ ├── about.html
│ │ ├── base.html
│ │ ├── blank.html
│ │ ├── chart_collection.html
│ │ ├── chart_parametric.html
│ │ ├── chart_single.html
│ │ ├── home.html
│ │ ├── items_grid.html
│ │ ├── items_list.html
│ │ ├── list.html
│ │ ├── list_with_paginator.html
│ │ ├── message.html
│ │ ├── settings.html
│ │ ├── simple
│ │ └── page.html
│ │ └── widgets
│ │ ├── chart_info.html
│ │ ├── container.html
│ │ ├── jumbotron.html
│ │ ├── link_group.html
│ │ ├── row_container.html
│ │ ├── title.html
│ │ └── value_item.html
├── core
│ ├── __init__.py
│ ├── dms.py
│ ├── exceptions.py
│ ├── localfiles.py
│ ├── settings_store.py
│ └── tms.py
├── custom_maps.py
├── datatools
│ ├── __init__.py
│ ├── helpers.py
│ ├── managers.py
│ └── section_counter.py
├── entities
│ ├── __init__.py
│ ├── articles.py
│ ├── base.py
│ ├── chart_widgets.py
│ ├── containers.py
│ ├── html_widgets.py
│ ├── layouts.py
│ ├── pages.py
│ ├── styles.py
│ └── uri.py
├── geojson.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ ├── _downloader.py
│ │ ├── download.py
│ │ ├── info.py
│ │ ├── startsite.py
│ │ └── starttpl.py
├── renders
│ ├── __init__.py
│ ├── html.py
│ └── javascript.py
├── site_exts.py
├── starter
│ ├── __init__.py
│ ├── optstools.py
│ ├── params_forms.py
│ └── sites.py
├── stores
│ ├── __init__.py
│ └── entity_factory.py
├── templates
│ └── snippets
│ │ ├── echarts_init_options.tpl
│ │ └── first_views.py.tpl
├── templatetags
│ ├── __init__.py
│ └── echarts.py
├── utils
│ ├── __init__.py
│ ├── burl.py
│ ├── compat.py
│ ├── downloader.py
│ ├── lazy_dict.py
│ └── param_converters.py
└── views.py
├── docs
├── about.md
├── breaking-change.md
├── changelog.md
├── gallery.md
├── guides
│ ├── advance_development.md
│ ├── custom_maps.md
│ ├── dms.md
│ └── entity_params.md
├── images
│ ├── chart_fj-map-gdp.png
│ ├── collection_all.png
│ ├── fujian-custom-geojson.png
│ ├── fujian-forest-coverage.png
│ ├── geojson-io-export.png
│ ├── home_top.png
│ ├── home_with_chart_bootstrap5.png
│ ├── home_with_values_panel.png
│ ├── list_with_pagination.png
│ ├── material_list_page.png
│ ├── quickstart-0.png
│ ├── row_container_demo.png
│ ├── theme_darkly.png
│ ├── theme_material.png
│ └── timeline_demo.png
├── index.md
├── posts
│ ├── new_page_feature.md
│ ├── pyecharts_project.md
│ ├── third_html_library.md
│ ├── use_custom_geojson.md
│ └── widget_collection.md
├── reference
│ ├── commands.md
│ ├── configure.md
│ ├── stores.md
│ ├── system_design.md
│ ├── tags.md
│ └── views_and_templates.md
├── release-note
│ ├── v050.md
│ ├── v051.md
│ └── v060.md
└── starter
│ ├── base.md
│ ├── localize_staticfiles.md
│ ├── site_configuration.md
│ ├── themes.md
│ └── widget_and_layout.md
├── long_description.md
├── mkdocs.yaml
├── requirements.txt
├── requirements_dev.txt
├── requirements_doc.txt
├── setup.cfg
├── setup.py
├── tests
├── __init__.py
├── settings_mock.py
├── test_dms.py
├── test_factory.py
├── test_hosts.py
├── test_interface.py
├── test_layouts.py
├── test_mcharts.py
├── test_renders.py
├── test_section_counter.py
├── test_settings_store.py
├── test_tms.py
├── test_uri.py
├── test_utils.py
└── test_widgets.py
└── validate_release.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit =
3 | tests/*
4 | django_echarts/views.py
5 | django_echarts/ajax_echarts.py
6 | django_echarts/starter/*
7 | django_echarts/management/*
8 | django_echarts/utils/compat.py
9 | django_echarts/geojson.py
10 | django_echarts/utils/downloader.py
11 |
12 | [report]
13 | exclude_lines =
14 | pragma: no cover
15 | def __repr__
16 | def __str__
17 | def __eq__
18 | def __hash__
19 | if self.debug:
20 | if settings.DEBUG
21 | raise AssertionError
22 | raise NotImplementedError
23 | if 0:
24 | if __name__ == .__main__.:
25 | class .*\bProtocol\):
26 | @(abc\.)?abstractmethod
27 | omit =
28 | tests/*
29 | django_echarts/views.py
30 | django_echarts/ajax_echarts.py
31 | django_echarts/starter/*
32 | django_echarts/management/*
33 | django_echarts/utils/compat.py
34 | django_echarts/geojson.py
35 | django_echarts/utils/downloader.py
--------------------------------------------------------------------------------
/.github/workflows/unittest.yaml:
--------------------------------------------------------------------------------
1 | name: unittest
2 | on:
3 | push:
4 | branches:
5 | - master
6 | - dev
7 | - 'release/**'
8 | pull_request:
9 | branches:
10 | - master
11 | jobs:
12 | unittest:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | python-version: [ '3.8', '3.9' ]
17 | django-version: [ '2.2', '3.2' ]
18 | pyecharts-version: ['1.9.1', '2.0.2']
19 | steps:
20 | - uses: actions/checkout@v2
21 | - name: Set up Python ${{ matrix.python-version }}
22 | uses: actions/setup-python@v2
23 | with:
24 | python-version: ${{ matrix.python-version }}
25 | - name: Install dependencies
26 | run: |
27 | python -m pip install --upgrade pip
28 | pip install django~=${{ matrix.django-version }}
29 | pip install borax~=4.0
30 | pip install pyecharts~=${{ matrix.pyecharts-version }}
31 | pip install typing_extensions~=4.5
32 | pip install htmlgenerator~=1.2
33 | pip install flake8~=3.9
34 | pip install nose2~=0.10
35 | - name: Lint with flake8
36 | run: |
37 | flake8 django_echarts tests
38 | - name: Test with pytest
39 | run: |
40 | nose2 --with-coverage --coverage django_echarts --coverage-report xml
41 | - name: Upload coverage to Codecov
42 | uses: codecov/codecov-action@v1
43 | with:
44 | file: ./coverage.xml
45 | env_vars: OS,PYTHON
46 | name: codecov-umbrella
47 | fail_ci_if_error: false
48 | verbose: true
--------------------------------------------------------------------------------
/.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 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
103 | .idea
104 | demo.py
105 | example/
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yaml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Set the version of Python and other tools you might need
9 | build:
10 | os: ubuntu-20.04
11 | tools:
12 | python: "3.9"
13 |
14 | mkdocs:
15 | configuration: mkdocs.yaml
16 |
17 | # Optionally declare the Python requirements required to build your docs
18 | python:
19 | install:
20 | - requirements: requirements_doc.txt
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2022
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 | recursive-include django_echarts/templates *.tpl *.py *.html
2 | include django_echarts/contrib/*.html
3 | include django_echarts/contrib/*/*.html
4 | include django_echarts/contrib/*/*/*.html
5 | include django_echarts/contrib/*/*/*/*.html
6 |
--------------------------------------------------------------------------------
/django_echarts/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | A django app for Echarts integration with pyecharts as chart builder.
3 | """
4 |
5 | __version__ = '0.6.0'
6 | __author__ = 'kinegratii'
7 |
--------------------------------------------------------------------------------
/django_echarts/ajax_echarts.py:
--------------------------------------------------------------------------------
1 | import json
2 | from typing import Any
3 |
4 | from django.http.response import JsonResponse
5 | from django.views.generic.base import View
6 | from django_echarts.stores.entity_factory import factory
7 |
8 |
9 | class ChartOptionsView(View):
10 | def get(self, request, *args, **kwargs) -> Any:
11 | chart_name = self.kwargs.get('name')
12 | chart_obj = factory.get_chart_widget(chart_name)
13 | if not chart_obj:
14 | data = {}
15 | else:
16 | data = json.loads(chart_obj.dump_options_with_quotes())
17 | return JsonResponse(data, safe=False)
18 |
--------------------------------------------------------------------------------
/django_echarts/class_typing.py:
--------------------------------------------------------------------------------
1 | from typing import List, Union, TypedDict
2 |
3 | from typing_extensions import NotRequired
4 |
5 |
6 | class TNavConfig(TypedDict):
7 | nav_left: NotRequired[List[Union[str, dict]]]
8 | nav_right: NotRequired[List[Union[str, dict]]]
9 | nav_footer: NotRequired[List[Union[str, dict]]]
10 |
--------------------------------------------------------------------------------
/django_echarts/conf.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 |
3 | """
4 | Expose the settings objects.
5 | """
6 | import os
7 | from django.conf import settings
8 | from django.utils.functional import SimpleLazyObject
9 |
10 | from django_echarts.core.settings_store import SettingsStore
11 |
12 | __all__ = ['DJANGO_ECHARTS_SETTINGS']
13 |
14 |
15 | def get_django_echarts_settings():
16 | project_echarts_settings = getattr(settings, 'DJANGO_ECHARTS', {})
17 | static_url = settings.STATIC_URL
18 | if settings.STATICFILES_DIRS:
19 | staticfiles_dir = str(settings.STATICFILES_DIRS[0])
20 | else:
21 | staticfiles_dir = os.path.join(str(settings.BASE_DIR), 'static')
22 | extra_settings = {
23 | 'STATIC_URL': settings.STATIC_URL,
24 | 'STATICFILES_DIRS': settings.STATICFILES_DIRS
25 | }
26 | settings_store = SettingsStore(
27 | echarts_settings=project_echarts_settings,
28 | extra_settings=extra_settings,
29 | static_url=static_url,
30 | staticfiles_dir=staticfiles_dir
31 | )
32 | return settings_store
33 |
34 |
35 | DJANGO_ECHARTS_SETTINGS = SimpleLazyObject(get_django_echarts_settings) # type: SettingsStore
36 |
--------------------------------------------------------------------------------
/django_echarts/contrib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/contrib/__init__.py
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/contrib/bootstrap3/__init__.py
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/config.py:
--------------------------------------------------------------------------------
1 | NAME = 'bootstrap3'
2 |
3 | PALETTES = [
4 | "cerulean", "cosmo", "cyborg", "darkly", "flatly", "journal", "lumen", "paper",
5 | "readable", "sandstone", "simplex", "slate", "spacelab", "superhero", "united", "yeti",
6 | ]
7 |
8 | STATIC_URL_DICT = {
9 | 'main_css': 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css',
10 | 'palette_css': 'https://bootswatch.com/3/{palette}/bootstrap.min.css',
11 | 'jquery_js': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js',
12 | 'main_js': 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js',
13 | }
14 | # replace or append palette css with main css
15 | PALETTE_POLICY = 'replace'
16 |
17 | CLASS_NAMES = {'row': 'row', 'col': 'col-sm-12 col-md-{n}'}
18 |
19 | TABLE_CLASS_NAMES = {
20 | 'default': 'table table-responsive',
21 | 'border': 'table-bordered',
22 | 'borderless': 'table-borderless',
23 | 'striped': 'table-striped',
24 | 'size': 'table-{size}'
25 | }
26 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/about.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
The about Info
6 |
7 | {% endblock %}
8 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/blank.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block extra_css %}
3 | {% endblock %}
4 |
5 | {% block main_content %}
6 | This is a blank page.
7 |
8 | {% endblock %}
9 |
10 | {% block extra_script %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/chart_collection.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 | {% dw_widget widget_collection %}
6 | {% endblock %}
7 |
8 | {% block extra_script %}
9 | {% echarts_js_dependencies widget_collection %}
10 | {% echarts_js_content widget_collection %}
11 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/chart_parametric.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 | {{ chart_info.title }}
6 |
7 | {% dw_widget link_group %}
8 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/chart_single.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
6 |
7 |
8 |
11 | {% if chart_info.description %}
12 |
{{ chart_info.description }}
13 | {% endif %}
14 | {% if chart_info.tags %}
15 |
16 | {% for tag in chart_info.tags %}
17 | {{ tag }}
18 | {% endfor %}
19 |
20 |
21 | {% endif %}
22 |
23 | {% dw_widget chart_obj %}
24 | {% if chart_info.body %}
25 |
26 |
{{ chart_info.body }}
27 | {% endif %}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {% endblock %}
36 |
37 | {% block extra_script %}
38 | {% echarts_js_dependencies chart_obj %}
39 | {% echarts_js_content chart_obj %}
40 | {% endblock %}
41 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/home.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 | {% block main_content %}
4 | {% dw_widget jumbotron %}
5 | {% if chart_obj %}
6 |
7 |
8 |
9 |
{{ chart_info.title }}
10 |
11 |
12 |
13 | {% dw_widget chart_obj width="100%" height="320px" %}
14 |
15 |
16 |
{{ chart_info.description }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {% endif %}
25 | {% if home_values_panel %}
26 | {% dw_widget home_values_panel %}
27 | {% endif %}
28 | {% if top_chart_info_list %}
29 |
32 | {% include layout_tpl with chart_info_list=top_chart_info_list only %}
33 | {% endif %}
34 |
35 | {% endblock %}
36 | {% block extra_script %}
37 | {% if chart_obj %}
38 | {% echarts_js_dependencies chart_obj %}
39 | {% echarts_js_content chart_obj %}
40 | {% endif %}
41 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/items_grid.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 | {% for chart_info in chart_info_list %}
4 |
5 |
6 |
7 |
{{ chart_info.title }}
8 | {% if chart_info.tags %}
9 |
10 | {% for tag in chart_info.tags %}
11 | {{ tag }}
12 | {% endfor %}
13 |
14 | {% endif %}
15 |
16 |
17 |
18 |
{{ chart_info.description|default:"暂无描述" }}
19 |
20 |
21 |
22 |
23 | {% endfor %}
24 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/items_list.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block main_content %}
4 |
7 |
16 | {% include layout_tpl with chart_info_list=chart_info_list only %}
17 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/list_with_paginator.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
6 |
9 |
18 | {% include layout_tpl with chart_info_list=page_obj.object_list only %}
19 |
20 |
21 |
43 |
44 |
45 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/message.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
6 |
{{ message.title }}
7 |
8 |
{{ message.text }}
9 |
10 |
11 |
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/settings.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block main_content %}
3 |
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/simple/page.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 |
4 |
5 |
6 |
7 | Title
8 | {% block include_css %}
9 | {% theme_css %}
10 | {% endblock %}
11 |
12 |
13 | {% dw_widget container_obj %}
14 | {% theme_js %}
15 | {% echarts_js_dependencies container_obj %}
16 | {% echarts_js_content container_obj %}
17 |
18 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/widgets/chart_info.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ widget.title }}
3 |
4 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/widgets/container.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 | {% if widget.div_class %}
3 |
4 | {% endif %}
5 | {% for row_container in widget %}
6 | {% dw_widget row_container %}
7 | {% endfor %}
8 | {% if widget.div_class %}
9 |
10 | {% endif %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/widgets/jumbotron.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ widget.title }}
3 |
{{ widget.main_text }}
4 |
{{ widget.small_text }}
5 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/widgets/link_group.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/widgets/row_container.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 | {% for rw, layout in widget.iter_layout %}
4 |
5 | {% dw_widget rw width=layout.ww height=layout.wh %}
6 |
7 | {% endfor %}
8 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/widgets/title.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap3/templates/widgets/value_item.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/contrib/bootstrap5/__init__.py
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/config.py:
--------------------------------------------------------------------------------
1 | NAME = 'bootstrap5'
2 |
3 | PALETTES = ["cerulean", "cosmo", "cyborg", "darkly", "flatly", "journal", "litera", "lumen", "lux", "materia",
4 | "minty", "morph", "pulse", "quartz", "sandstone", "simplex", "sketchy", "slate", "solar", "spacelab",
5 | "superhero", "united", "vapor", "yeti", "zephyr",
6 | ]
7 |
8 | STATIC_URL_DICT = {
9 | 'main_css': 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css',
10 | 'palette_css': 'https://bootswatch.com/5/{palette}/bootstrap.min.css',
11 | 'font_css': 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.8.1/font/bootstrap-icons.min.css',
12 | 'jquery_js': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js',
13 | 'main_js': 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js',
14 | }
15 | # replace or append palette css with main css
16 | PALETTE_POLICY = 'replace'
17 |
18 | CLASS_NAMES = {'row': 'row', 'col': 'col-sm-12 col-md-{n}'}
19 |
20 | TABLE_CLASS_NAMES = {
21 | 'default': 'table table-responsive',
22 | 'border': 'table-bordered',
23 | 'borderless': 'table-borderless',
24 | 'striped': 'table-striped',
25 | 'size': 'table-{size}'
26 | }
27 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/about.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 | The about Info
6 |
7 | {% include 'widgets/collection.html' %}
8 |
9 |
10 | {% endblock %}
11 | {% block extra_script %}
12 | {% echarts_js_dependencies collection %}
13 | {% echarts_js_content collection %}
14 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/blank.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block extra_css %}
3 | {% endblock %}
4 |
5 | {% block main_content %}
6 | This is a blank page.
7 |
8 | {% endblock %}
9 |
10 | {% block extra_script %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/chart_collection.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 | {% dw_widget widget_collection %}
6 | {% endblock %}
7 |
8 | {% block extra_script %}
9 | {% echarts_js_dependencies widget_collection %}
10 | {% echarts_js_content widget_collection %}
11 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/chart_parametric.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 | {{ chart_info.title }}
6 |
7 | {% dw_widget link_group %}
8 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/chart_single.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
6 |
7 |
10 |
11 |
12 | {% if chart_info.description %}
13 |
{{ chart_info.description }}
14 | {% endif %}
15 | {% if chart_info.tags %}
16 |
17 | {% for tag in chart_info.tags %}
18 | {{ tag }}
19 | {% endfor %}
20 |
21 |
22 | {% endif %}
23 |
24 |
25 |
26 | {% dw_widget chart_obj %}
27 |
28 | {% if chart_info.body %}
29 |
30 |
{{ chart_info.body }}
31 | {% endif %}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {% endblock %}
40 |
41 | {% block extra_script %}
42 | {% echarts_js_dependencies chart_obj %}
43 | {% echarts_js_content chart_obj %}
44 | {% endblock %}
45 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/detail_frontend.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 | 描述 |
15 | {{ chart_info.description }} |
16 |
17 | {% if chart_info.tags %}
18 |
19 | 标签 |
20 | {% for tag in chart_info.tags %}
21 | {{ tag }}
22 | {% endfor %} |
23 |
24 | {% endif %}
25 |
26 |
27 |
28 |
29 | {% dw_widget chart_obj %}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {% endblock %}
39 |
40 | {% block extra_script %}
41 | {% echarts_js_dependencies chart_obj %}
42 | {% echarts_js_content chart_obj %}
43 | {% endblock %}
44 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/home.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 | {% block main_content %}
4 | {% dw_widget jumbotron %}
5 | {% if chart_obj %}
6 |
7 |
8 |
9 |
10 |
11 |
12 | {% dw_widget chart_obj width="100%" height="320px" %}
13 |
14 |
15 |
16 |
{{ chart_info.title }}
17 |
{{ chart_info.description }}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | {% endif %}
27 | {% if home_values_panel %}
28 | {% dw_widget home_values_panel %}
29 | {% endif %}
30 | {% if top_chart_info_list %}
31 |
32 |
热门图表
33 |
34 |
35 | {% include layout_tpl with chart_info_list=top_chart_info_list only %}
36 | {% endif %}
37 | {% endblock %}
38 | {% block extra_script %}
39 | {% if chart_obj %}
40 | {% echarts_js_dependencies chart_obj %}
41 | {% echarts_js_content chart_obj %}
42 | {% endif %}
43 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/items_grid.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 | {% for chart_info in chart_info_list %}
4 |
5 |
6 |
7 |
15 |
16 |
{{ chart_info.description|default:"暂无描述" }}
17 |
详情
19 |
20 |
21 |
22 |
23 |
24 | {% endfor %}
25 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/items_list.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/items_simple_list.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block main_content %}
4 |
7 |
16 | {% include layout_tpl with chart_info_list=chart_info_list only %}
17 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/list_with_paginator.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
6 |
9 |
18 | {% include layout_tpl with chart_info_list=page_obj.object_list only %}
19 |
20 |
21 |
42 |
43 |
44 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/message.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
6 |
7 |
8 |
{{ message.text }}
9 |
10 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/settings.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block main_content %}
3 |
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/simple/page.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 |
4 |
5 |
6 |
7 | Title
8 | {% block include_css %}
9 | {% theme_css %}
10 | {% endblock %}
11 |
12 |
13 | {% dw_widget container_obj %}
14 | {% theme_js %}
15 | {% echarts_js_dependencies container_obj %}
16 | {% echarts_js_content container_obj %}
17 |
18 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/widgets/chart_info.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ widget.title }}
3 |
{{ widget.description }}
4 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/widgets/container.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 | {% if widget.div_class %}
3 |
4 | {% endif %}
5 | {% for row_container in widget %}
6 | {% dw_widget row_container %}
7 | {% endfor %}
8 | {% if widget.div_class %}
9 |
10 | {% endif %}
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/widgets/jumbotron.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ widget.title }}
4 |
5 |
{{ widget.main_text }}
6 |
{{ widget.small_text }}
7 |
8 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/widgets/link_group.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/widgets/row_container.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 | {% for rw, layout in widget.iter_layout %}
4 |
5 | {% dw_widget rw width=layout.ww height=layout.wh %}
6 |
7 | {% endfor %}
8 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/widgets/title.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ widget.text }}{% if widget.small_text %}
3 | {{ widget.small_text }}{% endif %}
4 |
5 |
--------------------------------------------------------------------------------
/django_echarts/contrib/bootstrap5/templates/widgets/value_item.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ widget.value }} {{ widget.unit }}
4 | {% if widget.arrow %}{% endif %}
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/contrib/material/__init__.py
--------------------------------------------------------------------------------
/django_echarts/contrib/material/config.py:
--------------------------------------------------------------------------------
1 | NAME = 'material'
2 |
3 | PALETTES = []
4 |
5 | STATIC_URL_DICT = {
6 | 'font_css': 'https://fonts.font.im/icon?family=Material+Icons',
7 | 'main_css': 'https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css',
8 | 'jquery_js': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js',
9 | 'main_js': 'https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js',
10 | }
11 | # replace or append palette css with main css
12 | PALETTE_POLICY = 'replace'
13 |
14 | CLASS_NAMES = {'row': 'row', 'col': 'col s12 m{n}'}
15 |
16 | TABLE_CLASS_NAMES = {
17 | 'default': 'responsive-table',
18 | 'striped': 'striped',
19 | 'center': 'centered'
20 | }
21 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/about.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block main_content %}
3 | The about Info
4 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 |
4 |
5 |
6 | {{ page_title }}
7 | {% block include_css %}
8 | {% theme_css %}
9 | {% endblock %}
10 | {% block extra_css %}
11 | {% endblock %}
12 |
13 |
14 |
15 | {% block nav %}
16 |
61 | {% endblock %}
62 | {% block main_content %}
63 | {% endblock %}
64 | {% block footer %}
65 |
89 | {% endblock %}
90 |
91 | {% block include_script %}
92 | {% theme_js %}
93 | {% endblock %}
94 |
95 | {% block extra_script %}
96 |
101 | {% endblock %}
102 |
103 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/blank.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block extra_css %}
3 | {% endblock %}
4 |
5 | {% block main_content %}
6 | This is a blank page.
7 |
8 | {% endblock %}
9 |
10 | {% block extra_script %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/chart_collection.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 | {% dw_widget widget_collection %}
6 | {% endblock %}
7 |
8 | {% block extra_script %}
9 | {% echarts_js_dependencies widget_collection %}
10 | {% echarts_js_content widget_collection %}
11 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/chart_parametric.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 | {{ chart_info.title }}
6 |
7 | {% dw_widget link_group %}
8 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/chart_single.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
6 |
7 |
8 |
9 |
{{ title }}
10 |
11 |
12 | {% if chart_info.description %}
13 |
14 |
{{ chart_info.description }}
15 | {% if chart_info.tags %}
16 |
17 | {% for tag in chart_info.tags %}
18 | {{ tag }}
19 | {% endfor %}
20 |
21 | {% endif %}
22 |
23 | {% endif %}
24 |
25 | {% dw_widget chart_obj %}
26 | {% if chart_info.body %}
27 |
28 |
{{ chart_info.body }}
29 | {% endif %}
30 |
31 |
32 |
33 |
34 | {% endblock %}
35 |
36 | {% block extra_script %}
37 | {% echarts_js_dependencies chart_obj %}
38 | {% echarts_js_content chart_obj %}
39 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/home.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 | {% block main_content %}
4 |
5 |
6 |
7 | {% dw_widget jumbotron %}
8 | {% if home_values_panel %}
9 | {% dw_widget home_values_panel %}
10 | {% endif %}
11 | {% if top_chart_info_list %}
12 | {% include layout_tpl with chart_info_list=top_chart_info_list only %}
13 | {% endif %}
14 |
15 |
16 |
17 |
18 |
19 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/items_grid.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 | {% for chart_info in chart_info_list %}
4 |
5 |
6 |
7 |
{{ chart_info.title }}
8 |
{{ chart_info.description|default:"暂无描述" }}
9 | {% if chart_info.tags %}
10 | {% for tag in chart_info.tags %}
11 |
{{ tag }}
12 | {% endfor %}
13 | {% endif %}
14 |
15 |
18 |
19 |
20 | {% endfor %}
21 |
22 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/items_list.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 | {% for chart_info in chart_info_list %}
4 |
5 |
6 |
7 |
{{ chart_info.title }}
8 |
{{ chart_info.description|default:"暂无描述" }}
9 | {% if chart_info.tags %}
10 |
11 | {% for tag in chart_info.tags %}
12 |
{{ tag }}
13 | {% endfor %}
14 | {% endif %}
15 |
16 |
17 |
18 |
21 |
22 |
23 | {% endfor %}
24 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block main_content %}
4 |
5 |
6 |
15 |
16 |
17 |
18 | {% include layout_tpl with chart_info_list=chart_info_list only %}
19 |
20 |
21 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/list_with_paginator.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load echarts %}
3 |
4 | {% block main_content %}
5 |
6 |
7 |
16 |
17 |
18 | {% include layout_tpl with chart_info_list=page_obj.object_list only %}
19 |
20 |
21 |
22 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/message.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block main_content %}
3 |
4 |
5 |
6 |
7 | {{ message.text }}
8 |
9 |
10 |
11 |
12 | {% endblock %}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/settings.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block main_content %}
3 |
16 |
17 | {% endblock %}
18 |
19 | {% block extra_script %}
20 |
26 | {% endblock %}
27 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/simple/page.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 |
4 |
5 |
6 |
7 | Title
8 | {% block include_css %}
9 | {% theme_css %}
10 | {% endblock %}
11 |
12 |
13 | {% dw_widget container_obj %}
14 | {% theme_js %}
15 | {% echarts_js_dependencies container_obj %}
16 | {% echarts_js_content container_obj %}
17 |
18 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/widgets/chart_info.html:
--------------------------------------------------------------------------------
1 | {{ widget.description }}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/widgets/container.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 | {% if widget.div_class %}
3 |
4 | {% endif %}
5 | {% for row_container in widget %}
6 | {% dw_widget row_container %}
7 | {% endfor %}
8 | {% if widget.div_class %}
9 |
10 | {% endif %}
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/widgets/jumbotron.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ widget.small_text }}
8 |
9 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/widgets/link_group.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/widgets/row_container.html:
--------------------------------------------------------------------------------
1 | {% load echarts %}
2 |
3 | {% for rw, layout in widget.iter_layout %}
4 |
5 | {% dw_widget rw width=layout.ww height=layout.wh %}
6 |
7 | {% endfor %}
8 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/widgets/title.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ widget.text }}{% if widget.small_text %}
3 | {{ widget.small_text }}{% endif %}
4 |
5 |
--------------------------------------------------------------------------------
/django_echarts/contrib/material/templates/widgets/value_item.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ widget.value }} {{ widget.unit }}
4 |
5 |
{{ widget.description }}
6 |
--------------------------------------------------------------------------------
/django_echarts/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/core/__init__.py
--------------------------------------------------------------------------------
/django_echarts/core/exceptions.py:
--------------------------------------------------------------------------------
1 | __all__ = ['DJEAbortException', 'WidgetNotRegisteredError']
2 |
3 |
4 | class DJEAbortException(BaseException):
5 | def __init__(self, message: str):
6 | self.message = message
7 | super().__init__(message)
8 |
9 |
10 | # ErrorMessageException / WarningMessageException
11 |
12 | class ChartFuncCallError(BaseException):
13 | def __init__(self, char_name: str):
14 | super().__init__(f'ChartName:{char_name}')
15 |
16 |
17 | class WidgetNotRegisteredError(BaseException):
18 | def __init__(self, widget):
19 | super().__init__(f'Unknown widget type:{widget.__class__.__name__}')
20 |
21 |
22 | class ChartDoesNotExist(BaseException):
23 | def __init__(self, message: str = None, param_name: str = None, param_input=None, param_choices=None):
24 | if param_name:
25 | message = f'Invalid param value: {param_name}={param_input}. Choices are: {param_choices}'
26 | super().__init__(message)
27 |
--------------------------------------------------------------------------------
/django_echarts/core/localfiles.py:
--------------------------------------------------------------------------------
1 | from typing import Tuple
2 | import os
3 |
4 |
5 | class DownloaderResource:
6 | def __init__(self, remote_url, ref_url, local_path, label=None, catalog=None, exists=False):
7 | self.remote_url = remote_url
8 | self.ref_url = ref_url
9 | self.local_path = local_path
10 | self.label = label or ''
11 | self.catalog = catalog or ''
12 | self.exists = exists
13 |
14 |
15 | class LocalFilesMixin:
16 | def set_localize_opts(self, static_url, staticfiles_dir):
17 | """Set options for localize_opts"""
18 | self._localize_opts = {
19 | 'static_url': static_url,
20 | 'staticfiles_dir': staticfiles_dir
21 | }
22 |
23 | def url2filename(self, url, **kwargs) -> str:
24 | pass
25 |
26 | def localize_url(self, filename: str) -> Tuple[str, str]:
27 | """Return local ref url and local file path from a remote url."""
28 | local_ref_url = self._localize_opts['static_url'] + filename
29 | local_path = os.path.join(self._localize_opts['staticfiles_dir'], filename)
30 | return local_ref_url, local_path
31 |
--------------------------------------------------------------------------------
/django_echarts/datatools/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/datatools/__init__.py
--------------------------------------------------------------------------------
/django_echarts/datatools/helpers.py:
--------------------------------------------------------------------------------
1 | """
2 | 数据处理函数
3 | See more detail https://mp.weixin.qq.com/s/ZYU7g3mVF-cvRZ3JyMIngw
4 | """
5 | import statistics
6 | from collections import namedtuple, UserList
7 | from dataclasses import dataclass
8 | from typing import List, Tuple
9 |
10 |
11 | def adjust_max_min(values: list):
12 | ma = max(values)
13 | mi = min(values)
14 | nmax = int(ma + (ma - mi) / 2) + 1
15 | nmin = int(mi - (ma - mi) / 2)
16 | return nmax, nmin
17 |
18 |
19 | def ceil_n(val, n=3):
20 | return (val // (10 ** n) + 1) * 10 ** n
21 |
22 |
23 | def floor_n(val, n=3):
24 | return (val // (10 ** n)) * 10 ** n
25 |
26 |
27 | QuartilesRank = namedtuple('QuartilesRank', 'rank text color')
28 |
29 |
30 | @dataclass
31 | class SeriesStatisticsValue:
32 | """一维数据统计值"""
33 | label: str
34 | index: int
35 | num: int
36 | avg: int
37 | std_deviation: int
38 | min_: int
39 | q1: int
40 | q2: int
41 | q3: int
42 | max_: int
43 |
44 | @property
45 | def boxplot_values(self) -> Tuple:
46 | """Return five values for boxplot"""
47 | return (self.min_, self.q1, self.q2, self.q3, self.max_)
48 |
49 | def evaluate(self, value: int) -> QuartilesRank:
50 | if value < self.q1:
51 | return QuartilesRank(rank=1, text='下', color='danger')
52 | elif value < self.q2:
53 | return QuartilesRank(rank=2, text='中下', color='warning')
54 | elif value < self.q3:
55 | return QuartilesRank(rank=2, text='中上', color='info')
56 | else:
57 | return QuartilesRank(rank=2, text='上', color='success')
58 |
59 | @classmethod
60 | def from_(cls, data: List, label: str = '', index=0):
61 | q1, q2, q3 = list(map(int, statistics.quantiles(data)))
62 | return cls(label=label, num=len(data),
63 | avg=int(statistics.mean(data)), std_deviation=int(statistics.pstdev(data)),
64 | min_=min(data), max_=max(data), q1=q1, q2=q2, q3=q3, index=index)
65 |
66 |
67 | class SeriesStatisticsList(UserList[SeriesStatisticsValue]):
68 |
69 | @property
70 | def axis_values(self):
71 | return [item.label for item in self]
72 |
73 | @property
74 | def boxplot_data(self):
75 | return [list(item.boxplot_values) for item in self]
76 |
--------------------------------------------------------------------------------
/django_echarts/datatools/managers.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 |
3 | from borax.datasets.fetch import fetch
4 | from django.db import models
5 |
6 |
7 | class AxisValuesQuerySet(models.QuerySet):
8 | def as_axis_values(self, *keys, **kwargs):
9 | return fetch(self, *keys, **kwargs)
10 |
--------------------------------------------------------------------------------
/django_echarts/datatools/section_counter.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 | """
3 | A indexes-based counter module for value list.
4 | 1. It is some likely feature but indexes-based
5 | 2. Index match supports the "in" operator
6 | 3. The counter of missing index will be set to 0
7 | 4. export to counter or axis data list
8 | """
9 |
10 | from collections import Counter
11 |
12 | MIN_LOWER = -100000 # The min value of range lower.
13 |
14 |
15 | class BIndex:
16 | def __contains__(self, item):
17 | return False
18 |
19 | def __str__(self):
20 | return ''
21 |
22 | def _key(self):
23 | return 0
24 |
25 |
26 | class BValueIndex(BIndex):
27 | def __init__(self, value):
28 | self.value = value
29 |
30 | def __contains__(self, item):
31 | return item == self.value
32 |
33 | def __str__(self):
34 | return str(self.value)
35 |
36 | def _key(self):
37 | return self.value
38 |
39 |
40 | class BSectionIndex(BIndex):
41 | def __init__(self, lower=None, upper=None):
42 | self.lower = lower
43 | self.upper = upper
44 | self._lower_specified = self.lower is not None
45 | self._upper_specified = self.upper is not None
46 |
47 | def __contains__(self, item):
48 | if self._lower_specified:
49 | if self._upper_specified:
50 | return self.lower <= item <= self.upper
51 | else:
52 | return self.lower <= item
53 | else:
54 | if self._upper_specified:
55 | return item <= self.upper
56 | else:
57 | return True
58 |
59 | def __str__(self):
60 | if self._lower_specified and self._upper_specified:
61 | return '{0}~{1}'.format(self.lower, self.upper)
62 | elif self._lower_specified:
63 | return '≥{}'.format(self.lower)
64 | elif self._upper_specified:
65 | return '≤{}'.format(self.upper)
66 | else:
67 | return '~'
68 |
69 | def _key(self):
70 | return self.lower if self._lower_specified else MIN_LOWER
71 |
72 |
73 | class BRangeIndex(BIndex):
74 | def __init__(self, lower=None, upper=None):
75 | self.lower = lower
76 | self.upper = upper
77 | self._lower_specified = self.lower is not None
78 | self._upper_specified = self.upper is not None
79 |
80 | def __contains__(self, item):
81 | if self._lower_specified:
82 | if self._upper_specified:
83 | return self.lower <= item < self.upper
84 | else:
85 | return self.lower <= item
86 | else:
87 | if self._upper_specified:
88 | return item < self.upper
89 | else:
90 | return True
91 |
92 | def __str__(self):
93 | if self._lower_specified and self._upper_specified:
94 | return '{0}~{1}'.format(self.lower, self.upper)
95 | elif self._lower_specified:
96 | return '≥{}'.format(self.lower)
97 | elif self._upper_specified:
98 | return '<{}'.format(self.upper)
99 | else:
100 | return '~'
101 |
102 | def _key(self):
103 | return self.lower if self._lower_specified else MIN_LOWER
104 |
105 |
106 | class BSectionCounter:
107 | def __init__(self, *indexes):
108 | self.indexes = indexes
109 |
110 | def feed(self, data):
111 | c = Counter({str(r): 0 for r in self.indexes})
112 | for d in data:
113 | c.update([str(r) for r in self.indexes if d in r])
114 | return c
115 |
116 | def feed_as_axises(self, data):
117 | rc = self.feed(data)
118 | keys = sorted(self.indexes, key=lambda x: x._key())
119 | return zip(*[(str(k), rc[str(k)]) for k in keys])
120 |
121 | @classmethod
122 | def from_simple(cls, *index_or_number_list):
123 | indexes = []
124 | for ion in index_or_number_list:
125 | if isinstance(ion, BIndex):
126 | indexes.append(ion)
127 | elif isinstance(ion, (tuple, list)) and len(ion) == 2:
128 | indexes.append(BRangeIndex(ion[0], ion[1]))
129 | else:
130 | indexes.append(BValueIndex(ion))
131 | return cls(*indexes)
132 |
133 | @classmethod
134 | def from_range(cls, start, end, step):
135 | indexes = [BRangeIndex(i, i + step) for i in range(start, end, step)]
136 | return cls(*indexes)
137 |
--------------------------------------------------------------------------------
/django_echarts/entities/__init__.py:
--------------------------------------------------------------------------------
1 | from .articles import (
2 | ChartInfo, ChartInfoManagerMixin, LocalChartInfoManager
3 | )
4 | from .base import DwString
5 | from .chart_widgets import NamedCharts, BlankChart
6 | from .containers import RowContainer, Container
7 | from .html_widgets import (
8 | HTMLBase, LinkItem, SeparatorItem, Menu, Jumbotron, Nav, Copyright, Message, ValuesPanel, ValueItem, Title,
9 | ElementEntity, LinkGroup
10 | )
11 | from .pages import (WidgetCollection, WidgetGetterMixin)
12 | from .styles import (bootstrap_table_class, material_table_class)
13 | from .uri import EntityURI
14 |
--------------------------------------------------------------------------------
/django_echarts/entities/base.py:
--------------------------------------------------------------------------------
1 | class DwString(str):
2 | """This is a str for django template string."""
3 |
4 | @classmethod
5 | def login_name(cls, login_text: str = '{{ request.user.username }}', un_login_text: str = 'Unknown'):
6 | code_list = [
7 | '{% if request.user.is_authenticated %}',
8 | login_text,
9 | '{% else %}',
10 | un_login_text,
11 | '{% endif %}'
12 | ]
13 | return cls(''.join(code_list))
14 |
--------------------------------------------------------------------------------
/django_echarts/entities/chart_widgets.py:
--------------------------------------------------------------------------------
1 | import warnings
2 | from dataclasses import dataclass, field
3 | from .containers import RowContainer
4 |
5 |
6 | @dataclass
7 | class BlankChart:
8 | """A interface class for tests only."""
9 | width: str = '900px'
10 | height: str = '500px'
11 | renderer: str = 'canvas'
12 | page_title: str = 'Blank Chart'
13 | theme: str = 'white'
14 | chart_id: str = 'dje_blank'
15 | options: dict = field(default_factory=dict)
16 | js_dependencies: list = field(default_factory=lambda: ['echarts']) # Use Simplified format.
17 | geojson: dict = None # added by django-echarts
18 |
19 | def __post_init__(self):
20 | self._is_geo_chart = False
21 |
22 | def dump_options(self) -> str:
23 | return "{}"
24 |
25 |
26 | class NamedCharts(RowContainer):
27 | """
28 | A data structure class containing multiple named charts.
29 | is_combine: if True, the collection will not contains this chart.
30 |
31 |
32 | """
33 |
34 | def __init__(self, page_title: str = 'EChart', col_chart_num: int = 0, is_combine: bool = False):
35 | super().__init__()
36 | self.page_title = page_title
37 | self._col_chart_num = col_chart_num
38 | self.is_combine = is_combine
39 | self.has_ref = is_combine
40 |
41 | def auto_layout(self):
42 | if self._col_chart_num != 0:
43 | span = int(12 / self._col_chart_num)
44 | self.set_spans(span)
45 | else:
46 | super().auto_layout()
47 |
48 | def add_chart(self, chart_obj, name=None):
49 | warnings.warn('This method is deprecated. Use NamedChart.add_widget instead.', DeprecationWarning, stacklevel=2)
50 | self.add_widget(chart_obj, name=name)
51 | return self
52 |
53 | def add(self, achart_or_charts):
54 | warnings.warn('This method is deprecated. Use NamedChart.add_widget instead.', DeprecationWarning, stacklevel=2)
55 | if not isinstance(achart_or_charts, (list, tuple, set)):
56 | achart_or_charts = achart_or_charts, # Make it a sequence
57 | for c in achart_or_charts:
58 | self.add_widget(c)
59 | return self
60 |
--------------------------------------------------------------------------------
/django_echarts/entities/containers.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | from itertools import zip_longest
3 | from typing import Dict, Generator, Tuple, Any, List, Union
4 |
5 |
6 | class LayoutCfg:
7 | __slots__ = ['ww', 'wh', 'span', 'offset']
8 |
9 | def __init__(self, ww: str = '', wh: str = '', span: int = 0, offset: int = 0):
10 | self.ww = ww
11 | self.wh = wh
12 | self.span = span
13 | self.offset = offset
14 |
15 | def __str__(self):
16 | return f''
17 |
18 |
19 | COL_TOTAL = 12
20 |
21 |
22 | class ContainerBase:
23 | """A container containing some widgets."""
24 |
25 | def __init__(self, *args, **kwargs):
26 | self._widgets = OrderedDict()
27 | self._layouts = {} # type: Dict[str,LayoutCfg]
28 | self.div_class = kwargs.get('div_class', '')
29 | self._layout_str = ''
30 |
31 | def add_widget(self, widget, name: str = None, width: str = "", height: str = "", span: int = 0,
32 | first: bool = False):
33 | """Add a widget in this container widget.
34 | If first set to True, insert at the beginning position in list."""
35 | name = name or 'c{}'.format(len(self._widgets))
36 | self._widgets[name] = widget
37 | lc = LayoutCfg(ww=width, wh=height, span=span)
38 | self._layouts[name] = lc
39 | if first:
40 | self._widgets.move_to_end(name, False)
41 | return self
42 |
43 | def auto_layout(self):
44 | """Auto compute col spans for each widget."""
45 | pass
46 |
47 | def __iter__(self):
48 | """Iter each widget."""
49 | for chart in self._widgets.values():
50 | yield chart
51 |
52 | def __len__(self):
53 | return len(self._widgets)
54 |
55 | def __getitem__(self, item):
56 | if isinstance(item, int):
57 | # c[1], Just compatible with Page
58 | return list(self._widgets.values())[item]
59 | return self._widgets[item]
60 |
61 | def iter_layout(self) -> Generator[Tuple[Any, LayoutCfg], None, None]:
62 | """Iter each widget and its layout config."""
63 | self.auto_layout()
64 | for name, widget in self._widgets.items():
65 | yield widget, self._layouts.get(name, LayoutCfg())
66 |
67 |
68 | class Container(ContainerBase):
69 | """General container."""
70 |
71 | def __init__(self, *args, div_class: str = '', **kwargs):
72 | super().__init__(div_class=div_class, *args, **kwargs)
73 |
74 |
75 | class RowContainer(ContainerBase):
76 | """A row container."""
77 |
78 | def set_spans(self, spans: Union[int, List[int]]):
79 | """Set span value for each widget.
80 | This should be called after all widgets is add."""
81 | if isinstance(spans, int):
82 | if spans == 0:
83 | span_list = [int(12 / len(self._widgets))] * len(self._layouts)
84 | else:
85 | span_list = [spans] * len(self._layouts)
86 | else:
87 | span_list = spans
88 | for lc, span in zip_longest(self._layouts.values(), span_list):
89 | if lc is None:
90 | continue
91 | lc.span = span or COL_TOTAL
92 |
93 | def auto_layout(self):
94 | """Auto compute col spans for each widget."""
95 | item_num = len([lc for lc in self._layouts.values() if lc.span == 0])
96 | if item_num == 0: # Each widget has its own span.
97 | return
98 | total_span = COL_TOTAL - sum([lc.span for lc in self._layouts.values() if lc.span])
99 | col_span = int(total_span / item_num)
100 | for lc in self._layouts.values():
101 | if lc.span == 0:
102 | lc.span = col_span
103 | lc.ww = '100%'
104 |
105 | def get_spans(self) -> Tuple[int]:
106 | return tuple([lc.span for lc in self._layouts.values()])
107 |
--------------------------------------------------------------------------------
/django_echarts/entities/layouts.py:
--------------------------------------------------------------------------------
1 | import re
2 | from functools import singledispatch
3 | from typing import List, Union
4 |
5 | __all__ = ['LayoutOpts', 'TYPE_LAYOUT_OPTS', 'any2layout']
6 |
7 | _defaults = {'l': 8, 'r': 8, 's': 8, 't': 6, 'b': 6, 'f': 12}
8 |
9 | _rm = re.compile(r'([lrtbfsa])(([1-9]|(1[12]))?)')
10 |
11 |
12 | class LayoutOpts:
13 | """Layout for user defined.
14 | """
15 | __slots__ = ['pos', 'spans', 'start']
16 |
17 | # l=left,r=right,s=stripped,t=top,b=bottom,f=full
18 | _defaults = {'l': 8, 'r': 8, 's': 8, 't': 6, 'b': 6, 'f': 12}
19 |
20 | _rm = re.compile(r'([lrtbfsa])(([1-9]|(1[12]))?)')
21 |
22 | def __init__(self, pos: str = 'r', spans: List[int] = None):
23 | self.pos = pos
24 | self.spans = spans or []
25 | self.start = pos in 'rb'
26 |
27 | def stripped_layout(self) -> 'LayoutOpts':
28 | if self.pos == 'r':
29 | return LayoutOpts(pos='l', spans=self.spans)
30 | elif self.pos == 'l':
31 | return LayoutOpts(pos='r', spans=self.spans)
32 | else:
33 | return self
34 |
35 | def __str__(self):
36 | return f''
37 |
38 |
39 | TYPE_LAYOUT_OPTS = Union[int, List[int], str]
40 |
41 |
42 | @singledispatch
43 | def any2layout(obj) -> LayoutOpts:
44 | raise TypeError('Can not parse LayOpts.')
45 |
46 |
47 | @any2layout.register(LayoutOpts)
48 | def _(obj) -> LayoutOpts:
49 | return obj
50 |
51 |
52 | @any2layout.register(int)
53 | def _(obj) -> LayoutOpts:
54 | return LayoutOpts(spans=[obj])
55 |
56 |
57 | @any2layout.register(list)
58 | def _(obj) -> LayoutOpts:
59 | return LayoutOpts(spans=obj)
60 |
61 |
62 | @any2layout.register(str)
63 | def _(obj) -> LayoutOpts:
64 | m = _rm.match(obj)
65 | if m:
66 | pos, cols = m.group(1), m.group(2)
67 | if cols is None or cols == '':
68 | cols = _defaults.get(pos, 8)
69 | else:
70 | cols = int(cols)
71 | return LayoutOpts(pos, [cols])
72 | else:
73 | raise ValueError(f'This layout can not be parsed: {obj}')
74 |
--------------------------------------------------------------------------------
/django_echarts/entities/pages.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | from abc import abstractmethod
3 | from typing import List, Union, Tuple, Any, Optional
4 | import warnings
5 |
6 | from .uri import EntityURI
7 | from .articles import ChartInfo
8 | from .containers import RowContainer, Container
9 | from .layouts import LayoutOpts, TYPE_LAYOUT_OPTS, any2layout
10 |
11 |
12 | class WidgetGetterMixin:
13 |
14 | @abstractmethod
15 | def get_chart_and_info(self, name: str) -> Tuple[Optional[Any], bool, Optional[ChartInfo]]:
16 | """Return a pycharts chart object."""
17 | warnings.warn('This method is deprecated, use get_chart_and_info_by_uri instead.', DeprecationWarning,
18 | stacklevel=2)
19 | pass
20 |
21 | @abstractmethod
22 | def get_html_widget(self, name: str) -> Any:
23 | """Return a html widget object."""
24 | warnings.warn('This method is deprecated, use get_widget_by_uri instead.', DeprecationWarning, stacklevel=2)
25 | pass
26 |
27 | @abstractmethod
28 | def get_widget_by_name(self, name: str) -> Any:
29 | warnings.warn('This method is deprecated, use get_widget_by_uri instead.', DeprecationWarning, stacklevel=2)
30 | pass
31 |
32 | @abstractmethod
33 | def get_chart_and_info_by_uri(self, uri: EntityURI) -> Tuple[Any, bool, Optional[ChartInfo]]:
34 | pass
35 |
36 | @abstractmethod
37 | def get_widget_by_uri(self, uri: EntityURI):
38 | pass
39 |
40 |
41 | class WidgetCollection(Container):
42 | """A row-list-container"""
43 | widget_type = 'Collection'
44 |
45 | def __init__(self, name: str, title: str = None, layout: TYPE_LAYOUT_OPTS = 'a'):
46 | super().__init__()
47 | self.name = name
48 | self.title = title
49 | self._user_defined_layout = any2layout(layout)
50 | self._ref_config_list = [] # type: List
51 | self._row_no = 0
52 |
53 | def start_(self):
54 | self._widgets = OrderedDict()
55 | self._row_no = 0
56 | return self
57 |
58 | def add_chart_widget(self, chart_name: str, layout: TYPE_LAYOUT_OPTS = 'l8'):
59 | self._ref_config_list.append([True, layout, EntityURI.from_str(chart_name, catalog='chart')])
60 | return self
61 |
62 | def add_html_widget(self, widget_names: List, layout: TYPE_LAYOUT_OPTS = 0):
63 | widget_uri_list = [EntityURI.from_str(name, catalog='widget') for name in widget_names]
64 | self._ref_config_list.append([False, layout, *widget_uri_list])
65 |
66 | def auto_mount(self, widget_container: WidgetGetterMixin):
67 | self.start_()
68 | for is_chart, layout_str, *uri_list in self._ref_config_list:
69 | if is_chart:
70 | chart_uri = uri_list[0]
71 | chart_obj, _, info = widget_container.get_chart_and_info_by_uri(chart_uri)
72 | self.pack_chart_widget(chart_obj, info, row_no=self._row_no)
73 | else:
74 | widget_list = [widget_container.get_widget_by_uri(uri) for uri in uri_list]
75 | self.pack_html_widget(widget_list)
76 |
77 | def pack_chart_widget(self, chart_obj, info: ChartInfo, ignore_ref: bool = True, layout: str = 'l8',
78 | row_no: int = 0):
79 | r_layout = self.compute_layout(any2layout(layout))
80 | if isinstance(chart_obj, RowContainer):
81 | if getattr(chart_obj, 'has_ref', False) and ignore_ref:
82 | return
83 | row_widget = chart_obj
84 | row_widget.set_spans(0)
85 | row_widget.add_widget(info, first=r_layout.start, span=12)
86 | else:
87 | # pyecharts.charts.Base
88 | row_widget = RowContainer()
89 | row_widget.add_widget(chart_obj, span=r_layout.spans[0])
90 | row_widget.add_widget(info, first=r_layout.start)
91 | self.add_widget(row_widget)
92 | self._row_no += 1
93 |
94 | def pack_html_widget(self, widget_list: List, spans: Union[int, List[int]] = 0, row_no: int = 0):
95 | row_widget = RowContainer()
96 | for widget in widget_list:
97 | row_widget.add_widget(widget)
98 | if spans != 0:
99 | row_widget.set_spans(spans)
100 | self.add_widget(row_widget)
101 | self._row_no += 1
102 |
103 | def compute_layout(self, row_layout: LayoutOpts):
104 | if self._user_defined_layout.pos == 'a':
105 | return row_layout
106 | elif self._user_defined_layout.pos == 's':
107 | if self._row_no % 2 == 1:
108 | return row_layout.stripped_layout()
109 | else:
110 | return row_layout
111 | else:
112 | return row_layout
113 |
--------------------------------------------------------------------------------
/django_echarts/entities/styles.py:
--------------------------------------------------------------------------------
1 | """
2 | A shortcut method for builtin theme styles.
3 | """
4 |
5 |
6 | def bootstrap_table_class(border=False, borderless=False, striped=False, size=None):
7 | class_list = ['table', 'table-responsive']
8 | if border:
9 | class_list.append('table-bordered')
10 | if borderless:
11 | class_list.append('table-borderless')
12 | if striped:
13 | class_list.append('table-striped')
14 | if size:
15 | class_list.append(f'table-{size}')
16 | return ' '.join(class_list)
17 |
18 |
19 | def material_table_class(striped=False, center=False):
20 | class_list = ['responsive-table', ]
21 | if striped:
22 | class_list.append('striped')
23 | if center:
24 | class_list.append('centered')
25 | return ' '.join(class_list)
26 |
--------------------------------------------------------------------------------
/django_echarts/entities/uri.py:
--------------------------------------------------------------------------------
1 | from typing import List, Union, Generator
2 | import itertools
3 |
4 |
5 | class EntityURI:
6 | __empty = None
7 |
8 | CATALOG_CHART = 'chart'
9 | CATALOG_INFO = 'info'
10 | CATALOG_WIDGET = 'widget'
11 | CATALOG_LAYOUT = 'layout'
12 | CATALOG_COLLECTION = 'collection'
13 |
14 | CATALOG_VIEW = 'view'
15 | # TODO description attr
16 |
17 | __slots__ = ['catalog', 'name', 'params', '_params_path']
18 |
19 | def __init__(self, catalog: str, name: str, params: dict = None):
20 | self.catalog = catalog
21 | self.name = name
22 | self.params = params or {}
23 | _items = []
24 | for k, v in self.params.items():
25 | _items.extend([k, str(v)])
26 | self._params_path = '/'.join(_items)
27 |
28 | @property
29 | def params_path(self):
30 | return self._params_path
31 |
32 | def __str__(self):
33 | if len(self.params):
34 | return f'{self.catalog}:{self.name}/{self.params_path}'
35 | else:
36 | return f'{self.catalog}:{self.name}'
37 |
38 | __repr__ = __str__
39 |
40 | def is_empty(self):
41 | return not self.catalog or self.catalog == 'empty'
42 |
43 | @classmethod
44 | def empty(cls):
45 | if cls.__empty is None:
46 | cls.__empty = cls('empty', '')
47 | return cls.__empty
48 |
49 | @classmethod
50 | def from_params_path(cls, catalog: str, name: str, params_path: str, param_names: list = None):
51 | """Parse URI param string to param dict.
52 | param_names is required when param value contains '/' character.
53 | """
54 | params = {}
55 | if not params_path:
56 | return cls(catalog, name, params)
57 | kw = params_path.split('/')
58 | if param_names:
59 | name2pos = []
60 | for pn in param_names:
61 | try:
62 | pos = kw.index(pn)
63 | name2pos.append((pn, pos))
64 | except IndexError:
65 | pass
66 | name2pos.sort(key=lambda _item: _item[1])
67 | for i, item in enumerate(name2pos):
68 | pname, npos = item
69 | start_pos = npos + 1
70 | if i == len(name2pos) - 1:
71 | end_pos = len(kw)
72 | else:
73 | end_pos = name2pos[i + 1][1]
74 | if end_pos - start_pos == 1:
75 | value = kw[start_pos]
76 | else:
77 | value = '/'.join(kw[start_pos:end_pos])
78 | params[pname] = value
79 | else:
80 | for i in range(0, len(kw), 2):
81 | params[kw[i]] = kw[i + 1]
82 | return cls(catalog, name, params)
83 |
84 | @classmethod
85 | def from_str(cls, s: str, catalog: str = None, param_names: list = None):
86 | pos1 = s.find(':')
87 | if pos1 == -1:
88 | _catalog, st = catalog, s
89 | else:
90 | _catalog, st = s[:pos1], s[pos1 + 1:]
91 | if len(st) == 0:
92 | st = _catalog
93 | _catalog = catalog
94 | pos = st.find('/')
95 | if pos == -1:
96 | name, params_path = st, ''
97 | else:
98 | name, params_path = st[:pos], st[pos + 1:]
99 | return cls.from_params_path(_catalog, name, params_path, param_names)
100 |
101 |
102 | USER_PARAMS_CONFIGS_TYPE = Union[List[dict], dict]
103 |
104 |
105 | def parse_params_choices(config: USER_PARAMS_CONFIGS_TYPE) -> Generator[dict, None, None]:
106 | """
107 | [{'year':2021, 'month':1},{'year':2021, 'month':2},]
108 | {'year':[2021,2022], 'month':[1,2]}
109 | """
110 | if isinstance(config, List):
111 | for item in config:
112 | yield item
113 | else:
114 | names = []
115 | values_list = []
116 | for name, values in config.items():
117 | names.append(name)
118 | values_list.append(values)
119 | for t in itertools.product(*values_list):
120 | item = dict((k, v) for k, v in zip(names, t))
121 | yield item
122 |
123 |
124 | class ParamsConfig:
125 | __empty__ = None
126 | __slots__ = ['choices']
127 |
128 | def __init__(self, choices: USER_PARAMS_CONFIGS_TYPE = None):
129 | self.choices = choices or []
130 |
131 | def __iter__(self):
132 | for param_dic in parse_params_choices(self.choices):
133 | yield param_dic
134 |
135 | @classmethod
136 | def empty(cls):
137 | if ParamsConfig.__empty__ is None:
138 | ParamsConfig.__empty__ = ParamsConfig()
139 | return ParamsConfig.__empty__
140 |
--------------------------------------------------------------------------------
/django_echarts/geojson.py:
--------------------------------------------------------------------------------
1 | """A extension for geojson."""
2 | import warnings
3 | from collections import namedtuple
4 |
5 | from django_echarts.custom_maps import use_custom_map, custom_map_url
6 |
7 | __all__ = ['use_geojson', 'geojson_url']
8 |
9 | warnings.warn('The django_echarts.geojson is deprecated.Use django_echarts.custom_maps instead.', DeprecationWarning)
10 |
11 | GeojsonItem = namedtuple('GeojsonItem', 'map_name url')
12 |
13 |
14 | def use_geojson(chart_obj, map_name: str, url: str = None):
15 | """Register and use geojson map for a chart."""
16 | use_custom_map(chart_obj, map_name, url)
17 |
18 |
19 | def geojson_url(geojson_name: str) -> str:
20 | """Get default url for a geojson file."""
21 | return custom_map_url(geojson_name)
22 |
--------------------------------------------------------------------------------
/django_echarts/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/management/__init__.py
--------------------------------------------------------------------------------
/django_echarts/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/management/commands/__init__.py
--------------------------------------------------------------------------------
/django_echarts/management/commands/_downloader.py:
--------------------------------------------------------------------------------
1 | import os
2 | from typing import List
3 |
4 | from django.core.management.base import BaseCommand
5 |
6 | from django_echarts.conf import DJANGO_ECHARTS_SETTINGS
7 | from django_echarts.core.localfiles import DownloaderResource
8 | from django_echarts.renders import get_js_dependencies
9 | from django_echarts.stores.entity_factory import factory
10 | from django_echarts.utils.downloader import download_files
11 |
12 |
13 | class DownloadBaseCommand(BaseCommand):
14 |
15 | def do_action(self, chart_names: List, dep_names: List, theme_name: str, repo_name: str, fake: bool):
16 |
17 | all_resources = [] # type: List[DownloaderResource]
18 | if theme_name:
19 | all_resources += self.resolve_theme(theme_name)
20 | all_dep_names = dep_names or []
21 | if chart_names:
22 | for cname in chart_names:
23 | all_dep_names += self.resolve_chart(cname)
24 | if all_dep_names:
25 | all_resources += self.resolve_dep(all_dep_names, repo_name)
26 | if fake:
27 | for i, res in enumerate(all_resources):
28 | if os.path.exists(res.local_path):
29 | res.exists = True
30 | msg = self.style.SUCCESS(' Local Path: {}'.format(res.local_path))
31 | else:
32 | res.exists = False
33 | msg = self.style.WARNING(' Local Path: {}'.format(res.local_path))
34 | self.stdout.write('[Resource #{:02d}] {}: Catalog: {}'.format(i + 1, res.label, res.catalog))
35 | self.stdout.write(' Remote Url: {}'.format(res.remote_url))
36 | self.stdout.write(' Static Url: {}'.format(res.ref_url))
37 | self.stdout.write(msg)
38 | else:
39 | file_info_list = [(res.remote_url, res.local_path) for res in all_resources if not res.exists]
40 | download_files(file_info_list)
41 | self.stdout.write(self.style.SUCCESS('Task completed!'))
42 |
43 | def resolve_dep(self, dep_names, repo_name) -> List[DownloaderResource]:
44 | manager = DJANGO_ECHARTS_SETTINGS.dependency_manager
45 | return manager.get_download_resources(dep_names, repo_name)
46 |
47 | def resolve_theme(self, theme_name) -> List[DownloaderResource]:
48 | if theme_name:
49 | theme = DJANGO_ECHARTS_SETTINGS.create_theme(theme_name)
50 | else:
51 | theme = DJANGO_ECHARTS_SETTINGS.theme
52 | return DJANGO_ECHARTS_SETTINGS.theme_manger.get_download_resources(theme)
53 |
54 | def resolve_chart(self, chart_name) -> List[str]:
55 | chart_obj = factory.get_chart_widget(chart_name)
56 | if not chart_obj:
57 | self.stdout.write(self.style.WARNING('The chart with name does not exits.'))
58 | return []
59 | dep_names = get_js_dependencies(chart_obj)
60 | return dep_names
61 |
--------------------------------------------------------------------------------
/django_echarts/management/commands/download.py:
--------------------------------------------------------------------------------
1 | from ._downloader import DownloadBaseCommand
2 |
3 |
4 | class Command(DownloadBaseCommand):
5 | help = 'Download one or some dependency files from remote CDN to project staticfile dirs.'
6 |
7 | def add_arguments(self, parser):
8 | parser.add_argument('--chart', '-c', nargs='+', type=str, help='The name of chart.')
9 | parser.add_argument('--dep', '-d', nargs='+', type=str, help='The name of dependency files.')
10 | parser.add_argument('--theme', '-t', help='The name of theme.')
11 | parser.add_argument('--repo', '-r', help='The name of dependency repo.', default='pyecharts')
12 | parser.add_argument('--force', '-f', action='store_true')
13 |
14 | def handle(self, *args, **options):
15 | chart_names = options.get('chart', [])
16 | dep_names = options.get('dep', [])
17 | theme_name = options.get('theme')
18 | repo_name = options.get('repo')
19 | self.do_action(chart_names=chart_names, dep_names=dep_names, theme_name=theme_name, repo_name=repo_name,
20 | fake=False)
21 |
--------------------------------------------------------------------------------
/django_echarts/management/commands/info.py:
--------------------------------------------------------------------------------
1 | from ._downloader import DownloadBaseCommand
2 |
3 |
4 | class Command(DownloadBaseCommand):
5 | help = 'Show one or some dependency files.'
6 |
7 | def add_arguments(self, parser):
8 | parser.add_argument('--chart', '-c', nargs='+', type=str, help='The name of chart.')
9 | parser.add_argument('--dep', '-d', nargs='+', type=str, help='The name of dependency files.')
10 | parser.add_argument('--theme', '-t', help='The name of theme.')
11 | parser.add_argument('--repo', '-r', help='The name of dependency repo.', default='pyecharts')
12 |
13 | def handle(self, *args, **options):
14 | chart_names = options.get('chart', [])
15 | dep_names = options.get('dep', [])
16 | theme_name = options.get('theme')
17 | repo_name = options.get('repo')
18 | self.do_action(chart_names=chart_names, dep_names=dep_names, theme_name=theme_name, repo_name=repo_name,
19 | fake=True)
20 |
--------------------------------------------------------------------------------
/django_echarts/management/commands/startsite.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | from datetime import date
3 |
4 | from django.core.management.base import BaseCommand
5 | from django.template.loader import render_to_string
6 | from django_echarts import __version__
7 |
8 |
9 | class Command(BaseCommand):
10 | help = 'Auto generate site_views.py file.'
11 |
12 | def add_arguments(self, parser):
13 | parser.add_argument('output', type=str, help='The output file.')
14 | def_year = date.today().year
15 | parser.add_argument('--site-title', '-t', type=str, help='The title of site.', default='Echarts Demo')
16 | parser.add_argument('--empty', '-e', action='store_true')
17 | parser.add_argument('--start-year', '-y', type=int, help='The start year.', default=def_year)
18 | parser.add_argument('--powered-by', '-p', type=str, help='The principal of copyright.',
19 | default='Django-Echarts')
20 | parser.add_argument('--force', '-f', action='store_true')
21 |
22 | def handle(self, *args, **options):
23 | output = options.get('output')
24 | if output[-3:] != '.py':
25 | output += '.py'
26 | force = options.get('force', False)
27 | if not force and os.path.exists(output):
28 | self.stdout.write(self.style.ERROR(f'The file {output} exists! Add -f to overwrite it.'))
29 | return
30 | site_title = options.get('site_title')
31 | start_year = options.get('start_year')
32 | powered_by = options.get('powered_by')
33 | empty_chart = options.get('empty', False)
34 | self.generate_code_spinet(output, site_title, start_year, powered_by, empty_chart)
35 |
36 | def generate_code_spinet(self, output, site_title, start_year, powered_by, empty_chart):
37 | context = {
38 | 'site_title': site_title,
39 | 'start_year': start_year,
40 | 'powered_by': powered_by,
41 | 'use_chart': not empty_chart,
42 | 'version': __version__,
43 | }
44 | s = render_to_string('snippets/first_views.py.tpl', context=context)
45 | with open(output, 'w') as f:
46 | f.write(s)
47 | self.stdout.write(self.style.SUCCESS(f'File {output} generated success!'))
48 |
--------------------------------------------------------------------------------
/django_echarts/management/commands/starttpl.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 |
4 | from django.apps import apps
5 | from django.conf import settings
6 | from django.core.management.base import BaseCommand
7 | from django.template import engines
8 | from django_echarts.conf import DJANGO_ECHARTS_SETTINGS
9 |
10 |
11 | def concat_if(s: str, fix: str):
12 | if s[-len(fix):] != fix:
13 | return s + fix
14 | else:
15 | return s
16 |
17 |
18 | def get_theme_template_dir(app_name, *args) -> str:
19 | for app_config in apps.get_app_configs():
20 | if app_name in app_config.name:
21 | return os.path.join(app_config.path, 'templates', *args)
22 |
23 |
24 | def get_dest_template_dir():
25 | django_engine = engines['django']
26 | if django_engine.dirs:
27 | return django_engine.dirs[0]
28 | else:
29 | return os.path.join(str(settings.BASE_DIR), 'templates')
30 |
31 |
32 | class Command(BaseCommand):
33 | help = 'Copy the builtin template files to your project templates.'
34 |
35 | def add_arguments(self, parser):
36 | """
37 | Examples:
38 | starttpl
39 | starttpl bootstrap5 # Print all template_names in this theme.
40 | starttpl bootstrap5 --all # Copy all template_names in this theme.
41 | starttpl -t bootstrap5 -n base # Copy templates/base.html
42 | starttpl base
43 | starttpl blank -o my_page
44 | starttpl list all
45 | """
46 | parser.add_argument('--theme', type=str, help='The name of theme.', default='bootstrap5',
47 | choices=['bootstrap3', 'bootstrap5', 'material'])
48 | parser.add_argument('tpl_name', type=str, nargs='+', help='The name of template file.', )
49 | parser.add_argument('--output', '-o', type=str, help='The output filename')
50 | parser.add_argument('--force', '-f', action='store_true', help='Whether to copy if the file exists.')
51 |
52 | def handle(self, *args, **options):
53 | theme_name = options.get('theme')
54 | if not theme_name:
55 | theme = DJANGO_ECHARTS_SETTINGS.theme
56 | else:
57 | theme = DJANGO_ECHARTS_SETTINGS.create_theme(theme_name)
58 | theme_name = theme.name
59 | template_names = options.get('tpl_name', [])
60 | if template_names:
61 | show_action = False
62 | else:
63 | template_names = self.get_all_template_names(theme_name)
64 | show_action = True
65 | if show_action:
66 | self.stdout.write(f'The template names of Theme [{theme_name}]:')
67 | for name in template_names:
68 | self.stdout.write(f'\t{name}')
69 | self.stdout.write('\n Start to custom a template: python manage.py starttpl -n blank -o my_page')
70 | else:
71 | template_name = template_names[0]
72 | output = options.get('output')
73 | force_action = options.get('force')
74 | self.copy_template_files(theme_name, template_name, output, force_action)
75 |
76 | def get_all_template_names(self, theme_name):
77 | theme_dir = get_theme_template_dir(theme_name)
78 | template_names = []
79 | for root, dirs, files in os.walk(theme_dir):
80 | for f in files:
81 | template_names.append(os.path.join(root, f)[len(theme_dir) + 1:])
82 | return template_names
83 |
84 | def copy_template_files(self, theme_name, template_name, output, force_action):
85 | template_name = concat_if(template_name, '.html')
86 | if output:
87 | output = concat_if(output, '.html')
88 | else:
89 | output = template_name
90 | from_path = get_theme_template_dir(theme_name, template_name)
91 | # From path
92 | if not os.path.exists(from_path):
93 | self.stdout.write(self.style.WARNING(f' {template_name}, skipped!'))
94 | pro_template_dir = get_dest_template_dir()
95 | to_path = os.path.join(pro_template_dir, output)
96 | if os.path.exists(to_path) and not force_action:
97 | self.stdout.write(self.style.WARNING(f' {output}, Exists! Add -f option to override write.'))
98 | else:
99 | try:
100 | os.makedirs(os.path.dirname(to_path), exist_ok=True)
101 | shutil.copy(from_path, to_path)
102 | self.stdout.write(self.style.SUCCESS(f' {output}, Success!'))
103 | except BaseException as e:
104 | self.stdout.write(self.style.ERROR(f' {output}, Fail! {str(e)}'))
105 |
--------------------------------------------------------------------------------
/django_echarts/renders/__init__.py:
--------------------------------------------------------------------------------
1 | from .html import render_widget
2 | from .javascript import get_js_dependencies, flat_chart
3 |
--------------------------------------------------------------------------------
/django_echarts/renders/html.py:
--------------------------------------------------------------------------------
1 | from functools import singledispatch
2 | from typing import Union
3 |
4 | import htmlgenerator as hg
5 | from borax.htmls import HTMLString, html_tag
6 | from borax.strings import camel2snake
7 | from django.template import engines
8 | from django.template.loader import get_template
9 | from django.utils.safestring import SafeString
10 | from prettytable import PrettyTable
11 | from pyecharts.charts.base import Base
12 | from pyecharts.components.table import Table
13 |
14 | from django_echarts.entities import (
15 | ValuesPanel, LinkItem, Menu, NamedCharts, DwString, RowContainer, Container, HTMLBase, BlankChart
16 | )
17 |
18 |
19 | def _to_css_length(val):
20 | if isinstance(val, (int, float)):
21 | return '{}px'.format(val)
22 | else:
23 | return val
24 |
25 |
26 | @singledispatch
27 | def render_widget(widget, **kwargs) -> SafeString:
28 | if hasattr(widget, '__html__') and callable(widget.__html__):
29 | return widget.__html__()
30 | # raise WidgetNotRegisteredError(widget)
31 | return SafeString(f'Unknown widget type:{widget.__class__.__name__}
')
32 |
33 |
34 | @render_widget.register(type(None))
35 | def render_none(widget, **kwargs) -> SafeString:
36 | return SafeString('')
37 |
38 |
39 | @render_widget.register(SafeString)
40 | @render_widget.register(HTMLString)
41 | def render_html(widget, **kwargs) -> SafeString:
42 | return widget
43 |
44 |
45 | @render_widget.register(Base)
46 | @render_widget.register(BlankChart)
47 | def render_chart(widget, **kwargs) -> SafeString:
48 | width = kwargs.get('width') or widget.width
49 | height = kwargs.get('height') or widget.height
50 | html = ''.format(
51 | chart_id=widget.chart_id,
52 | width=_to_css_length(width),
53 | height=_to_css_length(height)
54 | )
55 | return SafeString(html)
56 |
57 |
58 | @render_widget.register(Container)
59 | @render_widget.register(HTMLBase)
60 | @render_widget.register(NamedCharts)
61 | @render_widget.register(RowContainer)
62 | @render_widget.register(ValuesPanel)
63 | def render_with_tpl(widget, **kwargs) -> SafeString:
64 | if hasattr(widget, '__html__') and callable(widget.__html__):
65 | return widget.__html__()
66 | if 'tpl' in kwargs:
67 | tpl_name = kwargs['tpl']
68 | else:
69 | widget_name = camel2snake(widget.__class__.__name__)
70 | if widget_name in ('values_panel', 'named_charts'):
71 | widget_name = 'row_container'
72 | elif widget_name == 'widget_collection':
73 | widget_name = 'container'
74 | tpl_name = 'widgets/{}.html'.format(widget_name)
75 | tpl = get_template(tpl_name)
76 | return SafeString(tpl.render({'widget': widget}))
77 |
78 |
79 | @render_widget.register(Table)
80 | @render_widget.register(PrettyTable)
81 | def render_table(widget, **kwargs) -> SafeString:
82 | if isinstance(widget, Table):
83 | html_content = widget.html_content
84 | else:
85 | html_content = widget.get_html_string(**kwargs)
86 | html_content = f'{html_content}
'
87 | return SafeString(html_content)
88 |
89 |
90 | @render_widget.register(LinkItem)
91 | @render_widget.register(Menu)
92 | def render_link(widget, **kwargs) -> Union[SafeString, HTMLString]:
93 | context = kwargs.get('context')
94 | class_ = kwargs.get('class_')
95 | params = {'href': widget.url or 'javascript:;'}
96 | if isinstance(widget.text, DwString):
97 | django_engine = engines['django']
98 | template_obj = django_engine.from_string(widget.text)
99 | fields = ['request', ]
100 | context_dic = {}
101 | for f in fields:
102 | if f in context:
103 | context_dic[f] = context[f]
104 | params['content'] = template_obj.render(context_dic)
105 | else:
106 | params['content'] = widget.text
107 | if class_:
108 | params['class_'] = class_
109 | if isinstance(widget, LinkItem) and widget.new_page:
110 | params['target'] = '_blank'
111 | return html_tag('a', **params)
112 |
113 |
114 | @render_widget.register(hg.BaseElement)
115 | def render_library_html(widget: hg.BaseElement, **kwargs):
116 | context = kwargs.get('context', {})
117 | return hg.render(widget, context)
118 |
--------------------------------------------------------------------------------
/django_echarts/renders/javascript.py:
--------------------------------------------------------------------------------
1 | from functools import singledispatch
2 |
3 | from django_echarts.entities import (
4 | NamedCharts, ValuesPanel, RowContainer, Container, WidgetCollection, HTMLBase, BlankChart
5 | )
6 | from prettytable import PrettyTable
7 | from pyecharts.charts.base import Base
8 | from pyecharts.components.table import Table
9 | from pyecharts.globals import ThemeType
10 | from htmlgenerator.base import BaseElement
11 |
12 | __all__ = ['flat_chart', 'get_js_dependencies']
13 |
14 |
15 | @singledispatch
16 | def flat_chart(widget):
17 | """Get chart object list from a widget."""
18 | raise TypeError(f'Can not flat widget type: {widget.__class__.__name__}')
19 |
20 |
21 | @flat_chart.register(Base)
22 | @flat_chart.register(BlankChart)
23 | def flat_base(widget: Base):
24 | return [widget]
25 |
26 |
27 | @flat_chart.register(type(None))
28 | @flat_chart.register(PrettyTable)
29 | @flat_chart.register(Table)
30 | @flat_chart.register(HTMLBase)
31 | @flat_chart.register(BaseElement)
32 | def flat_not_chart(widget):
33 | return []
34 |
35 |
36 | @flat_chart.register(NamedCharts)
37 | @flat_chart.register(ValuesPanel)
38 | @flat_chart.register(Container)
39 | @flat_chart.register(RowContainer)
40 | @flat_chart.register(WidgetCollection)
41 | @flat_chart.register(tuple)
42 | @flat_chart.register(list)
43 | def flat_named_charts(widget):
44 | chart_list = []
45 | for chart in widget:
46 | chart_list.extend(flat_chart(chart))
47 | return chart_list
48 |
49 |
50 | _ECHARTS_LIB_NAMES = [
51 | 'echarts.common', 'echarts.common.min',
52 | 'echarts', 'echarts.min', 'echartsgl', 'echarts-gl', 'echarts-gl.min',
53 | 'echarts.simple', 'echarts.simple.min',
54 | 'extension/bmap', 'extension/bmap.min',
55 | 'extension/dataTool', 'extension/dataTool.min'
56 | ]
57 |
58 |
59 | def get_js_dependencies(widget, global_theme: str = None):
60 | dep_list = []
61 | widget_list = []
62 | if isinstance(widget, (list, tuple)):
63 | for w in widget:
64 | if isinstance(w, str):
65 | dep_list.append(w)
66 | else:
67 | widget_list.append(w)
68 | elif isinstance(widget, str):
69 | dep_list.append(widget)
70 | else:
71 | widget_list.append(widget)
72 | chart_list = flat_chart(widget_list)
73 |
74 | def _deps(_chart):
75 | if isinstance(_chart.js_dependencies, list):
76 | return _chart.js_dependencies
77 | if hasattr(_chart.js_dependencies, 'items'):
78 | return list(_chart.js_dependencies.items) # pyecharts.commons.utils.OrderedSet
79 | raise ValueError('Can not parse js_dependencies.')
80 |
81 | front_items = []
82 | for chart in chart_list:
83 | _dep_list = _deps(chart)
84 |
85 | for dep in _dep_list:
86 | # exclude custom map.
87 | if dep.endswith('.geojson') or dep.endswith('.json') or dep.endswith('.svg'):
88 | continue
89 | if dep not in dep_list and dep not in front_items:
90 | if dep in _ECHARTS_LIB_NAMES:
91 | front_items.append(dep)
92 | else:
93 | dep_list.append(dep)
94 | if hasattr(chart, 'theme'):
95 | theme_dep = global_theme or chart.theme
96 | if theme_dep not in ThemeType.BUILTIN_THEMES and theme_dep not in dep_list:
97 | dep_list.append(theme_dep)
98 | front_items.sort(key=lambda x: _ECHARTS_LIB_NAMES.index(x))
99 | return front_items + dep_list
100 |
--------------------------------------------------------------------------------
/django_echarts/site_exts.py:
--------------------------------------------------------------------------------
1 | from django.urls import reverse_lazy
2 | from django_echarts.entities.uri import EntityURI
3 | from typing import Union
4 |
5 |
6 | def reverse_chart_url(uri_or_name: Union[EntityURI, str], params_dic: dict = None):
7 | """
8 | reverse url of a single chart view in DJESite.
9 | The following three signatures are supported.
10 |
11 | reverse_chart_url(uri:EntityURI)
12 | reverse_chart_url(name:str)
13 | reverse_chart_url(name:str, params_dic:dict)
14 | """
15 | if isinstance(uri_or_name, EntityURI):
16 | uri = uri_or_name
17 | else:
18 | uri = EntityURI('chart', uri_or_name, params_dic)
19 | if len(uri.params):
20 | return reverse_lazy('dje_chart_single', args=(uri.name, uri.params_path))
21 | else:
22 | return reverse_lazy('dje_chart_single', args=(uri.name,))
23 |
--------------------------------------------------------------------------------
/django_echarts/starter/__init__.py:
--------------------------------------------------------------------------------
1 | from .optstools import SiteOpts, SiteOptsForm
2 | from .sites import DJESite, DJESiteBackendView
3 |
--------------------------------------------------------------------------------
/django_echarts/starter/optstools.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass, field
2 | from typing import Optional, List
3 |
4 | from django import forms
5 | from django_echarts.conf import DJANGO_ECHARTS_SETTINGS
6 | from typing_extensions import Literal
7 |
8 |
9 | @dataclass
10 | class SiteOpts:
11 | """The opts for DJESite."""
12 | list_layout: Literal['grid', 'list'] = 'grid'
13 | paginate_by: Optional[int] = 0
14 | nav_top_fixed: bool = False
15 | detail_tags_position: Literal['none', 'top', 'bottom'] = 'top'
16 | detail_sidebar_shown: bool = True
17 | nav_shown_pages: List = field(default_factory=lambda: ['home'])
18 |
19 |
20 | page_num_choices = ((0, '不分页'), (5, '5'), (10, '10'), (15, '15'), (20, '20'))
21 | bool_choices = ((True, 'Yes'), (False, 'No'))
22 |
23 |
24 | class SiteOptsForm(forms.Form):
25 | nav_top_fixed = forms.TypedChoiceField(label='是否固定顶部导航栏', coerce=lambda x: x == 'True', choices=bool_choices)
26 | list_layout = forms.ChoiceField(label='列表页布局', choices=(('list', '列表布局'), ('grid', '网格布局')))
27 | paginate_by = forms.TypedChoiceField(label='每页项目数', coerce=int, initial=0, choices=page_num_choices)
28 | theme_palette_name = forms.ChoiceField(label='主题调色')
29 |
30 | def __init__(self, *args, **kwargs):
31 | super().__init__(*args, **kwargs)
32 | choices = [(k, k) for k in DJANGO_ECHARTS_SETTINGS.theme_manger.available_palettes]
33 | self.fields['theme_palette_name'].choices = choices
34 |
--------------------------------------------------------------------------------
/django_echarts/starter/params_forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 |
4 | class ParamsForm:
5 | name = forms.CharField(label='Name', required=True, max_length=200)
6 | params_dic = forms.JSONField(label='Params(JSON)', max_length=500)
7 |
--------------------------------------------------------------------------------
/django_echarts/stores/__init__.py:
--------------------------------------------------------------------------------
1 | from .entity_factory import factory
2 |
--------------------------------------------------------------------------------
/django_echarts/templates/snippets/echarts_init_options.tpl:
--------------------------------------------------------------------------------
1 | {% for custom_map_item, chart_list in structured_data %}
2 | {% if custom_map_item.url %}
3 | $.get({
4 | url: '{{ custom_map_item.url }}',
5 | dataType: '{{ custom_map_item.ajax_data_type }}'
6 | }).done(function(mapData){
7 | echarts.registerMap("{{ custom_map_item.map_name }}", {{ custom_map_item.param_str }});
8 | {% endif %}
9 | {% for c in chart_list %}
10 | var chart_{{ c.chart_id }} = echarts.init(
11 | document.getElementById('{{ c.chart_id }}'), '{{ c.dje_echarts_theme }}', {renderer: '{{ c.renderer }}'});
12 | {% for js in c.js_functions.items %}
13 | {{ js|safe }}
14 | {% endfor %}
15 | var option_{{ c.chart_id }} = {{ c.dump_options|default:'{}'|safe }};
16 | chart_{{ c.chart_id }}.setOption(option_{{ c.chart_id }});
17 | {% if c.is_geo_chart %}
18 | var bmap = chart_{{ c.chart_id }}.getModel().getComponent('bmap').getBMap();
19 | {% if c.bmap_js_functions %}
20 | {% for fn in c.bmap_js_functions.items %}
21 | {{ fn|safe }}
22 | {% endfor %}
23 | {% endif %}
24 | {% endif %}
25 | window.addEventListener('resize', function(){
26 | chart_{{ c.chart_id }}.resize();
27 | });
28 | {% endfor %}
29 |
30 | {% if custom_map_item.url %}
31 | }).fail(function(jqXHR, textStatus, error){
32 | $("#{{ c.chart_id }}").html("Load custom map fail! Status: " + textStatus);
33 | });
34 | {% endif %}
35 | {% endfor %}
--------------------------------------------------------------------------------
/django_echarts/templates/snippets/first_views.py.tpl:
--------------------------------------------------------------------------------
1 | """
2 | The file is generated by django-echarts {{ version }}.
3 | Now you can add site urls to the entry urlpatterns in project urls.py.
4 | Example:
5 | urlpatterns = [
6 | # Other urlpatterns
7 | path('', site_obj.urls),
8 | ]
9 |
10 | """
11 | from django_echarts.entities import Copyright
12 | from django_echarts.starter.sites import DJESite
13 | {% if use_chart %}
14 | from pyecharts import options as opts
15 | from pyecharts.charts import Bar
16 | {% endif %}
17 |
18 | __all__ = ['site_obj']
19 |
20 | site_obj = DJESite(
21 | site_title='{{ site_title }}'
22 | )
23 |
24 | site_obj.add_widgets(
25 | copyright_=Copyright(start_year={{ start_year }}, powered_by='{{ powered_by }}')
26 | )
27 |
28 |
29 | @site_obj.register_chart
30 | def my_first_chart():
31 | {% if use_chart %}
32 | bar = Bar().add_xaxis(
33 | ["Nanping", "Sanming", "Longyan", "Ningde", "Zhangzhou", "Fuzhou", "Quanzhou", "Putian", "Xiamen"]).add_yaxis(
34 | 'Area', [26300, 22900, 19050, 13450, 12600, 12150, 11020, 4119, 1576]
35 | ).set_global_opts(
36 | title_opts=opts.TitleOpts(title="The Area of cities In Fujian Province", subtitle="Unit: km3"))
37 | return bar
38 | {% else %}
39 | # Write your pyecharts here.
40 | # This method must return an object of pycharts.charts.Chart
41 | pass
42 | {% endif %}
43 |
--------------------------------------------------------------------------------
/django_echarts/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/templatetags/__init__.py
--------------------------------------------------------------------------------
/django_echarts/templatetags/echarts.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 | """Template tags for django-echarts.
3 |
4 | """
5 | from collections import defaultdict
6 | from typing import Union
7 |
8 | from borax.utils import chain_getattr
9 | from django import template
10 | from django.template.loader import render_to_string, get_template
11 | from django.utils.safestring import SafeString
12 | from django_echarts.conf import DJANGO_ECHARTS_SETTINGS
13 | from django_echarts.entities import LinkItem, Menu, EntityURI
14 | from django_echarts.renders import render_widget, flat_chart, get_js_dependencies
15 | from django_echarts.utils.burl import burl_kwargs
16 | from django_echarts.site_exts import reverse_chart_url
17 |
18 | register = template.Library()
19 |
20 |
21 | @register.simple_tag(takes_context=True)
22 | def dep_url(context, dep_name: str, repo_name: str = None):
23 | return DJANGO_ECHARTS_SETTINGS.resolve_url(dep_name, repo_name)
24 |
25 |
26 | @register.simple_tag(takes_context=True)
27 | def echarts_container(context, *echarts, **kwargs):
28 | div_list = []
29 | for chart in echarts:
30 | div_list.append(render_widget(chart, context=context, **kwargs))
31 | return template.Template('
'.join(div_list)).render(context)
32 |
33 |
34 | @register.simple_tag(takes_context=True)
35 | def echarts_js_dependencies(context, *args):
36 | dependencies = get_js_dependencies(args, global_theme=DJANGO_ECHARTS_SETTINGS.opts.echarts_theme)
37 | links = map(DJANGO_ECHARTS_SETTINGS.resolve_url, dependencies)
38 |
39 | return template.Template(
40 | '
'.join([''.format(link=link) for link in links])
41 | ).render(context)
42 |
43 |
44 | def build_echarts_initial_fragment(*args):
45 | chart_list = flat_chart(args)
46 | structured_dic = defaultdict(list) # key = CustomMapItem
47 | for chart in chart_list:
48 | if hasattr(chart, '_is_geo_chart'):
49 | chart.is_geo_chart = chart._is_geo_chart
50 | chart.dje_echarts_theme = DJANGO_ECHARTS_SETTINGS.opts.get_echarts_theme(chart.theme)
51 | map_item = getattr(chart, 'custom_map_item', None)
52 | if map_item:
53 | structured_dic[map_item].append(chart)
54 | else:
55 | structured_dic[None].append(chart)
56 | structured_data = list(structured_dic.items())
57 | context = {'structured_data': structured_data}
58 | return SafeString(render_to_string('snippets/echarts_init_options.tpl', context))
59 |
60 |
61 | @register.simple_tag(takes_context=True)
62 | def echarts_js_content(context, *echarts):
63 | contents = build_echarts_initial_fragment(*echarts)
64 | return template.Template(
65 | ''.format(contents)
66 | ).render(context)
67 |
68 |
69 | @register.simple_tag(takes_context=True)
70 | def echarts_js_content_wrap(context, *charts):
71 | return template.Template(
72 | build_echarts_initial_fragment(*charts)
73 | ).render(context)
74 |
75 |
76 | @register.simple_tag
77 | def dw_table(table_obj, **kwargs):
78 | return render_widget(table_obj, **kwargs)
79 |
80 |
81 | @register.simple_tag
82 | def dw_values_panel(panel):
83 | return render_widget(panel)
84 |
85 |
86 | @register.simple_tag(takes_context=True)
87 | def dw_widget(context, widget, **kwargs):
88 | return render_widget(widget, context=context, **kwargs)
89 |
90 |
91 | @register.simple_tag
92 | def dw_collection(collection):
93 | tpl = get_template('widgets/collection.html')
94 | return SafeString(tpl.render({'collection': collection}))
95 |
96 |
97 | @register.simple_tag
98 | def theme_js():
99 | theme = DJANGO_ECHARTS_SETTINGS.theme
100 | html = []
101 | for link in theme.js_urls:
102 | html.append(f'')
103 | return SafeString(''.join(html))
104 |
105 |
106 | @register.simple_tag
107 | def theme_css():
108 | theme = DJANGO_ECHARTS_SETTINGS.theme
109 | html = []
110 | for link in theme.css_urls:
111 | html.append(f'')
112 | return SafeString(''.join(html))
113 |
114 |
115 | @register.simple_tag(takes_context=True)
116 | def page_link(context, page_number: int):
117 | url = context['request'].get_full_path()
118 | return burl_kwargs(url, page=page_number)
119 |
120 |
121 | @register.simple_tag(takes_context=True)
122 | def dw_link(context, item: Union[LinkItem, Menu], class_: str = None):
123 | return render_widget(item, context=context, class_=class_)
124 |
125 |
126 | # Site urls
127 | @register.simple_tag(takes_context=True)
128 | def url_single_chart(context, uri_or_name: Union[EntityURI, str], params_dic: dict = None):
129 | # TODO use {% dw uri %} ?
130 | return reverse_chart_url(uri_or_name, params_dic)
131 |
--------------------------------------------------------------------------------
/django_echarts/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/django_echarts/utils/__init__.py
--------------------------------------------------------------------------------
/django_echarts/utils/burl.py:
--------------------------------------------------------------------------------
1 | from urllib.parse import urlencode, parse_qs, urlsplit, urlunsplit
2 |
3 |
4 | class BUrl:
5 | def __init__(self, url):
6 | self._url = url
7 | self._scheme, self._netloc, self._path, self._query_string, self._fragment = urlsplit(url)
8 | self._query_params = parse_qs(self._query_string)
9 |
10 | def replace(self, name, value, only_replace=False):
11 | if name not in self._query_params:
12 | if only_replace:
13 | return self
14 | self._query_params[name] = []
15 | self._query_params[name] = [value]
16 | return self
17 |
18 | def append(self, name, value):
19 | if name not in self._query_params:
20 | self._query_params[name] = []
21 | self._query_params[name].append(value)
22 | return self
23 |
24 | def delete(self, name):
25 | if name in self._query_params:
26 | del self._query_params[name]
27 | return self
28 |
29 | @property
30 | def url(self):
31 | new_query_string = urlencode(self._query_params, doseq=True)
32 | return urlunsplit((self._scheme, self._netloc, self._path, new_query_string, self._fragment))
33 |
34 |
35 | def burl_args(url, *args):
36 | """Add query parameters to url.
37 | burl_args('/foo/', 'p', 1, 'q', 'test') => '/foo/?p=1&q=test'
38 | """
39 | b = BUrl(url)
40 | _key = ''
41 | for i, value in enumerate(args):
42 | if i % 2 == 0:
43 | _key = value
44 | else:
45 | if _key and value is not None:
46 | b.replace(_key, value)
47 | return b.url
48 |
49 |
50 | def burl_kwargs(url, **kwargs):
51 | b = BUrl(url)
52 | for k, v in kwargs.items():
53 | b.replace(k, v)
54 | return b.url
55 |
--------------------------------------------------------------------------------
/django_echarts/utils/compat.py:
--------------------------------------------------------------------------------
1 | from django import VERSION as django_version
2 | from django.core.paginator import Paginator
3 |
4 |
5 | def compat_get_elided_page_range(paginator: Paginator, number=1, *, on_each_side=3, on_ends=2):
6 | if django_version > (3, 2):
7 | return paginator.get_elided_page_range(number, on_each_side=on_each_side, on_ends=on_ends)
8 | else:
9 | return _get_elided_page_range(paginator, number, on_each_side=on_each_side, on_ends=on_ends)
10 |
11 |
12 | def _get_elided_page_range(paginator: Paginator, number=1, *, on_each_side=3, on_ends=2):
13 | """
14 | Return a 1-based range of pages with some values elided.
15 |
16 | If the page range is larger than a given size, the whole range is not
17 | provided and a compact form is returned instead, e.g. for a paginator
18 | with 50 pages, if page 43 were the current page, the output, with the
19 | default arguments, would be:
20 |
21 | 1, 2, …, 40, 41, 42, 43, 44, 45, 46, …, 49, 50.
22 | """
23 | number = paginator.validate_number(number)
24 |
25 | if paginator.num_pages <= (on_each_side + on_ends) * 2:
26 | yield from paginator.page_range
27 | return
28 |
29 | if number > (1 + on_each_side + on_ends) + 1:
30 | yield from range(1, on_ends + 1)
31 | yield paginator.ELLIPSIS
32 | yield from range(number - on_each_side, number + 1)
33 | else:
34 | yield from range(1, number + 1)
35 |
36 | if number < (paginator.num_pages - on_each_side - on_ends) - 1:
37 | yield from range(number + 1, number + on_each_side + 1)
38 | yield paginator.ELLIPSIS
39 | yield from range(paginator.num_pages - on_ends + 1, paginator.num_pages + 1)
40 | else:
41 | yield from range(number + 1, paginator.num_pages + 1)
42 |
--------------------------------------------------------------------------------
/django_echarts/utils/downloader.py:
--------------------------------------------------------------------------------
1 | import os
2 | import urllib.request
3 | from concurrent.futures import ThreadPoolExecutor
4 |
5 |
6 | def download_file(remote_url: str, local_path: str):
7 | parent = os.path.dirname(local_path)
8 | if not os.path.exists(parent):
9 | os.mkdir(parent)
10 | filename = local_path.split('/')[-1]
11 | print(f'Download file {filename} start!')
12 | rsp = urllib.request.Request(
13 | remote_url,
14 | headers={
15 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36'
16 | }
17 | )
18 | with urllib.request.urlopen(rsp) as response, open(local_path, 'w+b') as out_file:
19 | data = response.read()
20 | out_file.write(data)
21 |
22 | print(f'File {filename} download success!')
23 |
24 |
25 | def download_files(file_info_list: list):
26 | with ThreadPoolExecutor() as executor:
27 | for remote_url, local_path in file_info_list:
28 | executor.submit(download_file, remote_url, local_path)
29 |
--------------------------------------------------------------------------------
/django_echarts/utils/param_converters.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 |
4 | class DateConverter:
5 | regex = '[0-9]{8}'
6 |
7 | def to_python(self, value):
8 | return datetime.strptime(value, '%Y%m%d').date()
9 |
10 | def to_url(self, value):
11 | return value.strftime('%Y%m%d')
12 |
13 |
14 | DJE_CONVERTERS = {
15 | 'date': DateConverter()
16 | }
17 |
--------------------------------------------------------------------------------
/django_echarts/views.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 | from django.views.generic.base import TemplateView
4 |
5 | __all__ = ['SimplePageView', 'PageTemplateView']
6 |
7 |
8 | class SimplePageView(TemplateView):
9 | template_name = 'simple/page.html'
10 |
11 | def get_context_data(self, **kwargs):
12 | context = super(SimplePageView, self).get_context_data(**kwargs)
13 | context['container_obj'] = self.get_container_obj()
14 | return context
15 |
16 | def get_container_obj(self) -> Any:
17 | pass
18 |
19 |
20 | PageTemplateView = SimplePageView # Old alias
21 |
--------------------------------------------------------------------------------
/docs/about.md:
--------------------------------------------------------------------------------
1 | # 关于(About)
2 |
3 | ## 贡献者(Contributions )
4 |
5 | | 作者 | 主页 |
6 | | ---- | ---- |
7 | | Samuel.Yan | [https://github.com/kinegratii](https://github.com/kinegratii) |
8 |
9 | ## 资源(Resources)
10 |
11 | 本项目参考或者引用了下列资源:
12 |
13 | - Bootstrap5: [https://v5.bootcss.com/](https://v5.bootcss.com/)
14 | - Bootstrap Icons: [https://icons.getbootstrap.com/](https://icons.getbootstrap.com/)
15 | - Bootstrap3调色css: [ https://bootswatch.com/3/]( https://bootswatch.com/3/)
16 | - Bootstrap5调色css: [ https://bootswatch.com/](https://bootswatch.com/)
17 | - borax: [https://github.com/kinegratii/borax](https://github.com/kinegratii/borax)
18 | - cdnjs: [https://cdnjs.com/](https://cdnjs.com/)
19 | - Codecov: [https://about.codecov.io/](https://about.codecov.io/)
20 | - django: [https://www.djangoproject.com/](https://www.djangoproject.com/)
21 | - Django Best Practices: [https://django-best-practices.readthedocs.io/en/latest/index.html](https://django-best-practices.readthedocs.io/en/latest/index.html)
22 | - domonic: [https://github.com/byteface/domonic](https://github.com/byteface/domonic)
23 | - echarts: [https://echarts.apache.org/zh/index.html](https://echarts.apache.org/zh/index.html)
24 | - Jinja2: [https://jinja2docs.readthedocs.io/en/stable/](https://jinja2docs.readthedocs.io/en/stable/)
25 | - Materialize: [https://materializecss.com/](https://materializecss.com/)
26 | - PrettyTable: [https://github.com/jazzband/prettytable](https://github.com/jazzband/prettytable)
27 | - pyecharts: [https://pyecharts.org/](https://pyecharts.org/)
28 | - pyecharts-asserts: [https://github.com/pyecharts/pyecharts-assets](https://github.com/pyecharts/pyecharts-assets)
29 | - typing_extensions [https://github.com/python/typing_extensions](https://github.com/python/typing_extensions)
30 | - yattag: [https://www.yattag.org/](https://www.yattag.org/)
31 |
32 |
33 |
34 | ## 参与开发(Development)
35 |
36 | 在参与开发之前,您必须确保:
37 |
38 | - 掌握 Python Typing Hints 相关内容
39 | - 熟悉Django框架的有关内容,包括但不限于路由、模板系统、CBV等
40 | - 熟悉pyecharts库的功能,以及核心实现逻辑
41 |
42 | ## 开发方向(Roadmap)
43 |
44 | - 【数据处理】基于 Django Model的数据预处理。
45 | - 【UI框架扩展标准】通过进一步抽象UI框架渲染逻辑,使得能够更好地支持现有的其他主流UI框架。
46 | - 【版本特性兼容】关注Django和pyecharts的重大版本更新,能够兼容更多的版本组合。
47 |
48 | ## 开源协议(License)
49 |
50 | MIT License
51 |
52 |
--------------------------------------------------------------------------------
/docs/breaking-change.md:
--------------------------------------------------------------------------------
1 | # 破坏性变更
2 |
3 | ## Deprecated API
4 |
5 | 以下API已经标记为 Deprecated ,并在v1.0正式版本发布时被移除(Removed)。
6 |
7 | * 方法 `django_echarts.entities.ValuesPanel.add`
8 |
9 | 使用方法 `ValuesPanel.add_widget` 代替。
10 |
11 | * 模板标签 `dw_table` / `echarts_container`
12 |
13 | 使用 `dw_widget` 代替。
14 |
--------------------------------------------------------------------------------
/docs/changelog.md:
--------------------------------------------------------------------------------
1 | # 更新日志
2 |
3 | ## v0.6.0 - (20230317)
4 |
5 | [发布日志](/release-note/v060)
6 |
7 | - 新增pycharts2.0.x适配
8 | - 新增参数化图表支持
9 | - 重构自定义地图,支持 geojson&svg格式
10 | - 新增资源类 `EntityURI`
11 | - 新增 `WidgetNotRegisteredError` 异常
12 | - `WidgetGetterMixin` 接口类支持 `EntityURI`
13 | - 修改导航栏(Nav)配置方式
14 | - 右侧导航栏支持二级菜单
15 | - Borax 更新至 4.0
16 | - typing_extensions 更新至 4.5
17 |
18 | ## v0.5.2 - (20220508)
19 |
20 | - 新增 `ElementEntity` HTML组件
21 | - 新增支持HTML生成库:[htmlgenerator](https://github.com/basxsoftwareassociation/htmlgenerator)
22 | - 新增百度地图密钥全局配置 `DJEOpts.baidu_map_ak`
23 | - 优化资源本地化功能
24 | - 修正 `Nav.add_item` 函数中 `after_separator` 逻辑
25 | - 依赖项自定义d2u支持format字符串格式化
26 |
27 | ## v0.5.1 - (20220410)
28 |
29 | [发布日志](/release-note/v051)
30 |
31 | - 新增统一的 `dw_widget` 组件渲染标签函数
32 | - 优化echarts依赖项解析逻辑
33 | - 新增echarts主题全局配置 `DJEOpts.echarts_theme`
34 | - 重构 `WidgetGetterMixin` 接口
35 | - 新增布局容器组件 `RowContainer` 、文本组件 `Title`
36 | - `ValuesPanel` 支持布局特性,移除 data / col_item_span 参数
37 | - 新增表格css函数,bootstrap_table_css 和 material_table_css
38 | - 重命名表格css函数
39 | - 新增 `DJESite.extend_urlpatterns` 函数,移除 `DJESite.dje_get_urls` 函数
40 | - 命令 download / info 按图表不再需要设置 `site_class`
41 | - 新增 unittest 单元测试构建
42 |
43 | ## v0.5.0 - (20220318)
44 |
45 | [发布日志](/release-note/v050)
46 |
47 | - 使用主题独立APP架构,修改主题设置方式
48 | - 新增直接支持 `prettytable.PrettyTable` 表格类型
49 | - 新增支持自定义geojson地图
50 | - 命令 info / download / starttpl 可忽略 `--theme` 参数,默认为 `INSTALLED_APPS` 配置的主题。
51 | - 新增 *blank.html* 空白模板文件。
52 | - Nav组件新增底部链接列表。
53 | - 新增设置(settings)页面。
54 | - 支持以表单方式更换调色主题。
55 | - 导航栏文字支持模板字符串 `DwString`
56 | - 导航栏支持是否固定设置
57 | - Borax更新至v3.5.3
58 |
59 | ## v0.5.0b2 - (20220306)
60 |
61 | - 列表页面支持关键字搜索
62 | - 优化列表页分页组件
63 | - 首页jumbotron组件支持使用图表显示
64 | - 新增支持Bootstrap5框架,默认采用Bootstrap5框架
65 | - 新增内置支持 Bootstrap框架的16个调色主题
66 | - 新增查看echarts图表options功能
67 | - 新增支持pyecharts在线地图
68 | - 新增支持3D图表
69 | - 新增支持 pyecharts Table 表
70 | - 新增 WidgetCollection图文合辑页面渲染
71 | - 新增支持echarts主题
72 | - 新增 info/download 命令行工具
73 | - 新增 starttpl 命令工具
74 | - 图表支持自动调整大小功能
75 | - 移除 `listdep` 命令
76 |
77 | ## v0.5.0b1 - (20220212)
78 |
79 | - 新增starter可视化网站生成器
80 | - 首页新增热门推荐模块
81 | - 列表页面支持grid布局
82 | - 支持bootstrap3调色主题切换
83 | - 新增支持Materialcss框架
84 |
85 | ## v0.5.0a1 - (20220110)
86 |
87 | - 迁移支持 Python3.7+ / Django3.0+ / pyecharts1.9+
--------------------------------------------------------------------------------
/docs/gallery.md:
--------------------------------------------------------------------------------
1 | # Gallery
2 |
3 | **Home Page with Top Charts**
4 |
5 | 
6 |
7 | **Home with Chart(Bootstrap5)**
8 |
9 | 
10 |
11 | **Word Cloud (Bootstrap.Darkly)**
12 |
13 | 
14 |
15 | **List Page (material)**
16 |
17 | 
18 |
19 | **Collection Named All**
20 |
21 | 
22 |
23 | **Timeline Demo**
24 |
25 | 
26 |
27 |
--------------------------------------------------------------------------------
/docs/guides/advance_development.md:
--------------------------------------------------------------------------------
1 | # 高级开发
2 |
3 | 本文提供了一些在扩展开发过程所需要的功能。这些功能的实现可能需要整合Django核心模块功能或者其他第三方库。
4 |
5 | ## 组件
6 |
7 | ### 组件编写
8 |
9 | django-echarts 内置的UI框架与下列项目是一样的,因此可以在项目中使用其提供模板标签以提高编码效率。
10 |
11 | - Bootstrap3: [https://github.com/zostera/django-bootstrap3](https://github.com/zostera/django-bootstrap3)
12 | - Bootstrap5: [https://github.com/zostera/django-bootstrap5](https://github.com/zostera/django-bootstrap5)
13 | - Material: [https://github.com/viewflow/django-material](https://github.com/viewflow/django-material)
14 |
15 |
16 |
17 | ## 视图逻辑
18 |
19 | ### 仅登录用户访问
20 |
21 | django提供了 `login_required` 装饰器用于仅登录用户可访问的功能,但是该装饰器只能装饰视图函数。因此只能单独一个一个添加到对应视图函数之前。
22 |
23 | [django-decorator-include](https://github.com/twidi/django-decorator-include) 是一个可以在装饰`include` 函数,使得对于同组的多个路由同时添加登录限制。
24 |
25 | 下面是一个例子:
26 |
27 | ```python
28 | from django.conf.urls import url, include
29 | from django.contrib.auth.decorators import login_required
30 |
31 | from decorator_include import decorator_include
32 |
33 | from site_views import site_obj
34 |
35 | urlpatterns = [
36 | path('site_demo/', decorator_include(login_required, site_obj.urls))
37 | ]
38 | ```
39 |
40 | ### 用户名显示
41 |
42 | `DwString` 提供了一种利用模板字符串显示动态字符串的功能。
43 |
44 | 例子:在右侧菜单栏显示用户信息,如果登录则显示用户名(`request.user.username`),未登录显示“匿名用户” 文字。
45 |
46 | ```python
47 | site_obj.add_right_link(
48 | LinkItem(text=DwString.login_name(un_login_text='匿名用户'))
49 | )
50 | ```
51 |
52 |
--------------------------------------------------------------------------------
/docs/guides/entity_params.md:
--------------------------------------------------------------------------------
1 | # 参数化图表
2 |
3 | > Add in 0.6.0
4 |
5 | 从0.6.0开始,django-echarts 支持参数化图表,对于同一个图表,可以根据传入的数据参数,构建不同的图表,这些图表的配置是相同的,只是数据有所不同。
6 |
7 | ## 基础例子
8 |
9 | 实现一个参数化图表很简单,只需在图表注册函数添加参数即可。
10 |
11 | ```python
12 | from django_echarts.entity.uri import ParamsConfig
13 | from django_echarts.starter.sites import DJESite
14 | from pyecharts import options as opts
15 | from pyecharts.charts import Bar
16 |
17 | site_obj = DJESite(site_title='福建统计')
18 |
19 | @site_obj.register_chart(title='{year}年福建省家庭户类型组成',
20 | params_config=ParamsConfig({'year': [1982, 1990, 2000, 2010, 2020]}))
21 | def yearly_family_types(year: int):
22 | family_types = [
23 | '一人户', '二人户', '三人户', '四人户', '五人户', '六人户', '七人户', '八人户', '九人户', '十人及其以上'
24 | ]
25 | data = [
26 | [1982, 7.7, 8.2, 12.2, 17.1, 18.4, 14.7, 10.1, 11.6, 0, 0],
27 | [1990, 5.8, 8.6, 16.8, 23.6, 21.4, 11.8, 5.9, 2.9, 1.4, 1.8],
28 | [2000, 9.1, 15.5, 25.4, 24.7, 15.8, 5.9, 2.2, 0.8, 0.3, 0.3],
29 | [2010, 12.1, 17.2, 24.3, 21.7, 13.7, 6.4, 2.6, 1.1, 0.5, 0.4],
30 | [2020, 27.3, 26.3, 19.4, 14.2, 6.9, 4, 1.1, 0.4, 0.2, 0.2]
31 | ]
32 | yearly_data = {item[0]: item[1:] for item in data}
33 | if year not in yearly_data:
34 | raise ChartDoesNotExist(f'暂无{year}年数据')
35 | year_data = yearly_data[year]
36 | bar = (
37 | Bar()
38 | .add_xaxis(family_types).add_yaxis('百分比(%)', year_data)
39 | .set_global_opts(title_opts=opts.TitleOpts("福建省家庭户类型构成-{}年".format(year)))
40 | )
41 | return bar
42 | ```
43 |
44 | ### 定义图表参数
45 |
46 | 图表参数位于图表注册函数(为 `DJESite.register` 或 `EntityFactory.register` 所修饰的函数)的参数列表,你可以和正常函数一样定义任何参数。
47 |
48 | django-echarts 推荐尽可能遵循下列明确性的规则:
49 |
50 | - 为每个参数声明数据类型,在URL方式调用下能够将其转化为对应的数据类型值,目前仅支持 `int / float / str / UUID` 等构造函数为`type(s)`的数据类型。
51 | - 不推荐使用 `*args` ,使用 `**kwags`。
52 |
53 | ### 定义关联ChartInfo
54 |
55 | `DJESite.register` 或 `EntityFactory.register` 装饰器的三个参数 title,description,body支持模板字符串,如上述例子中的 `{year}年福建省家庭户类型组成`。
56 |
57 | **注意:此处使用str.format的单括号形式,而不是Django模板的双括号形式。**
58 |
59 | ### 返回图表
60 |
61 | 如果用户输入的参数没有对应的图表,可以抛出 `ChartDoesNotExist` 异常。
62 |
63 | ### 参数配置ParamsConfig
64 |
65 | `params_config` 用于定义可选参数项,仅用于无参数时的图表页面显示。
66 |
67 | 在构建图表对象时不会检查参数是否符合在此配置之中,因此在图表构建函数中仍需检查相应的参数,对不符合的情况抛出 `ChartDoesNotExist 异常`。
68 |
69 | `params_config` 必须是支持下列接口的对象。
70 |
71 | ```python
72 | class ParamsConfigMixin:
73 | def __iter__(self) -> Generator[dict, Any, None]:
74 | pass
75 | ```
76 |
77 | 如下列两种方式定义是等效的。
78 |
79 | ```python
80 | pc1 = ParamsConfig([
81 | {'year':2021, 'month':1}, {'year':2021, 'month':2},
82 | {'year':2021, 'month':3}, {'year':2021, 'month':4}
83 | ])
84 |
85 | pc2 = ParamsConfig({
86 | 'year':[2021],
87 | 'month':[1, 2, 3, 4]
88 | })
89 | ```
90 |
91 |
92 |
93 | ## EntityURI
94 |
95 | 为统一标识django-echarts的实体(图表、组件)引入`ENtityURI` 类,该类的一个对象由类别(catalog)、名称(name)、参数(params)三部分组成。例如上述例子中2020年的资源标识可表示为:
96 |
97 | ```python
98 | family_types_2020_uri = EntityURI(catalog='chart', name='yearly_family_types', params={'year':2020})
99 | ```
100 |
101 | `EntityURI` 还有字符串形式,其格式如下:
102 |
103 | ```
104 | <资源类别>:<资源名称>/<参数名称1>/<参数值1>/<参数名称2>/<参数值2>/<参数名称3>/<参数值3>/
105 | ```
106 |
107 | 在多个参数情况下,其参数顺序不受影响,因此一个图表可能有多个字符串标识。
108 |
109 | 例如:上述例子可表示为
110 |
111 | ```
112 | chart:yearly_family_types/year/2020
113 | ```
114 |
115 | 关联的`ChartInfo`表示为
116 |
117 | ```
118 | info:yearly_family_types/year/2020
119 | ```
120 |
121 | 字符串形式通常用于URL、合辑、页面组件构建。
122 |
123 | ```python
124 | site_obj.add_widgets(jumbotron_chart='chart:yearly_family_types/year/2020') # 首页jumbotron位置显示2020年的数据
125 |
126 |
127 |
128 |
129 | rc = RowContainer()
130 | rc.add_widget('chart:yearly_family_types/year/2020')
131 | rc.add_widget(EntityURI(catalog='chart', name='yearly_family_types', params={'year':2020}))
132 | ```
133 |
134 | 今年的数据总是
135 |
136 | ## 使用图表
137 |
138 | ### 从EntityFactory引用
139 |
140 | 所有的图表和HTML组件均存储在全局变量 `django_echarts.stores.entity_factory.factory` 中。`EntityFactory` 提供两种方式引用
141 |
142 | ```python
143 | from django_echarts.stores.entity_factory import factory
144 |
145 | family_types_chart_2020 = factory.get_chart_widget('yearly_family_types', params={'year':2020}) # 使用参数字典
146 |
147 | family_types_chart_2020 = factory.get_widget_by_uri(family_types_2020_uri) # 使用uri方式
148 | ```
149 |
150 | ### 从ChartInfo引用
151 |
152 | 使用 `ChartInfo.uri` 属性。
153 |
154 |
--------------------------------------------------------------------------------
/docs/images/chart_fj-map-gdp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/chart_fj-map-gdp.png
--------------------------------------------------------------------------------
/docs/images/collection_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/collection_all.png
--------------------------------------------------------------------------------
/docs/images/fujian-custom-geojson.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/fujian-custom-geojson.png
--------------------------------------------------------------------------------
/docs/images/fujian-forest-coverage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/fujian-forest-coverage.png
--------------------------------------------------------------------------------
/docs/images/geojson-io-export.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/geojson-io-export.png
--------------------------------------------------------------------------------
/docs/images/home_top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/home_top.png
--------------------------------------------------------------------------------
/docs/images/home_with_chart_bootstrap5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/home_with_chart_bootstrap5.png
--------------------------------------------------------------------------------
/docs/images/home_with_values_panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/home_with_values_panel.png
--------------------------------------------------------------------------------
/docs/images/list_with_pagination.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/list_with_pagination.png
--------------------------------------------------------------------------------
/docs/images/material_list_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/material_list_page.png
--------------------------------------------------------------------------------
/docs/images/quickstart-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/quickstart-0.png
--------------------------------------------------------------------------------
/docs/images/row_container_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/row_container_demo.png
--------------------------------------------------------------------------------
/docs/images/theme_darkly.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/theme_darkly.png
--------------------------------------------------------------------------------
/docs/images/theme_material.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/theme_material.png
--------------------------------------------------------------------------------
/docs/images/timeline_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/docs/images/timeline_demo.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # django-echarts文档
2 |
3 |    [](https://github.com/kinegratii/django-echarts/actions/workflows/unittest.yaml) [](https://codecov.io/gh/kinegratii/django-echarts)
4 |
5 |
6 | > A visual site scaffold based on [pyecharts](https://github.com/pyecharts/pyecharts) and [Django](https://www.djangoproject.com).
7 |
8 | **代码仓库**: [https://github.com/kinegratii/django-echarts](https://github.com/kinegratii/django-echarts)
9 |
10 | ## 概述
11 |
12 | django-echarts 是一个基于[pyecharts](https://github.com/pyecharts/pyecharts) 和 [Django](https://www.djangoproject.com) 的可视化网站脚手架。
13 |
14 | - 支持echarts图表和表格
15 | - 页面:主页 / 列表 / 详情 / 关于 / 设置
16 | - 组件:导航栏 / 网站底部栏 / 热门板块 / 列表 / 合辑 / 关于面板
17 | - UI主题:Bootstrap3 / Bootstrap5 / Material ,支持更换颜色模式
18 | - 可灵活扩展: 支持整合 Django用户认证 / 数据库 / Session
19 | - 基于Django Template System 的后端渲染
20 | - js/css静态文件托管,支持在线/本地切换
21 | - 生产力工具:代码生成器 / 静态文件下载器
22 | - 90%+ Python Typing Hints覆盖
23 |
24 | ## 安装
25 |
26 | django-echarts的运行环境要求如下:
27 |
28 | | django-echarts版本系列 | pyecharts & echarts | django | python |
29 | | ------ | ------ | ----- | ----- |
30 | | 0.6.x | 1.9 & 4.8.0 | 2.0 - 4.1 | 3.7+ |
31 | | | 2.0 & 5.4.1 | 2.0 - 4.1 | 3.7+ |
32 | | 0.5.x | 1.9 & 4.8.0 | 2.0 - 4.1 | 3.7+ |
33 |
34 | 可以使用 pip 命令安装。
35 |
36 | ```shell
37 | pip install django-echarts
38 | ```
39 |
40 | 在 *requirements.txt* 引用 django-charts时,推荐使用 **固定次版本号** 的方式。
41 |
42 | ```
43 | django-echarts~=0.6
44 | ```
45 |
46 |
47 |
48 | ## 3分钟上手
49 |
50 | **1.** 创建Django项目。
51 |
52 | ```shell
53 | django-admin startproject MyDemo
54 | ```
55 |
56 | 项目目录结构如下:
57 |
58 | ```text
59 | MyDemo
60 | |-- MyDemo
61 | |-- __init__.py
62 | |-- asgi.py
63 | |-- settings.py
64 | |-- urls.py
65 | |-- site_views.py
66 | |-- wsgi.py
67 | |-- manage.py
68 | ```
69 |
70 | **2.** 修改项目配置文件 *settings.py*。
71 |
72 | ```python
73 | # ...
74 |
75 | INSTALL_APPS = (
76 | # Your apps
77 | 'django_echarts',
78 | 'django_echarts.contrib.bootstrap5'
79 | # Your apps
80 | )
81 |
82 | # ...
83 | DJANGO_ECHARTS = {
84 | }
85 |
86 | ```
87 |
88 | 主要包括:
89 |
90 | - 添加 django_echarts包和对应的主题包到项目配置模块的 `INSTALL_APPS`列表。
91 | - 配置字典 `DJANGO_ECHARTS` 可以留空。
92 |
93 | **3.** 创建新文件 *site_views.py* ,输入下列代码。
94 |
95 | ```python
96 | # ...
97 | from django_echarts.starter.sites import DJESite
98 | from django_echarts.entities import Copyright
99 | from pyecharts import options as opts
100 | from pyecharts.charts import Bar
101 |
102 | site_obj = DJESite(site_title='福建统计')
103 |
104 | site_obj.add_widgets(copyright_=Copyright(start_year=2022, powered_by='Zinc'))
105 |
106 | chart_description = '截止2020年底,福建省土地面积1240.29万公顷,占我国国土总面积1.3%。全省森林面积811.58万公顷,森林覆盖率为66.8%,连续42年位居全国首位。
107 |
108 | @site_obj.register_chart(title='森林覆盖率', description = chart_description, catalog='基本信息')
109 | def fujian_forest_coverage():
110 | bar = Bar().add_xaxis(
111 | ['福州', '厦门', '莆田', '三明', '漳州', '泉州', '南平', '龙岩', '宁德']
112 | ).add_yaxis(
113 | '森林覆盖率', [54.7, 45, 58.1, 76.8, 63.4, 61, 75.3, 78, 75]
114 | ).set_global_opts(
115 | title_opts=opts.TitleOpts(title="福建省各地市森林覆盖率", subtitle="单位:%"),
116 | visualmap_opts=opts.VisualMapOpts(is_show=True, max_=100, min_=0)).set_series_opts(
117 | markline_opts=opts.MarkLineOpts(
118 | data=[
119 | opts.MarkLineItem(y=66.8, name="全省"),
120 | ]
121 | )
122 | )
123 | return bar
124 | ```
125 |
126 | 在函数`fujian_forest_coverage` 中编写pycharts代码,返回对应的图表对象。
127 |
128 | 根据需要修改文字显示,添加组件等。
129 |
130 | **4.** 在项目的路由模块 *urls.py* 添加挂载点。
131 |
132 |
133 | ```python
134 | from django.conf.urls import url, include
135 | from django.urls import path
136 |
137 | from .site_views import site_obj
138 |
139 | urlpatterns = [
140 | # Your urls
141 | path('', include(site_obj.urls))
142 | ]
143 | ```
144 |
145 | **5.** 启动开发服务器,打开浏览器预览结果。
146 |
147 | ```text
148 | python manage.py runserver 0.0.0.0:8900
149 | ```
--------------------------------------------------------------------------------
/docs/posts/new_page_feature.md:
--------------------------------------------------------------------------------
1 | # 新的页面功能
2 |
3 | ## 1 概述
4 |
5 | `django_echarts.starter` 支持新增新的页面功能,一个基本的页面功能由视图、路由和模板组成。
6 |
7 | ## 2 使用步骤
8 |
9 | ### 创建新的视图
10 |
11 | 视图类必须继承 `DJESiteBackendView`,并重写 `get_context_data` 方法,添加自己的数据到 `context`,并返回该字典 。
12 |
13 | ```python
14 | class MyPageView(DJESiteBackendView):
15 | template_name = 'mypage.html'
16 |
17 | def get_context_data(self, **kwargs):
18 | context = super().get_context_data(**kwargs)
19 | context['nickname'] = 'foo'
20 | return context
21 | ```
22 |
23 | ### 编写模板代码
24 |
25 | 模板页面必须继承模板的 *base.html* 页面。在 *main_content* 部分编写模板代码。
26 |
27 | ```html
28 | {% extends 'base.html' %}
29 | {% load echarts %}
30 |
31 | {% block main_content %}
32 | This is my nickname: {{ nickname }}
33 |
34 | {% endblock %}
35 | ```
36 |
37 | ### 关联路由
38 |
39 | 将新路由和 `site_obj.urls` 合并。
40 |
41 | ```python
42 | site_obj = MySite()
43 |
44 | site_obj.extend_urlpatterns([
45 | path('mypage/', MyPageView.as_view(), name='view_my_page')
46 | ])
47 | ```
48 |
49 | ### 添加到导航栏
50 |
51 | ```python
52 | site_obj.add_left_link(
53 | LinkItem(title='我的页面', url=reverse_lazy('view_my_page')),
54 | menu_title='菜单一'
55 | ) # 作为“菜单一”的二级菜单
56 | ```
57 |
58 |
--------------------------------------------------------------------------------
/docs/posts/pyecharts_project.md:
--------------------------------------------------------------------------------
1 | # pyecharts项目
2 |
3 | > 本文介绍了pyecharts项目中与django-echarts相关的逻辑和django-echarts对其适配扩展的原理和实现。
4 |
5 | > 本文以 pyecharts1.9和2.0为例。
6 |
7 | ## 图表支持
8 |
9 | ### 图表类型支持
10 |
11 | pyecharts 的图表类体系如下:
12 |
13 | ```
14 | - ChartMixin
15 | |- Base
16 | |- CompositeMixin
17 | |- Page
18 | |- Tab
19 | |- Table
20 | |- Image
21 | ```
22 |
23 |
24 |
25 | django-echarts对图表类型支持情况如下:
26 |
27 | - 仅支持所有继承自 `pyecharts.charts.base.Base` 的图表类型,即使用一个echarts即可定义的图表,包括 `Grid` 和 `Timeline`。
28 | - 对于涉及到布局的 `CompositeMixin` ,django-echarts 提供了 `RowContainer` 、`Container` 等布局容器类。
29 | - 可直接支持 `pyecharts.components.Table`,但更推荐直接使用 `prettytable.PrettyTable`。
30 |
31 | 代码实现方面,在 `render_widget` 时也直接引用该类。
32 |
33 | ```python
34 | @render_widget.register(Base)
35 | def render_chart(widget, **kwargs) -> SafeString:
36 | pass
37 |
38 | @render_widget.register(Table)
39 | @render_widget.register(PrettyTable)
40 | def render_table(widget, **kwargs) -> SafeString:
41 | pass
42 | ```
43 |
44 | ### 图表属性和方法
45 |
46 | 图表属性和方法包括三个部分:
47 |
48 | - 图表选项Opts:完全支持
49 | - 依赖项js_dependencies:兼容性支持,直接使用 `list` 类型并允许项重复。依赖项去重工作在模板渲染前(调用模板函数)完成,这更加适用于同一页面有多个图表的情况。
50 | - geojson地图:不支持pyecharts的内联引用方式。django-echarts提供了另一种外链引用方式。
51 |
52 | `django_echarts.entities.chart_widgets.BlankChart` 是与 `Base` 相对应的接口类,通过比较二者的区别可以看出 django-echarts的改造扩展。
53 |
54 | ## 图表渲染
55 |
56 | django-echarts 采用 Django Template Engine 而不是 Jinja2 ,因此模板函数/标签、模板tpl文件全部重写。
57 |
58 | 模板函数有三个,均定义在 `echarts` 模块。
59 |
60 | ```html
61 | {% load echarts %}
62 |
63 | {% dw_widget chart_obj %}
64 | {% echarts_js_dependencies chart_obj %}
65 | {% echarts_js_content chart_obj %}
66 | ```
67 |
68 | 其中后两个对标于 pyecharts的macro 函数。
69 |
70 | | django-echarts模板函数 | pyecharts Jinja2 Macro | 说明 |
71 | | ------------------------------------ | -------------------------------- | -------------- |
72 | | echarts_js_dependencies(*chart_list) | render_chart_dependencies(chart) | 渲染依赖项 |
73 | | echarts_js_content(*chart_list) | render_chart_content(chart) | 渲染初始化脚本 |
74 |
75 | 与pyecharts不同的是,django-echarts的模板支持所有在 `django_echarts.renders` 注册的类,不仅包括图表类,也包括普通的HTML组件和容器组件。
76 |
77 | 代码实现方面,主要依赖于被 `singledispatch` 装饰的 `flat_chart` 函数。
78 |
79 | 模板文件定义在 *django_echarts/templates/snippets/echarts_init_options.tpl* 文件中,该文件使用 Django模板语法。
80 |
81 | ## 依赖项静态资源引用
82 |
83 | 主要功能:确定确定所使用的静态文件仓库。根据依赖项确定对应的url路径。
84 |
85 | django-echarts的改造如下:
86 |
87 | - 新增支持混合仓库。比如同一图表的 *echarts.min.js* 使用公共CDN文件,地图文件 *china.js* 使用本地文件(自行创建或远程下载)。
88 | - 所有配置项均定义在 `settings.DJANGO_ECHARTS` 字典。
89 |
90 | 依赖项功能基本逻辑。
91 |
92 | ```
93 | dep_name(依赖项名称) --> filename(文件路径) --> 引用URL/本地文件路径
94 | ```
95 |
96 |
97 |
98 | ## 自定义地图(geojson&svg)
99 |
100 | pyecharts按内嵌方式引入geojson/svg数据,而 django-echarts 只支持 外链方式,这是二者最大的区别。详情参见 [《自定义地图》](/guides/custom_maps) 。
101 |
102 | ## 相关API接口
103 |
104 | ### 引用的类和接口
105 |
106 | 下列类和接口在 django-echarts 被直接引用。
107 |
108 | - `pyecharts.charts.base.Base`
109 | - `pyecharts.datasets.FILENAMES`
110 | - `pyecharts.dataset.EXTRA`
111 | - `pyecharts._version.__version__`
112 | - `pyecharts.components.table.Table`
113 | - `from pyecharts.globals.ThemeType`
114 |
115 | ### 无效的类和接口
116 |
117 | 以下类和方法函数在django-echarts不起作用。
118 |
119 | - `pyecharts.globals.CurrentConfig`
120 | - `pyecharts.charts.basic_charts.Map.add_geo_json`
121 | - `pyecharts.charts.basic_charts.Geo.add_geo_json`
122 |
--------------------------------------------------------------------------------
/docs/posts/third_html_library.md:
--------------------------------------------------------------------------------
1 | # 第三方HTML渲染库
2 |
3 | 本文介绍了一些常用的 HTML渲染库。
4 |
5 | ## htmlgenerator
6 |
7 | 地址: [https://github.com/basxsoftwareassociation/htmlgenerator](https://github.com/basxsoftwareassociation/htmlgenerator)
8 |
9 | ### 安装
10 |
11 | 自 v0.5.2开始,django-echarts 已经内置 htmlgenerator 作为依赖库,无需额外安装。
12 |
13 | ### 创建元素
14 |
15 | 下面是一个简单的例子。
16 |
17 | ```python
18 | import htmlgenerator as hg
19 |
20 | my_page = hg.HTML(hg.HEAD(), hg.BODY(hg.H1("It works!")))
21 | print(hg.render(my_page, {}))
22 |
23 | my_element = hg.DIV('This is a text.', _class='panel', id='my-div')
24 | print(hg.render(my_element, {}))
25 | ```
26 |
27 | 说明:
28 |
29 | 1. 在 htmlgenerator中,所有元素均是 `htmlgenerator.BaseElement` 的子类。
30 | 2. HTML标签以大写字符串
31 | 3. 子节点以位置参数传入
32 | 4. 属性值以关键字参数传入
33 |
34 | ### 动态值
35 |
36 | 使用 `htmlgenerator.render` 函数可以对同一元素进行多次不同渲染。
37 |
38 | ```python
39 | my_div = hg.DIV("Hello, ", hg.C("person.name"))
40 |
41 | print(hg.render(my_div, {"person": {"name": "Alice", "occupation": "Writer"}},))
42 |
43 | print(hg.render(my_div,{"person": {"name": "John", "occupation": "Police"}},))
44 | ```
45 |
46 | ### Django页面响应对象
47 |
48 | `BaseElement` 类的 `render` 函数返回值可以直接传入 `HttpResponse`。
49 |
50 | ```python
51 | from django.http import HttpResponse
52 |
53 | def render_layout_to_response(request, layout, context):
54 | return HttpResponse(layout.render(context))
55 | ```
56 |
57 |
58 |
59 | ### 与django-echarts
60 |
61 | django-echarts 渲染模块内置支持 `htmlgenerator.BaseElement` 类型。因此
62 |
63 | - 可以作为模板标签函数 `dw_widget` 的参数
64 | - 可以作为 `Container.add_widget` 的参数,融入现有的组件体系
65 |
66 |
67 |
68 | ```python
69 | import htmlgenerator as hg
70 | from django_echarts.entities import RowContainer
71 |
72 | container = RowContainer()
73 | header_div = hg.DIV(hg.H1('Page Title'), _class='header')
74 | container.add_widget(header_div)
75 | ```
76 |
77 |
--------------------------------------------------------------------------------
/docs/posts/use_custom_geojson.md:
--------------------------------------------------------------------------------
1 | # Geojson地图
2 |
3 | > 该教程适用于 0.5 版本,0.6+版本使用 `CustomMap`自定义地图功能([链接](/guides/custom_maps)),本模块将在 1.0 移除 。
4 |
5 | ## 1 使用方法
6 |
7 | django-echarts 支持直接引用 geojson 地图,地图来源可以线上资源提供或者自己制作。相关方法均定义在 `django_echarts.geojson` 模块。
8 |
9 | ```python
10 | use_geojson(chart_obj, map_name:str, url:str)
11 | ```
12 |
13 | 参数说明如下:
14 |
15 | | 参数 | 描述 |
16 | | --------- | ------------------------------------------------------------ |
17 | | chart_obj | pyecharts中的图表 |
18 | | map_name | 地图名称,可以为任何字符串。用于 `echarts.registerMap` 的第一个参数。 |
19 | | url | geojson文件引用地址,该url须返回一个json格式数据 |
20 |
21 | 远程地址使用
22 |
23 | ```python
24 | use_geojson(map1, '福建省', 'https://geo.datav.aliyun.com/areas_v3/bound/350000_full.json')
25 | ```
26 |
27 | 本地地址使用
28 |
29 | ```python
30 | use_geojson(map1, '福建市县', '/geojson/fujian.geojson') # 不推荐,使用下面两种的url反向解析
31 | use_geojson(map1, '福建市县', geojson_url('fujian.geojson'))
32 | ```
33 |
34 |
35 |
36 | ## 2 本地geojson示例
37 |
38 | ### 制作geojson文件
39 |
40 | 以制作福建省市县两级边界地图为例子。
41 |
42 | 第一步,打开阿里云数据可视化平台提供的工具 [http://datav.aliyun.com/portal/school/atlas/area_selector](http://datav.aliyun.com/portal/school/atlas/area_selector) ,依次下载福建省9个地市的geojson文件(须勾选 “包括子区域”)。
43 |
44 | 第二步, 打开 [geojson.io](http://geojson.io/) 网站,导入各地市geojson文件,直接再导出合并后的geojson,文件名为 *fujian.geojson*。
45 |
46 | 
47 |
48 | ### 编写图表代码
49 |
50 | 第一步,将 *fujian.geojson* 改名,名称只能包含 数字、字母、下划线、横杠等字符。
51 |
52 | 并放置在 static 目录下,具体路径为:
53 |
54 | ```
55 | /static/geojson/fujian.geojson
56 | ```
57 |
58 | 第二步,对图表对象调用 `use_geojson` 函数。
59 |
60 | ```python
61 | from django_echarts.entities import Map
62 | from django_echarts.geojson import use_geojson, geojson_url
63 |
64 | # ...
65 |
66 | @site_obj.register_chart
67 | def my_geojson_demo():
68 | map1 = Map()
69 | map1.add("", [('闽侯县', 23), ('湖里区', 45)], maptype="福建市县")
70 | map1.set_global_opts(title_opts=opts.TitleOpts(title="自定义geojson"))
71 | map1.height = '800px'
72 | use_geojson(map1, '福建市县', geojson_url('fujian.geojson'))
73 | return map1
74 | ```
75 |
76 |
77 |
78 | 渲染后的前端代码如下(省略非关键代码):
79 |
80 | ```javascript
81 | $.getJSON("/geojson/fujian.geojson").done(function(mapdata){
82 | echarts.registerMap("福建市县", mapdata);
83 | var chart_3bf0d2a = echarts.init(
84 | document.getElementById('3bf0d2a'),
85 | 'white',
86 | {renderer: 'canvas'}
87 | );
88 | var option_3bf0d2a = {
89 | "series": [
90 | {
91 | "type": "map",
92 | "mapType": "福建市县",
93 | "data": [
94 | { "name": "闽侯县", "value": 23 },
95 | { "name": "湖里区", "value": 45 }
96 | ],
97 | }
98 | ],
99 | };
100 | chart_3bf0d2a.setOption(option_3bf0d2a);
101 | }).fail(function(jqXHR, textStatus, error){
102 | $("#3bf0d2a").html("Load geojson file fail!Status:" + textStatus);
103 | });
104 |
105 | ```
106 |
107 | ### 运行效果
108 |
109 | 
110 |
111 |
--------------------------------------------------------------------------------
/docs/posts/widget_collection.md:
--------------------------------------------------------------------------------
1 | # 图表合辑
2 |
3 | ## 概述
4 |
5 | 合辑类 `WidgetCollection` 实现的是按照用户指定的布局填充相应的组件。
6 |
7 | ```
8 | Container WidgetGetterMixin
9 | ^ ^
10 | | |
11 | | |
12 | user_layout --> WidgetCollection EntityFactory
13 | |
14 | |- auto_mount(widget_container: WidgetGetterMixin)
15 | ```
16 |
17 | 这样可以减少中间环节的大量 `add_widget` 操作。比如,使用Container构建组件时:
18 |
19 | ```python
20 | factory = EntityFactory()
21 |
22 | container = Container()
23 | row_container = RowContainer()
24 | chart1, info, _ = factory.get_chart_and_info('my_chart_name')
25 | row_container.add_widget(chart1, span=8)
26 | row_container.add_widget(info)
27 | container.add_widget(row_container)
28 | ```
29 |
30 | 使用 WidgetCollection 时,将中间 `RowContainer` 的创建和添加组件封装实现在 `auto_mount` 函数之中。
31 |
32 | ```python
33 | factory = EntityFactory()
34 |
35 | wc = WidgetCollection()
36 | wc.add_chart_widget('my_chart_name', layout='l8')
37 | wc.auto_mount(factory)
38 | ```
39 |
40 |
41 |
42 | ## 定义
43 |
44 | > class WidgetCollection(name: str, title: str = None, layout: Union[str, LayoutOpts] = 'a')
45 |
46 | 一个合辑对象 `WidgetCollection` ,继承 `Container`类。
47 |
48 | ```python
49 | wc = WidgetCollection(
50 | name='first_collection', title='第一个合辑', layout='s8'
51 | )
52 | ```
53 |
54 | 参数列表
55 |
56 | | 参数 | 类型 | 描述 |
57 | | ------ | ---- | -------------------------------------- |
58 | | name | slug | 合辑标识符,作为url的一部分 |
59 | | title | str | 标题,菜单栏的文字 |
60 | | layout | str | 整体布局,仅对 `add_chart_widget` 方式 |
61 |
62 | ## 添加子组件
63 |
64 | 本节的添加方式均是按照 `auto_mount` 方式。
65 |
66 | ### 添加echarts图表
67 |
68 | > WidgetCollection.add_chart_widget(self, chart_name: str, layout: str = 'l8')
69 |
70 | `WdidgetCollection` 提供了 `add_*` 方法用于添加组件,函数将参数的组件使用 `row` 类(一行12列)进行包裹。
71 |
72 | ```python
73 | # 添加名为my_first的图表和对应的信息卡组件,以8:4方式显示
74 | wc.add_chart_widget(chart_name='my_first', layout='l8')
75 |
76 | # 显示名为my_named_charts的多图表组件NamedCharts。
77 | wc.add_chart_widget(chart_name='my_named_charts', layout='l6')
78 | ```
79 |
80 | ### 添加HTML组件
81 |
82 | > WidgetCollection.add_html_widget(widget_list: List, spans:List=None)
83 |
84 | 构建一个 RowContainer 容器,并添加一个或多个子组件。
85 |
86 | ```python
87 | # 按照给定的列数显示多个组件
88 | wc.add_html_widget(widget_names=['w1', 'w2'], spans=[8, 4])
89 | ```
90 |
91 |
92 |
93 | ### 组件布局
94 |
95 | 添加不同组件可使用的布局不同。
96 |
97 | | 布局layout | | 合辑定义 | 只添加echarts图表 | 添加html组件 |
98 | | ---------- | ------- | ---------------- | ----------------- | ---------------- |
99 | | 类型 | 示例 | WidgetCollection | add_chart_widget | add_html_widget |
100 | | str | l8 | Y | 8列图表 + 4列卡片 | - |
101 | | | r8 | Y | 4列卡片 + 8列图表 | - |
102 | | | s8 | Y | 交叉使用l8和r8 | - |
103 | | int | 0 | - | 12列图表 | 每个子组件平均分 |
104 | | | 8 | - | 8列图表 | 每个子组件8列 |
105 | | List[int] | [4,4,4] | - | 接受长度为2的列表 | Y |
106 |
107 |
108 |
109 | ## 注册合辑
110 |
111 | 可以通过 `DJESite.register_collection` 方法构建一个图表合辑页面。
112 |
113 | ```python
114 | site_obj = DJESite(site_title='DJE Demo')
115 |
116 | @site_obj.register_chart(name='fj_fimily_type', title='示例图表1', layout='l8')
117 | def fj_fimily_type():
118 | line = Line()
119 | # ...
120 | return line
121 |
122 | @site_obj.register_chart(name='fj_area_bar', title='示例图表2', layout='l8')
123 | def fj_fimily_type():
124 | bar = Bar()
125 | # ...
126 | return bar
127 |
128 |
129 | @site_obj.register_collection
130 | def collection1():
131 | wc = WidgetCollection(title='合辑01', layout='s8')
132 | wc.add_chart_widget(name='fj_area_bar')
133 | wc.add_chart_widget(name='fj_fimily_types')
134 | return wc
135 | ```
136 |
137 | 访问URL */collection/collection1/* 可以预览页面效果。
138 |
139 | register_collection 函数参数及其意义:
140 |
141 | | 参数 | 类型 | 说明 |
142 | | --------------- | --------- | ----------------------------------------- |
143 | | name | slug | 合辑标识,用于 `DJESite` 创建对应视图路由 |
144 | | charts | List[str] | 包含的图表标识 |
145 | | layout | str | 合辑布局 |
146 | | title | str | 标题 |
147 | | catalog | str | 如果设置,将添加合辑链接到该菜单项之下 |
148 | | after_separator | bool | 是否在菜单项前使用分隔符 |
149 |
150 |
--------------------------------------------------------------------------------
/docs/reference/commands.md:
--------------------------------------------------------------------------------
1 | # 命令行工具
2 |
3 | django-echarts 提供了一个包含若干个命令的 CLI 工具,这些命令都是标准的 Django 管理命令,均定义在 `django_echarts.management.commands` 包下。
4 |
5 | 本文档列出的帮助信息不包含继承自django命令的选项,你可以使用以下命令查看完整的帮助信息。
6 |
7 | ```shell
8 | python manage.py -h
9 | ```
10 |
11 | ## 静态文件 - 查看(info)和下载(download)
12 |
13 | 参见 本地化静态资源 一节。
14 |
15 |
16 | ## startsite - 生成视图代码
17 |
18 | ### 帮助信息
19 |
20 | 创建 初始化site的代码片段。
21 |
22 | ```text
23 | E:\projects\django-echarts\example>python manage.py startsite -h
24 | usage: manage.py startsite [-h] [--site-title SITE_TITLE] [--start-year START_YEAR] [--powered-by POWERED_BY]
25 | [--override] output
26 |
27 | Auto generate site_views.py file.
28 |
29 | positional arguments:
30 | output The output file.
31 |
32 | optional arguments:
33 | -h, --help show this help message and exit
34 | --site-title SITE_TITLE, -t SITE_TITLE
35 | The title of site.
36 | --start-year START_YEAR, -y START_YEAR
37 | The start year.
38 | --powered-by POWERED_BY, -p POWERED_BY
39 | The principal of copyright.
40 | --empty, -e
41 | --override, -o
42 | ```
43 |
44 | ## starttpl - 生成模板文件
45 |
46 | ### 使用方法
47 |
48 | ```text
49 | E:\projects\zinc> python .\manage.py starttpl -h
50 | usage: manage.py starttpl [-h] [--theme {bootstrap3,bootstrap5,material}] [--output OUTPUT] [--force] tpl_name [tpl_name ...]
51 |
52 | Copy the builtin template files to your project templates.
53 |
54 |
55 | positional arguments:
56 | tpl_name The name of template file.
57 |
58 | optional arguments:
59 | -h, --help show this help message and exit
60 | --theme {bootstrap3,bootstrap5,material}
61 | The name of theme.
62 | --output OUTPUT, -o OUTPUT
63 | The output filename
64 | --force, -f Whether to copy if the file exists.
65 | ```
66 |
67 | ### 查看主题所有文件
68 |
69 | 查看当前主题的所有模板文件。
70 |
71 | ```text
72 | E:\projects\zinc> python .\manage.py starttpl
73 | The template names of Theme [bootstrap3]:
74 | about.html
75 | base.html
76 | blank.html
77 | chart_collection.html
78 | chart_single.html
79 | home.html
80 | info_card.html
81 | items_grid.html
82 | items_list.html
83 | list.html
84 | list_with_paginator.html
85 | message.html
86 | widgets\values_panel.html
87 |
88 | Start to custom a template: python manage.py starttpl -n blank -o my_page
89 |
90 | ```
91 |
92 | ### 复制模板文件
93 |
94 | 使用 `-n` 复制一个文件,如果项目模板目录已经存在该文件,则需要添加 `-o` 覆盖,否则跳过复制。`-n` 的参数可以不添加后缀格式的 .html 。
95 |
96 | 复制内置的 message.html 文件
97 |
98 | ```text
99 | E:\projects\zinc> python .\manage.py starttpl message
100 | Generate message.html, Success!
101 | ```
102 |
103 | 创建新的空白模板
104 |
105 | ```text
106 | E:\projects\zinc> python .\manage.py starttpl blank -o my_page
107 | Generate my_page.html, Success!
108 | ```
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/docs/reference/stores.md:
--------------------------------------------------------------------------------
1 | # 组件存储类EntityFactory
2 |
3 | 本文对应于 `django_echarts.stores` 代码包。
4 |
5 | ## factory
6 |
7 | 全局变量,类型 `EntityFactory`。使用方式:
8 |
9 | ```python
10 | from django_echarts.stores import factory
11 |
12 | @factory.register_chart_widget
13 | def my_bar():
14 | bar = Bar()
15 | # ...
16 | return bar
17 |
18 | bar1 = factory.get_chart_widget('my_bar')
19 | ```
20 |
21 | ## EntityFactory
22 |
23 | `EntityFactory` 是存储echarts图表创建器或者具体组件实例对象的容器。
24 |
25 | ### html_widgets
26 |
27 | 属性,类型 : `LazyDict`。组件创建器或实例存储。
28 |
29 | ### chart_widgets
30 |
31 | 属性,类型 : `LazyDict`。图表创建器或实例存储。
32 |
33 | ### chart_info_manager
34 |
35 | 属性,类型: `ChartInfoManagerMixin`。 存储图表关联的 `ChartInfo` 数据类。
36 |
37 | ### register_chart_widget
38 |
39 | 方法,可用作装饰器。注册图表创建器或实例。
40 |
41 | ### register_html_widget
42 |
43 | 方法,可用作装饰器。注册HTML组件创建器或实例。
44 |
45 | ### register_chart_info
46 |
47 | 方法。关联已有的 `ChartInfo`。
48 |
49 | ### get_chart_and_info
50 |
51 | 方法。获取给定name对应的图表实例和关联的 ChartInfo 。
52 |
53 | ### get_chart_widget
54 |
55 | 方法。返回对应name的图表实例。
56 |
57 | ### get_html_widget
58 |
59 | 方法。返回对应name的HTML组件实例。
60 |
61 | ### get_widget_by_name
62 |
63 | 方法。返回对应name的图表或HTML组件实例。
64 |
65 | ```python
66 | chart1 = factory.get_widget_by_name('bar1')
67 |
68 | info = factory.get_widget_by_name('info:bar1')
69 | ```
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/docs/reference/system_design.md:
--------------------------------------------------------------------------------
1 | # 设计与实现
2 |
3 | 本文介绍了 django-echarts 的软件设计思想、体系和编程实现。
4 |
5 | ## 包体系设计
6 |
7 | v0.5.1版本在保持整体公共访问接口不变的基础上,进一步重构底层实现逻辑。目前按照代码包可划分为以下模块:
8 |
9 | | 代码包/模块 | 描述 | 备注 |
10 | | --------------------------- | ---------------------------------------- | ---- |
11 | | **配置层** | | |
12 | | django_echarts.core | 依赖项、主题配置 | |
13 | | django_echarts.conf | 配置访问入口 | |
14 | | **数据层** | | |
15 | | django_echarts.entities | 组件定义层 | |
16 | | django_echarts.stores | 组件全局存储入口 | |
17 | | **模板层** | | |
18 | | django_echarts.contrib | 内置的主题APP | |
19 | | django_echarts.templates | 模板文件 | |
20 | | **渲染层** | | |
21 | | django_echarts.renders | 组件、依赖项渲染逻辑 | |
22 | | django_echarts.templatetags | 标签函数 | |
23 | | **视图业务层** | | |
24 | | django_echarts.starter | 网站视图路由层 | |
25 | | django_echarts.views | 普通视图层 | |
26 | | **功能扩展** | | |
27 | | django_echarts.ajax_echarts | ajax方式渲染echarts图表逻辑 1 | |
28 | | django_echarts.management | 命令行工具 | |
29 | | django_echarts.geojson | geojson工具层 | |
30 |
31 | 1. 截至v0.5.x,该功能暂未实现。
32 |
33 |
34 |
35 | ## 单元测试
36 |
37 | django-echarts 坚持保证代码质量,包括 Typing Hints 、单元测试、代码覆盖率等工具和方法。
38 |
39 | 单元测试位于项目目录 `tests` (包含 `__init__.py` 文件,视为一个包)。
40 |
--------------------------------------------------------------------------------
/docs/reference/tags.md:
--------------------------------------------------------------------------------
1 | # 模板标签库
2 |
3 | ## 导入
4 |
5 | django-echarts 实现了与 pyecharts 功能上相似的模板标签,均定义在 `django_echarts.templatetags.echarts` 包。在使用之前需要先行导入,有两种方式。
6 |
7 | **方式一. 按需导入**
8 |
9 | 在每个需要使用标签函数的模板文件使用 `{% load echarts %}` 导入。
10 |
11 | **方式二. 统一导入**
12 |
13 | 添加标签目录到项目配置项 `TEMPLATES.OPTIONS.libraries` ,这样就无需在每个模板都使用 load 标签。
14 |
15 | ```python
16 | TEMPLATES = [
17 | {
18 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
19 | 'DIRS': [os.path.join(BASE_DIR, 'templates')],
20 | 'APP_DIRS': True,
21 | 'OPTIONS': {
22 | 'context_processors': [
23 | 'django.template.context_processors.debug',
24 | 'django.template.context_processors.request',
25 | 'django.contrib.auth.context_processors.auth',
26 | 'django.template.context_processors.static',
27 | 'django.contrib.messages.context_processors.messages',
28 | ],
29 | 'libraries': {
30 | 'echarts': 'django_echarts.templatetags.echarts'
31 | }
32 | },
33 | },
34 | ]
35 | ```
36 |
37 | ## 组件渲染
38 |
39 | ### dw_widget
40 |
41 | ```python
42 | django_echarts.templatetags.echarts.dw_widget(context, widget, **kwargs)
43 | ```
44 |
45 | 渲染单个组件,可以是图表组件、HTML组件和容器组件。使用方法如下:
46 |
47 | ```html
48 | {% dw_widget row_container %}
49 | {% dw_widget chart width="100%" height="700px" %}
50 | ```
51 |
52 | 可支持的参数:
53 |
54 | | 参数 | 类型 | 描述 |
55 | | ------ | ---------------------- | ------------------------------------------------------------ |
56 | | width | Union[int, float, str] | 宽度,仅适用于 pyecharts.charts.Base |
57 | | height | Union[int, float, str] | 高度,仅适用于 pyecharts.charts.Base |
58 | | class_ | str | 仅适用于LinkItem/Menu |
59 | | tpl | str | 模板文件,仅适用于 HTMLBase。默认为 *widgets/{widget_name}.html* |
60 |
61 | 当widget为None(不包括0, ''等空值),`dw_widget` 将输出空字符串。
62 |
63 | ```html
64 | # 直接使用即可
65 | {% dw_widget widget %}
66 |
67 | # 而不必使用if判断
68 | {% if widget %}
69 | {% dw_widget widget %}
70 | {% endif %}
71 | ```
72 |
73 | ### echarts_container
74 |
75 | 渲染图表,已被 `dw_widget` 替代。
76 |
77 | ## echarts初始化
78 |
79 | > 本节标签函数中参数 widgets 参数传入任何组件对象,标签函数自动提取其中的echarts图表对象。
80 |
81 | ### echarts_js_dependencies
82 |
83 | ```python
84 | django_echarts.templatetags.echarts.echarts_js_dependencies(*widgets)
85 | ```
86 | 渲染包含图表所需要的js文件的script一个或多个节点。该函数会对args里面的图表依赖项进行汇总去重。
87 |
88 | 在同一页面,应当保证 `echarts_js_dependencies` 只使用一次,避免部分库文件重复加载。
89 |
90 | ### echarts_js_content
91 |
92 | ```python
93 | django_echarts.templatetags.echarts.echarts_js_content(*widgets)
94 | ```
95 | 渲染图表初始js代码,支持多图表。包含首尾的 `` 标签。
96 |
97 | ### echarts_js_content_wrap
98 |
99 | ```python
100 | django_echarts.templatetags.echarts.echarts_js_content_wrap(*widgets)
101 | ```
102 |
103 | 渲染图表初始js代码,支持多图表。不包含首尾的 `` 标签。
104 |
105 | ## 主题相关
106 |
107 | ### theme_css
108 |
109 | ```python
110 | django_echarts.templatetags.dje.theme_css()
111 | ```
112 |
113 | 渲染主题css。
114 |
115 | ### theme_js
116 |
117 | ```python
118 | django_echarts.templatetags.dje.theme_js()
119 | ```
120 |
121 | 渲染主题javascript。
122 |
123 | ## 其他
124 |
125 | ### page_link
126 |
127 | ```python
128 | django_echarts.templatetags.echarts.page_link(context, page_number)
129 | ```
130 |
131 | 将当前url增加page参数。
132 |
133 | ***url_single_chart***
134 |
135 | ```python
136 | django_echarts.templatetags.echarts.url_single_chart(uri_or_name: Union[EntityURI, str], params_dic: dict = None)
137 | ```
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/docs/release-note/v050.md:
--------------------------------------------------------------------------------
1 | # v0.5.0发布日志
2 |
3 | > 发布日期:2022-03-18
4 |
5 | ## 1 脚手架
6 |
7 |
8 | django-echarts v0.5.0是项目新版本系列的第一个正式版本。从此版本开始, django-echarts 的定位从原来的 *工具库* 提升为 *脚手架* 。django-echarts力图在最短的时间内搭建一个可以显示可视化图表的web网站。
9 |
10 |
11 | 用户只需要编写图表的 pyecharts 代码,其他诸如页面样式、路由、视图逻辑等由django-echarts驱动,并支持可定制化。
12 |
13 |
14 | ## 2 丰富的图表类型
15 |
16 | django-echarts直接引用pyecharts图表配置逻辑并进行简单的语法适配(从jinja2到DTS),已经支持90%以上的图表类型(包括基本图表、地图、3D图、复合图表等)。
17 |
18 | django-echarts还提供了适用于web平台的图表类型,包括:
19 |
20 | - 响应式的表格,可支持 pyecharts.charts.Table 和 prettytable.PrettyTable
21 | - 地图底图支持原生geojson源
22 | - 多图表 NamedCharts
23 |
24 | ## 3 组件、布局和站点
25 |
26 | django-echarts 默认提供了一个通用的类CMS站点,该站点包含
27 |
28 | - 页面:首页、列表、单图表、图表合辑、关于
29 | - 组件:导航栏、网站底部、信息卡、数值面板、图表列表
30 |
31 | 对于每个页面,可以根据业务需求进行定制。用户也可以根据django的模板规则进行整体性和替换。
32 |
33 |
34 | ## 4 UI主题
35 |
36 | django-echarts 默认内置了两个主流的前端UI框架:Bootstrap 和 Materialcss 。借助 [bootswatch](https://bootswatch.com/) 项目,django-echarts也支持Bootstrap的调色主题切换。
37 |
38 | ## 5 生产力工具
39 |
40 | ### 下载器
41 |
42 |
43 | 默认情况下,django-echarts 使用由公共CDN提供的在线静态资源,主要包括:
44 |
45 | - 前端框架静态文件
46 | - echarts库静态文件
47 | - 地图数据文件
48 |
49 | django-echarts提供了基于Django Commands实现的下载器工具,通过简单的命令就可以把文件下载到本地,直接与 Django Staticfiles App 完美融合。
50 |
51 |
52 | ### 代码生成器
53 |
54 |
55 | 通过 `startsite` 命令生成站点框架代码,后续可以添加具体的图表代码。
56 |
57 |
58 | ## 6 代码标准化和规范化
59 |
60 |
61 | 在代码实现上,90%以上的代码使用 Python Typing Hints ,具有很强的可读性。
--------------------------------------------------------------------------------
/docs/release-note/v051.md:
--------------------------------------------------------------------------------
1 | # v0.5.1发布日志
2 |
3 | > 发布日志:2022-04-10
4 |
5 |
6 |
7 | ## 1 体系性、扩展性
8 |
9 | v0.5.1版本在保持整体公共访问接口不变的基础上,进一步重构底层实现逻辑。详情可查看文档 [《体系设计》](/reference/system_design/) 。
10 |
11 | ## 2 组件体系
12 |
13 | v0.5.1进一步抽象组件的共有特性,比如是否包括依赖项、是否是容器组件等。新增诸如 `ContainerBase` 、`HTMLWidgetBase` 等组件基类封装共同逻辑,,并支持容器组件的嵌套使用。
14 |
15 | 在实现上使用 [singledispatch](https://docs.python.org/3/library/functools.html#functools.singledispatch) 的函数重载功能,这样:
16 |
17 | - 所有组件拥有同一的入口,使用`dw_widget` 标签可渲染所有类型的组件
18 | - 将参数和变量的类型明确化,减少了 Duck Type 代码的使用。
19 |
20 | ## 3 新的组件存储层
21 |
22 | 将组件存储相关功能从`DJESite` 中移除,形成新的 `django_echarts.stores` 包。
23 |
24 | 这样,**保存图表创建器和组件的实例对象,由 `starter` 模块下移到 `stores` 模块,并实现单例模块。**。
25 |
26 | ```
27 | site.DJESite
28 | ^
29 | |
30 | stores.EntityFactory
31 | ^
32 | |
33 | Lazy
34 | ```
35 |
36 | 在实现细节上:
37 |
38 | - `DJESite` 不再实现 `WidgetGetterMixin`
39 | - 移除 `DJESite.resolve_chart` / `DJESite.resolve_html_widget`方法
40 | - `WidgetGetterMixin` 接口方法 `resolve_*` 重命名为 `get_*`
41 |
42 |
43 |
44 | ## 4 优化路由入口
45 |
46 | 移除 `DJESite.dje_get_urls` 接口,新增 `DJESite.extend_urlpatterns` 函数。
47 |
48 | ````python
49 | class MyPageView(DJESiteBackendView):
50 | pass
51 |
52 | site_obj.extend_urlpatterns([
53 | path('mypage/', MyPageView.as_view(), name='my-page')
54 | ])
55 | ````
56 |
57 | ## 5 命令行优化
58 |
59 | 命令 download / info 按图表查找依赖项不再需要设置 `site_class`, 使用 `stores.entity_factory.factory` 即可访问图表创建器和组件对象。
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/docs/release-note/v060.md:
--------------------------------------------------------------------------------
1 | # v0.6.0发布日志
2 |
3 | > 发布时间:2023年3月17日
4 |
5 | ## 1 新增pyecharts2.0适配
6 |
7 | **django-echarts 0.6.0支持pycharts 2.0。** pyecharts2.0的主要变化有两点:
8 |
9 | - 第一,仓库地址变化和地图绘制变化,主要包括echarts及其扩展库、echarts主题文件、地图文件。参见 [pyecharts-issue#2107](https://github.com/pyecharts/pyecharts/issues/2107) 。
10 | - 第二,纯pyecharts代码变化,如新的图表类型、图表配置等。具体可参见 pyecharts 项目。此项工作由用户自行修改。
11 |
12 | django-echarts 对远程仓库的处理如下:
13 |
14 | | django-echarts | pyecharts版本 | echarts | 引用仓库 | `dms_repo` 设置 |
15 | | -------------- | ------------- | ------- | ------------ | ----------------------------------- |
16 | | 0.5.x | 1.9 | 4.8.0 | pyecharts | `pyecharts` 或 缺省 |
17 | | 0.6.x | 1.9 | 4.8.0 | pyecharts | `pyecharts` 或 缺省 |
18 | | | 2.0 | 5.4.1 | pyecharts-v5 | `pyecharts` 、`pyecharts-v5`或 缺省 |
19 |
20 | 说明:
21 |
22 | - django-echarts 和 pyecharts 并无版本上的关联,只需要保证 pyecharts 和 echarts版本(仓库标识) 一致即可。
23 | - 为了简化配置,当 django-echarts0.6/pyecharts2.0,如果 `dms_repo='pyecharts' ` (默认配置),将自动切换为 `pyecharts-v5`。
24 |
25 | ## 2 仓库配置优化
26 |
27 | 单个依赖项设置支持批量配置。`dep2url` 字典key表示为仓库名称(需以`#`开头)。
28 |
29 | ```python
30 | DJANGO_ECHARTS = {
31 | 'dep2url':{
32 | '#local': ['echarts', '中国']
33 | }
34 | }
35 | ```
36 |
37 | 等效于
38 |
39 | ```python
40 | DJANGO_ECHARTS = {
41 | 'dep2url':{
42 | 'echarts':'#local',
43 | '中国':'#local'
44 | }
45 | }
46 | ```
47 |
48 | ## 3 图表构建函数支持自定义参数
49 |
50 | 图表构建函数支持自定义参数,可以根据不同的输入值,加载不同的数据,从而形成多个图表。
51 |
52 | 以之前的时间轴图表为例子,每次只显示一个年份的数据图表。
53 |
54 | ```python
55 | @site_obj.register_chart(title='{year}年福建省家庭户类型组成')
56 | def yearly_family(year: int):
57 | family_types = [
58 | '一人户', '二人户', '三人户', '四人户', '五人户', '六人户', '七人户', '八人户', '九人户', '十人及其以上'
59 | ]
60 | data = [
61 | [1982, 7.7, 8.2, 12.2, 17.1, 18.4, 14.7, 10.1, 11.6, 0, 0],
62 | [1990, 5.8, 8.6, 16.8, 23.6, 21.4, 11.8, 5.9, 2.9, 1.4, 1.8],
63 | [2000, 9.1, 15.5, 25.4, 24.7, 15.8, 5.9, 2.2, 0.8, 0.3, 0.3],
64 | [2010, 12.1, 17.2, 24.3, 21.7, 13.7, 6.4, 2.6, 1.1, 0.5, 0.4],
65 | [2020, 27.3, 26.3, 19.4, 14.2, 6.9, 4, 1.1, 0.4, 0.2, 0.2]
66 | ]
67 | yearly_data = {item[0]: item[1:] for item in data}
68 | if year not in yearly_data:
69 | raise ChartDoesNotExist(f'year={year}')
70 | year_data = yearly_data[year]
71 | bar = (
72 | Bar()
73 | .add_xaxis(family_types).add_yaxis('百分比(%)', year_data)
74 | .set_global_opts(title_opts=opts.TitleOpts("福建省家庭户类型构成-{}年".format(year)))
75 | )
76 | return bar
77 | ```
78 |
79 | 说明:
80 |
81 | - year为自定义参数,如未声明类型,默认为 `str`。
82 | - 如果传入的year找不到对应的数据,抛出 `ChartDoesNotExist` 异常,web页面显示“找不到图表”的页面。
83 | - title参数可以使用类似 `{year}`的模板字符串。
84 |
85 | 该图表对应的相关引用配置如下(以year=2020例子):
86 |
87 | | 项目 | 值 | 备注 |
88 | | ---------- | ------------------------------------------------------------ | -------------------------------------------- |
89 | | 图表URL | `/chart/year/2020/` | 格式:`<图表slug>/<参数名称1>/<参数值1>/...` |
90 | | python引用 | `reverse_lazy('dje_single_chart','yearly_family','year/2020/')` | |
91 | | 模板引用 | `{% url 'dje_single_chart' 'yearly_family', 'year/2020/' %}` | |
92 |
93 | ## 4 重构自定义地图,支持geojson&svg
94 |
95 | v0.6开始,新增 `django_echarts.custom_maps` 模块,原有的 `django_echarts.geojson` 标记为废弃状态。
96 |
97 | 将原有的 `use_geojson` 替换为 `use_custom_map` 即可。
98 |
99 | ```python
100 | chart = Map(...)
101 | use_custom_map(chart, '中国省市地图', 'china_cities.geojson')
102 | ```
103 |
104 |
105 |
106 | ## 5 新增声明式导航栏配置
107 |
108 | django-echarts 0.6 新增了使用字典方式一次性配置导航栏配置。
109 |
110 | ```python
111 | # site_config.py
112 |
113 | nav_config = {
114 | 'nav_left': ['home', 'list'],
115 | 'nav_right': ['settings'],
116 | 'nav_footer': [
117 | {'text': '项目主页', 'slug': 'project_url', 'url': 'https://github.com/kinegratii/django-echarts'}
118 | ]
119 | }
120 |
121 | # urls.py
122 |
123 | site_obj.config_nav(nav_config)
124 | ```
125 |
126 | 相应的,DJESite类移除导航栏相关方法,可使用 `DJESite.nav` 相关方法。
127 |
128 | - `DJESite.add_left_link`
129 | - `DJESite.add_right_link`
130 | - `DJESite.add_footer_link`
131 | - `DJESite.add_menu_item`
132 |
133 | ## 6 ChartInfo和ChartInfoManager
134 |
135 | - 类 `ChartInfo` 增加 `is_bound` 布尔值属性,如果对应图表注册函数携带任何参数,则设置为 `False`,数据格式化后变为`True`。
136 | - `ChartInfoManagerMixin.get_or_none` 增加 `uri` 参数。
137 | - 在 `is_bound = False` 情况下,body title description的转义处理
138 |
139 | ## 7 其他依赖库更新
140 |
141 | ```
142 | typing_extensions 4.0 -> 4.5
143 | borax 3.5 -> 4.0
144 | ```
145 |
146 |
--------------------------------------------------------------------------------
/docs/starter/site_configuration.md:
--------------------------------------------------------------------------------
1 | # 网站配置
2 |
3 | ## 导航栏(Nav)
4 |
5 | > Update in 0.6.0: 新增 `DJESite.config_nav` 函数。
6 |
7 | 网站导航栏包括:左侧导航栏、右侧导航栏、底部导航栏,其中左侧、右侧(均位于顶部)支持下拉菜单,底部导航栏仅支持多个链接形式。
8 |
9 | 有两种使用方法。
10 |
11 | ## SiteOptions.nav_shown_pages
12 |
13 | ```python
14 | class SiteOpts:
15 | """The opts for DJESite."""
16 | # ...
17 | nav_shown_pages: List = field(default_factory=lambda: ['home'])
18 | ```
19 |
20 | 该属性在 `DJESite` 对象初始化的时候传入,`nav_shown_pages` 为包含表示特定页面标识(字符串)的列表,可选的有:home / list / collection / settings / chart_nav 。
21 |
22 | 其中chart_nav需调用 `DJESite.config_nav`。
23 |
24 | ## 函数config_nav
25 |
26 | ```python
27 | DJESite.config_nav(self, menu_config: dict = None)
28 | ```
29 |
30 | 函数`config_nav` 用于配置导航栏,需注意两点:
31 |
32 | - 该函数是一次性配置的,将覆盖 `SiteOptions.nav_shown_pages`的配置。
33 | - 该函数必须在所有 `register_chart` 之后调用,一般在 `urls`模块调用。
34 |
35 | `menu_config` 参数基本形式如下:
36 |
37 | ```python
38 | class NavConfig(TypedDict):
39 | nav_left: NotRequired[List[Union[str, dict]]]
40 | nav_right: NotRequired[List[Union[str, dict]]]
41 | nav_footer: NotRequired[List[Union[str, dict]]]
42 | ```
43 |
44 | 一个菜单项可以是以下几种形式:
45 |
46 | - 内置页面(str):home / list / collection / settings
47 | - 图表分类导航(str):chart_nav
48 | - 图表或合辑资源标识符(str):使用其URI字符串形式
49 | - django视图链接(str):使用视图名称及其参数
50 | - 自定义链接(dict):适用于菜单或菜单项
51 |
52 | ### 1 自定义链接
53 |
54 | 这是最为基础的定义形式,完整的链接定义如下:
55 |
56 | ```python
57 | {'text': '网站源码', 'slug':'site_source', 'url': 'https://github.com/kinegratii/zinc', 'new_page': True}
58 | ```
59 |
60 | | 属性 | 描述 |
61 | | -------- | ---------------------------------- |
62 | | text | 必选,链接文字。 |
63 | | url | 必选,链接。 |
64 | | slug | 可选,标识符,默认生成uuid字符串。 |
65 | | new_page | 可选,默认为False。 |
66 |
67 | ### 2 菜单定义
68 |
69 | ```python
70 | {'text': '网站源码', 'slug':'site_source', 'children':[...]}
71 | ```
72 |
73 | 使用键 `children` 表明这是一个菜单列表,下级菜单定义在属性children之中。
74 |
75 | ### 3 内置页面
76 |
77 | 可支持的内置页面如下:
78 |
79 | | 标识符 | 页面 |
80 | | ---------- | ---------- |
81 | | home | 首页 |
82 | | list | 图表列表页 |
83 | | collection | 合辑列表页 |
84 | | settings | 设置页面 |
85 |
86 | ### 3 Django视图
87 |
88 | ```python
89 | {'text':'大屏面板', 'view_name': 'dashbord', 'args':[], 'kwargs':{},'new_page':False}
90 | ```
91 |
92 | 使用 `reverse_lazy` 反向解析url。
93 |
94 | ### 4 图表资源
95 |
96 | ```python
97 | {'text': '2020年数据', 'slug':'family_type_2020', 'url': 'chart:yearly_family_types/year/2020', 'new_page': False}
98 | ```
99 |
100 | 类似于
101 |
102 | ```python
103 | {'text':'2020年数据', 'view_name': 'dje_chart_single', 'args':['yearly_family_types','/year/2020'], 'kwargs':{},'new_page':False}
104 | ```
105 |
106 |
107 |
108 | ### 5 图表分类导航chart_nav
109 |
110 | ```python
111 | 'chart_nav'
112 | ```
113 |
114 | 该字符串仅为占位符,函数 `DJESite.config_nav` 将根据 `register_chart` 的 catalog,在该位置添加菜单栏。
115 |
116 | 该配置仅可用于左侧导航栏。
117 |
118 | ## 配置示例
119 |
120 | ### 默认配置
121 |
122 | 默认配置(不调用 `config_nav`函数),仅包含 `nav_shown_pages`所定义菜单。
123 |
124 | ### 0.5兼容配置
125 |
126 | 以下是 django-echart 0.6 兼容0.5版本的配置方法:
127 |
128 | ```python
129 | site_obj = DJESite(title='示例站点', opts=SiteOpts(nav_shown_pages=['home', 'list', 'chart_nav']))
130 |
131 |
132 | @site_obj.register(..., catalog='菜单1')
133 | def chart_1():
134 | pass
135 |
136 |
137 | site_obj.config_nav()
138 | ```
139 |
140 | ### 常规配置
141 |
142 | ```python
143 | nav = {
144 | 'nav_left': ['home', 'list', 'chart_nav', {'text': '2020年数据', 'url': 'chart:yearly_family/year/2020'}],
145 | 'nav_right': ['settings'],
146 | 'nav_footer': [
147 | {'text': '福建统计年鉴2021', 'url': 'https://tjj.fujian.gov.cn/tongjinianjian/dz2021/index.htm', 'new_page': True},
148 | {'text': '网站源码', 'url': 'https://github.com/kinegratii/zinc', 'new_page': True},
149 | ]
150 | }
151 | ```
152 |
153 |
154 |
155 | ### 关联导航栏
156 |
157 | 下面是演示如何添加到导航菜单栏的。以 `chart.title = 'Chart1'` 为例子:
158 |
159 | | chart.catalog | nav_parent_name | 说明 |
160 | | ------------- | --------------- | --------------------------------- |
161 | | None(未设置) | None(未设置) | 不显示在菜单栏上 |
162 | | 'BarCharts' | None(未设置) | 显示,“BarCharts- Chart1” |
163 | | None(未设置) | 'Menu' | 显示,“Menu - Chart1” |
164 | | 'BarCharts' | 'Menu' | 显示 "Menu - Chart1" 1 |
165 | | 任意 | 'self' | 显示一级菜单2 |
166 | | 任意 | 'none' | 不显示3 |
167 |
168 | 1. 只要 catalog 和 nav_parent_name 至少一个有设置,均显示为二级菜单
169 | 2. 使用特殊标识 'self' 表示显示为一级菜单
170 | 3. 使用特殊标识 'none' 表示不显示
171 |
--------------------------------------------------------------------------------
/long_description.md:
--------------------------------------------------------------------------------
1 | # django-echarts
2 |
3 |   
4 |
5 |
6 |
7 | > A visual site scaffold based on pyecharts and django.
8 |
9 | django-echarts 是一个基于[pyecharts](https://github.com/pyecharts/pyecharts) 和 [Django](https://www.djangoproject.com) 整合的可视化网站脚手架。
10 |
11 | ## 概述(Summary)
12 |
13 | django-echarts 主要提供了以下的内容:
14 |
15 | - 支持 90%+的pyecharts图表类型
16 | - 可显示页面:主页 / 列表 / 详情 / 关于
17 | - 可支持组件:导航栏 / 网站底部栏 / 热门板块 / 列表 / 合辑 / 关于面板
18 | - UI主题:Bootstrap3 / Bootstrap5 / Material ,支持更换颜色模式
19 | - 可灵活扩展: 支持整合 Django用户认证 / 数据库 / Session
20 | - 基于Django Template Engine 的后端渲染
21 | - js/css静态文件托管,支持在线/本地切换
22 | - 生产力工具:代码生成器 / 静态文件下载器
23 | - 90%+ Python Typing Hints覆盖
24 |
25 | ## 文档(Document)
26 |
27 | [在线文档](https://django-echarts.readthedocs.io/)
28 |
29 | Build on [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/)
30 |
31 | ## 示例项目(Demo Project)
32 |
33 | 参见项目 [kinegratii/zinc](https://github.com/kinegratii/zinc) 。
34 |
35 | ## 开源协议(License)
36 |
37 | MIT License
38 |
--------------------------------------------------------------------------------
/mkdocs.yaml:
--------------------------------------------------------------------------------
1 | site_name: django-echarts
2 | theme:
3 | language: zh
4 | icon:
5 | logo: material/monitor-dashboard
6 | name: material
7 | palette:
8 | primary: teal
9 | features:
10 | - navigation.tabs
11 | - navigation.sections
12 | extra:
13 | social:
14 | - icon: fontawesome/brands/github
15 | link: https://github.com/kinegratii/django-echarts
16 | - icon: fontawesome/brands/python
17 | link: https://pypi.org/project/django-echarts
18 | copyright: Copyright © 2017 - 2023 Samuel.Yan
19 | markdown_extensions:
20 | - pymdownx.highlight
21 | - pymdownx.superfences
22 | - mdx_truly_sane_lists
23 | nav:
24 | - 快速开始: index.md
25 | - 可视化构建:
26 | - 使用教程: starter/base.md
27 | - 网站配置: starter/site_configuration.md
28 | - 组件和布局: starter/widget_and_layout.md
29 | - UI框架和主题: starter/themes.md
30 | - 依赖项(一):基本配置: guides/dms.md
31 | - 依赖项(二):本地化: starter/localize_staticfiles.md
32 | - 参数化图表: guides/entity_params.md
33 | - 自定义地图: guides/custom_maps.md
34 | - 高级开发: guides/advance_development.md
35 | - Gallery: gallery.md
36 | - 参考:
37 | - 配置及其访问: reference/configure.md
38 | - 组件存储类: reference/stores.md
39 | - 模板标签库: reference/tags.md
40 | - 视图和模板: reference/views_and_templates.md
41 | - 命令行工具: reference/commands.md
42 | - 设计与实现: reference/system_design.md
43 | - 文章:
44 | - 新的页面功能: posts/new_page_feature.md
45 | - geojson地图: posts/use_custom_geojson.md
46 | - 图表合辑: posts/widget_collection.md
47 | - 第三方HTML渲染库: posts/third_html_library.md
48 | - 与pyecharts: posts/pyecharts_project.md
49 | - 项目开发:
50 | - 更新日志: changelog.md
51 | - 破坏性变更: breaking-change.md
52 | - 关于项目: about.md
53 | - 发布日志:
54 | - v0.6.0: release-note/v060.md
55 | - v0.5.1: release-note/v051.md
56 | - v0.5.0: release-note/v050.md
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | typing_extensions~=4.5
2 | borax~=4.0
3 | htmlgenerator~=1.2
--------------------------------------------------------------------------------
/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | typing_extensions~=4.5
2 | pyecharts~=2.0
3 | borax~=4.0
4 | django~=3.2
5 | htmlgenerator~=1.2
6 | flake8~=3.9
7 | mccabe~=0.6
--------------------------------------------------------------------------------
/requirements_doc.txt:
--------------------------------------------------------------------------------
1 | mkdocs-material==8.3.3
2 | mdx-truly-sane-lists==1.3
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore = E743,E501,F401,E126
3 | max-complexity = 18
4 | exclude = .git,__pycache__,docs/source/conf.py,old,build,dist
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 |
3 | import pathlib
4 | import re
5 |
6 | from setuptools import setup, find_packages
7 |
8 | here = pathlib.Path(__file__).parent
9 | txt = (here / 'django_echarts' / '__init__.py').read_text()
10 | __version__ = re.findall(r"^__version__ = '([^']+)'\r?$", txt, re.M)[0]
11 |
12 | lib_classifiers = [
13 | "Development Status :: 4 - Beta",
14 | "Programming Language :: Python :: 3",
15 | "Programming Language :: Python :: 3.7",
16 | "Programming Language :: Python :: 3.8",
17 | "Programming Language :: Python :: 3.9",
18 | "Programming Language :: Python :: 3.10",
19 | "Programming Language :: Python :: 3 :: Only",
20 | "Framework :: Django",
21 | "Framework :: Django :: 2.0",
22 | "Framework :: Django :: 2.1",
23 | "Framework :: Django :: 2.2",
24 | "Framework :: Django :: 3.0",
25 | "Framework :: Django :: 3.1",
26 | "Framework :: Django :: 3.2",
27 | "Framework :: Django :: 4.0",
28 | "Intended Audience :: Developers",
29 | "License :: OSI Approved :: MIT License",
30 | "Topic :: Software Development :: Libraries",
31 | "Topic :: Utilities",
32 | 'Operating System :: OS Independent'
33 | ]
34 |
35 | with open('long_description.md', encoding='utf8') as f:
36 | long_description = f.read()
37 |
38 | setup(
39 | name='django-echarts',
40 | version=__version__,
41 | packages=find_packages(exclude=['tests']),
42 | url='https://github.com/kinegratii/django-echarts',
43 | include_package_data=True,
44 | license='MIT',
45 | author='kinegratii',
46 | author_email='kinegratii@gmail.com',
47 | description='A visual site scaffold based on pyecharts and django.',
48 | classifiers=lib_classifiers,
49 | python_requires='>=3.7',
50 | install_requires=[
51 | 'borax>=3.5.3',
52 | 'typing_extensions',
53 | 'htmlgenerator~=1.2'
54 | ],
55 | long_description=long_description,
56 | long_description_content_type='text/markdown'
57 | )
58 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kinegratii/django-echarts/12de6cf4faab1ecbbc06bd487727cc16791ee556/tests/__init__.py
--------------------------------------------------------------------------------
/tests/settings_mock.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for zinc project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.2.11.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.2/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/3.2/ref/settings/
11 | """
12 | import os
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve().parent.parent
17 |
18 | # Quick-start development settings - unsuitable for production
19 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
20 |
21 | # SECURITY WARNING: keep the secret key used in production secret!
22 | SECRET_KEY = 'django-insecure-nh&ow1+n($82a5=3*qg_8=i0!h=j=y20wdzo6)h=^*5j*px#)k'
23 |
24 | # SECURITY WARNING: don't run with debug turned on in production!
25 | DEBUG = True
26 |
27 | ALLOWED_HOSTS = ['*']
28 |
29 | # Application definition
30 |
31 | INSTALLED_APPS = [
32 | 'django.contrib.admin',
33 | 'django.contrib.auth',
34 | 'django.contrib.contenttypes',
35 | 'django.contrib.sessions',
36 | 'django.contrib.messages',
37 | 'django.contrib.staticfiles',
38 | 'django_echarts',
39 | 'django_echarts.contrib.bootstrap5',
40 | ]
41 |
42 | MIDDLEWARE = [
43 | 'django.middleware.security.SecurityMiddleware',
44 | 'django.contrib.sessions.middleware.SessionMiddleware',
45 | 'django.middleware.common.CommonMiddleware',
46 | 'django.middleware.csrf.CsrfViewMiddleware',
47 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
48 | 'django.contrib.messages.middleware.MessageMiddleware',
49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
50 | ]
51 |
52 | ROOT_URLCONF = 'zinc.urls'
53 |
54 | TEMPLATES = [
55 | {
56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
57 | 'DIRS': [BASE_DIR / 'templates'],
58 | 'APP_DIRS': True,
59 | 'OPTIONS': {
60 | 'context_processors': [
61 | 'django.template.context_processors.debug',
62 | 'django.template.context_processors.request',
63 | 'django.contrib.auth.context_processors.auth',
64 | 'django.contrib.messages.context_processors.messages',
65 | ],
66 | },
67 | },
68 | ]
69 |
70 | WSGI_APPLICATION = 'zinc.wsgi.application'
71 |
72 | # Database
73 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases
74 |
75 |
76 | # Password validation
77 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
78 |
79 | AUTH_PASSWORD_VALIDATORS = [
80 | {
81 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
82 | },
83 | {
84 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
85 | },
86 | {
87 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
88 | },
89 | {
90 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
91 | },
92 | ]
93 |
94 | # Internationalization
95 | # https://docs.djangoproject.com/en/3.2/topics/i18n/
96 |
97 | LANGUAGE_CODE = 'zh-Hans'
98 |
99 | TIME_ZONE = 'UTC'
100 |
101 | USE_I18N = True
102 |
103 | USE_L10N = True
104 |
105 | USE_TZ = True
106 |
107 | # Static files (CSS, JavaScript, Images)
108 | # https://docs.djangoproject.com/en/3.2/howto/static-files/
109 |
110 | STATIC_URL = '/static/'
111 | STATICFILES_DIRS = (
112 | os.path.join(BASE_DIR, 'static').replace('\\', '/'),
113 | )
114 |
115 | # Default primary key field type
116 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
117 |
118 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
119 |
120 | LOGGING_CONFIG = {}
121 |
122 | LOGGING = {}
123 |
--------------------------------------------------------------------------------
/tests/test_dms.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from collections import namedtuple
3 | from unittest.mock import patch
4 |
5 | from django_echarts.core.settings_store import SettingsStore, DJEOpts
6 |
7 | MockAppConfig = namedtuple('MockAppConfig', 'name')
8 |
9 |
10 | class DMSFromSettingsTestCase(unittest.TestCase):
11 | @patch('django.apps.apps.get_app_configs')
12 | def test_custom_url_settings(self, get_func):
13 | get_func.return_value = [MockAppConfig('django_echarts.contrib.bootstrap5')]
14 | target_store = SettingsStore(
15 | echarts_settings=DJEOpts(dms_repo='pyecharts', theme_app='django_echarts.contrib.bootstrap5', dep2url={
16 | 'foo': 'https://foo.icc/foo.js',
17 | 'zzz': '#local',
18 | 'china': '#local',
19 | 'err': '#err',
20 | '#local': ['c1', 'c2']
21 | }),
22 | extra_settings={
23 | 'STATIC_URL': '/static/'
24 | }
25 | )
26 |
27 | self.assertEqual('/static/assets/zzz.js', target_store.resolve_url('zzz'))
28 | self.assertEqual('/static/assets/c1.js', target_store.resolve_url('c1'))
29 | self.assertEqual('/static/assets/c2.js', target_store.resolve_url('c2'))
30 | self.assertEqual('/static/assets/maps/china.js', target_store.resolve_url('china'))
31 |
--------------------------------------------------------------------------------
/tests/test_factory.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from django_echarts.entities import (Title, ChartInfo, WidgetCollection)
4 | from django_echarts.entities.uri import EntityURI, ParamsConfig
5 | from django_echarts.stores.entity_factory import EntityFactory
6 |
7 |
8 | class MockChart:
9 | def __init__(self, width, height):
10 | self.width = width
11 | self.height = height
12 |
13 |
14 | class EntityFactoryTestCase(unittest.TestCase):
15 | def test_factory(self):
16 | my_factory = EntityFactory()
17 | my_factory.register_html_widget(Title('Demo1'), name='demo1')
18 | self.assertEqual('Demo1', my_factory.get_widget_by_name('demo1').text)
19 | self.assertEqual('Demo1', my_factory.get_html_widget('demo1').text)
20 | my_factory.register_chart_widget(MockChart('100%', '500px'), name='chart1')
21 | info = my_factory.get_widget_by_name('info:chart1')
22 | self.assertTrue(isinstance(info, ChartInfo))
23 | self.assertEqual('100%', my_factory.get_widget_by_name('chart1').width)
24 | self.assertEqual('100%', my_factory.get_chart_widget('chart1').width)
25 | self.assertEqual(1, my_factory.get_chart_total())
26 | self.assertEqual(None, my_factory.get_widget_by_name('error'))
27 |
28 | chart1, exits, info1 = my_factory.get_chart_and_info('chart1')
29 | self.assertTrue(isinstance(info1, ChartInfo))
30 |
31 | my_factory.register_chart_widget(MockChart('100%', '400px'), name='chart2', info=False)
32 | self.assertEqual(None, my_factory.get_widget_by_name('info:chart2'))
33 |
34 | _, exits2, _ = my_factory.get_chart_and_info('chart3')
35 | self.assertEqual(False, exits2)
36 |
37 | wc = WidgetCollection('test')
38 | wc.add_chart_widget('chart1')
39 | wc.add_html_widget(['demo1'])
40 | wc.auto_mount(my_factory)
41 |
42 | def test_factory_uri_methods(self):
43 | factory2 = EntityFactory()
44 |
45 | @factory2.register_chart_widget
46 | def chart1():
47 | pass
48 |
49 | @factory2.register_chart_widget(info=ChartInfo('chart2'))
50 | def chart2(year: int):
51 | pass
52 |
53 | @factory2.register_chart_widget(info=ChartInfo('chart3', params_config=ParamsConfig({'year': [2022, 2023]})))
54 | def chart3(year: int):
55 | return MockChart(100, 100)
56 |
57 | uri_list = list(factory2.get_all_chart_uri())
58 | uri_string_list = [str(uri) for uri in uri_list]
59 | self.assertEqual(3, len(uri_list))
60 | self.assertIn('chart:chart3/year/2022', uri_string_list)
61 | self.assertFalse(any(['chart2' in s for s in uri_string_list]))
62 |
63 | widget = factory2.get_widget_by_uri(EntityURI.from_str('chart:chart3/year/2022'))
64 | self.assertTrue(isinstance(widget, MockChart))
65 | info = factory2.get_widget_by_uri(EntityURI.from_str('info:chart3/year/2022'))
66 | self.assertTrue(isinstance(info, ChartInfo))
67 |
--------------------------------------------------------------------------------
/tests/test_hosts.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 |
3 |
4 | import unittest
5 |
6 | from django_echarts.core.dms import DependencyManager
7 |
8 |
9 | class DependencyManagerTestCase(unittest.TestCase):
10 | def test_lib_host(self):
11 | # Basic tests
12 | m_context = {
13 | 'STATIC_URL': '/static/',
14 | 'echarts_version': '4.8.0',
15 | 'baidu_map_ak': 'foo23zoo'
16 | }
17 | manager = DependencyManager.create_default(
18 | context=m_context,
19 | repo_name='bootcdn',
20 | )
21 | manager.add_repo('bootcdn', repo_url='https://cdn.bootcss.com/echarts/{echarts_version}/')
22 |
23 | self.assertEqual(
24 | 'https://cdn.bootcss.com/echarts/4.8.0/echarts.min.js',
25 | manager.resolve_url('echarts.min')
26 | )
27 | self.assertEqual('https://api.map.baidu.com/api?v=2.0&ak=foo23zoo',
28 | manager.resolve_url('https://api.map.baidu.com/api?v=2.0&ak=xxx'))
29 |
30 | def test_map_host(self):
31 | m_context = {
32 | 'STATIC_URL': '/static/',
33 | 'echarts_version': '4.8.0'
34 | }
35 | manager = DependencyManager.create_default(
36 | context=m_context,
37 | )
38 | manager.add_repo('china-provinces', repo_url='https://echarts-maps.github.io/echarts-china-provinces-js/')
39 | self.assertEqual(
40 | 'https://echarts-maps.github.io/echarts-china-provinces-js/maps/china.js',
41 | manager.resolve_url('china', repo_name='china-provinces')
42 | )
43 | # Add
44 | manager.add_repo('amap', 'https://amap.com/js')
45 | self.assertEqual(
46 | 'https://amap.com/js/fujian.js',
47 | manager.resolve_url('fujian', 'amap')
48 | )
49 |
50 | def test_custom_d2u(self):
51 | m_context = {
52 | 'STATIC_URL': '/static/',
53 | 'echarts_version': '4.8.0',
54 | 'baidu_map_ak': 'foo'
55 | }
56 | manager = DependencyManager.create_default(
57 | context=m_context
58 | )
59 | manager.load_from_dep2url_dict({
60 | 'baidu_map_api': 'https://api.map.baidu.com/api?v=2.0&ak={baidu_map_ak}',
61 | 'baidu_map_script': 'https://api.map.baidu.com/getscript?v=2.0&ak={baidu_map_ak}'
62 | })
63 | self.assertEqual('https://api.map.baidu.com/api?v=2.0&ak=foo', manager.resolve_url('baidu_map_api'))
64 |
65 |
66 | class CustomHostTestCase(unittest.TestCase):
67 | def test_add_host(self):
68 | m_context = {
69 | 'echarts_version': '4.8.0'
70 | }
71 |
72 | manager = DependencyManager.create_default(
73 | context=m_context,
74 | repo_name='pyecharts'
75 | )
76 | manager.add_repo('demo', '/demo/')
77 | manager.add_repo('demo2', '/demo2/{echarts_version}')
78 | self.assertEqual(
79 | '/demo/fujian.js',
80 | manager.resolve_url('fujian', repo_name='demo')
81 | )
82 | self.assertEqual(
83 | '/demo2/4.8.0/fujian.js',
84 | manager.resolve_url('fujian', repo_name='demo2')
85 | )
86 |
--------------------------------------------------------------------------------
/tests/test_interface.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 |
3 | import unittest
4 |
5 | from unittest.mock import MagicMock
6 |
7 | from django_echarts.renders import get_js_dependencies, flat_chart
8 |
9 |
10 | class MockCharts:
11 | """
12 | A mock class for pyecharts.base.Base and pyecharts.custom.page.Page.
13 | """
14 |
15 | def __init__(self, js_dependencies):
16 | self.js_dependencies = MagicMock(items=js_dependencies)
17 |
18 |
19 | @flat_chart.register(MockCharts)
20 | def flat_mock(widget):
21 | return [widget]
22 |
23 |
24 | class JsMergeTestCase(unittest.TestCase):
25 | BASE_CHART = MockCharts(['echarts'])
26 | MAP_CHART = MockCharts(['echarts', 'fujian'])
27 | THREE_D_CHART = MockCharts(['echarts', 'echartsgl'])
28 |
29 | def test_one_chart_or_page(self):
30 | # One chart or one page
31 | self.assertListEqual(
32 | ['echarts'],
33 | get_js_dependencies(self.BASE_CHART)
34 | )
35 | self.assertListEqual(
36 | ['echarts', 'fujian'],
37 | get_js_dependencies(self.MAP_CHART)
38 | )
39 |
40 | def test_multiple_charts_and_pages(self):
41 | # Multiple charts
42 | self.assertListEqual(
43 | ['echarts', 'fujian'],
44 | get_js_dependencies([self.BASE_CHART, self.MAP_CHART])
45 | )
46 | self.assertListEqual(
47 | ['echarts', 'echartsgl'],
48 | get_js_dependencies([self.BASE_CHART, self.THREE_D_CHART])
49 | )
50 | self.assertListEqual(
51 | ['echarts', 'echartsgl', 'fujian'],
52 | get_js_dependencies([self.MAP_CHART, self.THREE_D_CHART])
53 | )
54 | self.assertListEqual(
55 | ['echarts', 'echartsgl', 'fujian'],
56 | get_js_dependencies([self.BASE_CHART, self.MAP_CHART, self.THREE_D_CHART])
57 | )
58 |
59 | def test_string_dependencies(self):
60 | self.assertListEqual(
61 | ['echarts', 'echartsgl'],
62 | get_js_dependencies(['echartsgl', self.BASE_CHART])
63 | )
64 |
--------------------------------------------------------------------------------
/tests/test_layouts.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from django_echarts.entities import RowContainer, HTMLBase
4 | from django_echarts.entities.layouts import any2layout
5 |
6 |
7 | class MockWidget(HTMLBase):
8 | pass
9 |
10 |
11 | class LayoutOptsTestCase(unittest.TestCase):
12 | def test_create_layout_opts(self):
13 | lto = any2layout('l8')
14 | self.assertEqual('l', lto.pos)
15 | self.assertListEqual([8], lto.spans)
16 | self.assertEqual(False, lto.start)
17 |
18 | with self.assertRaises(ValueError):
19 | any2layout('e4')
20 |
21 | lto2 = any2layout(9)
22 | self.assertListEqual([9], lto2.spans)
23 |
24 | lto3 = any2layout([4, 8])
25 | self.assertListEqual([4, 8], lto3.spans)
26 |
27 | s_lto3 = lto.stripped_layout()
28 | self.assertEqual('r', s_lto3.pos)
29 | self.assertListEqual([8], s_lto3.spans)
30 |
31 |
32 | class RowContainerTestCase(unittest.TestCase):
33 | def test_row_container_layout(self):
34 | rc = RowContainer()
35 | rc.add_widget(MockWidget(), span=2)
36 | rc.add_widget(MockWidget(), span=10)
37 | rc.auto_layout()
38 | self.assertTupleEqual((2, 10), rc.get_spans())
39 |
40 | rc2 = RowContainer()
41 | rc2.add_widget(MockWidget(), span=2)
42 | rc2.add_widget(MockWidget())
43 | rc2.auto_layout()
44 | self.assertTupleEqual((2, 10), rc.get_spans())
45 |
46 | def test_set_spans(self):
47 | rc = RowContainer()
48 | rc.add_widget(MockWidget(), span=2)
49 | rc.add_widget(MockWidget(), span=10)
50 | rc.set_spans([4, 8])
51 | self.assertTupleEqual((4, 8), rc.get_spans())
52 | rc.set_spans(6)
53 | self.assertTupleEqual((6, 6), rc.get_spans())
54 |
--------------------------------------------------------------------------------
/tests/test_mcharts.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 | """
3 | Test Cases for NamedCharts
4 | """
5 |
6 | import unittest
7 |
8 | from django_echarts.entities import NamedCharts, BlankChart
9 |
10 |
11 | class MChartsTestCase(unittest.TestCase):
12 | def test_create_with_kwargs(self):
13 | nc = NamedCharts().add_widget(
14 | BlankChart(page_title='Line-Chart'), name='line'
15 | ).add_widget(
16 | BlankChart(page_title='Bar-Chart')
17 | ).add_widget(
18 | BlankChart(page_title='Map-Chart'), name='map'
19 | )
20 | self.assertEqual('Line-Chart', nc['line'].page_title)
21 | self.assertEqual('Bar-Chart', nc['c1'].page_title)
22 | self.assertListEqual(
23 | ['Line-Chart', 'Bar-Chart', 'Map-Chart'],
24 | [c.page_title for c in nc]
25 | )
26 | with self.assertRaises(KeyError):
27 | c = nc['no_charts']
28 | print(c.page_title)
29 | self.assertEqual('Bar-Chart', nc[1].page_title)
30 |
31 |
32 | class MChartsPY36TestCase(unittest.TestCase):
33 | def test_create_with_kwargs(self):
34 | nc = NamedCharts()
35 | nc.add_widget(BlankChart(page_title='Bar-Chart'), name='bar')
36 | nc.add_widget(BlankChart(page_title='Line-Chart'), name='line')
37 | nc.add_chart(BlankChart(page_title='Map-Chart'), name='map')
38 | self.assertEqual('Bar-Chart', nc['bar'].page_title)
39 | self.assertListEqual(
40 | ['Bar-Chart', 'Line-Chart', 'Map-Chart'],
41 | [c.page_title for c in nc]
42 | )
43 |
--------------------------------------------------------------------------------
/tests/test_section_counter.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 |
3 |
4 | import unittest
5 | import random
6 |
7 | from django_echarts.datatools.section_counter import BValueIndex, BRangeIndex, BSectionIndex, BSectionCounter
8 |
9 |
10 | class BRangeIndexTestCase(unittest.TestCase):
11 | def test_basic(self):
12 | ri = BRangeIndex(3, 7)
13 | self.assertFalse(2 in ri)
14 | self.assertTrue(5 in ri)
15 | self.assertEqual('3~7', str(ri))
16 |
17 | ri = BRangeIndex(2)
18 | self.assertTrue(2 in ri)
19 | self.assertTrue(1000 in ri)
20 | self.assertEqual('≥2', str(ri))
21 |
22 | ri = BRangeIndex(upper=23)
23 | self.assertTrue(22 in ri)
24 | self.assertFalse(23 in ri)
25 | self.assertEqual('<23', str(ri))
26 |
27 |
28 | class BRangeCounterTestCase(unittest.TestCase):
29 | def test_value_indexes(self):
30 | rc = BSectionCounter(
31 | BValueIndex(1),
32 | BValueIndex(2),
33 | BValueIndex(3)
34 | )
35 | c = rc.feed([1, 2, 3, 1, 2, 3, 1, 2, 2, 3, 3, 3, 5, 6])
36 | self.assertEqual(3, c['1'])
37 | self.assertEqual(4, c['2'])
38 | self.assertEqual(5, c['3'])
39 |
40 | def test_basic_usage(self):
41 | rc = BSectionCounter(
42 | BRangeIndex(0, 50),
43 | BRangeIndex(50, 100),
44 | BRangeIndex(100)
45 | )
46 | c = rc.feed([0, 12, 23, 34, 50, 60, 67, 100]) # 4,3,1
47 | self.assertEqual(4, c['0~50'])
48 | self.assertEqual(3, c['50~100'])
49 | self.assertEqual(1, c['≥100'])
50 |
51 | def test_simple_counter(self):
52 | rc = BSectionCounter.from_simple([0, 50], [50, 100], [100, None])
53 | c = rc.feed([0, 12, 23, 34, 50, 60, 67, 100]) # 4,3,1
54 | self.assertEqual(4, c['0~50'])
55 | self.assertEqual(3, c['50~100'])
56 | self.assertEqual(1, c['≥100'])
57 |
58 | def test_feed_as_axis(self):
59 | rc = BSectionCounter.from_simple([0, 50], [50, 100], [100, None])
60 | a, b = rc.feed_as_axises([0, 12, 23, 34, 50, 60, 67, 100]) # 4,3,1
61 | self.assertTupleEqual(('0~50', '50~100', '≥100'), a)
62 | self.assertTupleEqual((4, 3, 1), b)
63 |
64 | def test_missing_index(self):
65 | rc = BSectionCounter(
66 | BRangeIndex(None, 0),
67 | BRangeIndex(0, 50),
68 | BRangeIndex(50, 100),
69 | BRangeIndex(100)
70 | )
71 | c = rc.feed([0, 12, 23, 34, 50, 60, 67, 100]) # 4,3,1
72 | self.assertEqual(0, c['<0'])
73 |
74 |
75 | class BSectionCounterTestCase(unittest.TestCase):
76 | def test_section_counter(self):
77 | rc = BSectionCounter(
78 | BValueIndex(0),
79 | BSectionIndex(1, 50),
80 | BSectionIndex(51, 99),
81 | BValueIndex(100)
82 | )
83 | test_data = [0, 12, 23, 34, 50, 60, 67, 99, 100]
84 | c = rc.feed(test_data)
85 | self.assertEqual(1, c['0'])
86 | self.assertEqual(4, c['1~50'])
87 | self.assertEqual(3, c['51~99'])
88 | self.assertEqual(1, c['100'])
89 |
90 |
91 | class ComprehensiveTestCase(unittest.TestCase):
92 | def test_counter_with_section_indexes(self):
93 | source_data = [random.choice(range(1500)) for _ in range(2000)]
94 |
95 | # Test Using len Counter
96 | sizes = [
97 | len([pp for pp in source_data if pp == 0]),
98 | len([pp for pp in source_data if 1 <= pp <= 10]),
99 | len([pp for pp in source_data if 11 <= pp <= 50]),
100 | len([pp for pp in source_data if 51 <= pp <= 100]),
101 | len([pp for pp in source_data if 101 <= pp <= 500]),
102 | len([pp for pp in source_data if 501 <= pp <= 1000]),
103 | len([pp for pp in source_data if pp >= 1001])
104 | ]
105 |
106 | # Test Using section counter
107 | rc1 = BSectionCounter(
108 | BValueIndex(0),
109 | BSectionIndex(1, 10),
110 | BSectionIndex(11, 50),
111 | BSectionIndex(51, 100),
112 | BSectionIndex(101, 500),
113 | BSectionIndex(501, 1000),
114 | BSectionIndex(1001)
115 | )
116 | ls, cs = rc1.feed_as_axises(source_data)
117 | self.assertSequenceEqual(sizes, cs)
118 |
--------------------------------------------------------------------------------
/tests/test_tms.py:
--------------------------------------------------------------------------------
1 | from django_echarts.core.tms import parse_theme_label, ThemeManager
2 |
3 | import unittest
4 |
5 |
6 | class ThemeLabelParseTestCase(unittest.TestCase):
7 |
8 | def test_parse_label(self):
9 | self.assertEqual(parse_theme_label('bootstrap5'), ('bootstrap5', 'bootstrap5', '', False))
10 | self.assertEqual(parse_theme_label('bootstrap5#local'), ('bootstrap5', 'bootstrap5', '', True))
11 | self.assertEqual(parse_theme_label('bootstrap5.foo#local'), ('bootstrap5.foo', 'bootstrap5', 'foo', True))
12 |
13 |
14 | class ThemeWithCustomUrlTestCase(unittest.TestCase):
15 | def test_custom_url(self):
16 | manager = ThemeManager.create_from_module('django_echarts.contrib.bootstrap3', d2u={
17 | 'bootstrap3': {'main_css': '/my/bootstrap3.min.css'},
18 | 'bootstrap3.foo': {'palette_css': '/my/bootstrap3.foo.min.css'}
19 | })
20 |
21 | theme1 = manager.create_theme('bootstrap3')
22 | css_urls = theme1.css_urls
23 | self.assertIn('/my/bootstrap3.min.css', css_urls)
24 |
25 | theme2 = manager.create_theme('bootstrap3.foo')
26 | css_urls2 = theme2.css_urls
27 | self.assertIn('/my/bootstrap3.foo.min.css', css_urls2)
28 |
29 | def test_incorrect_usage(self):
30 | manager = ThemeManager.create_from_module('django_echarts.contrib.bootstrap3', d2u={
31 | 'bootstrap3': {'palette_css': '/my/bootstrap3.min.css'},
32 | 'bootstrap3.foo': {'main_css': '/my/bootstrap3.foo.min.css'}
33 | })
34 |
35 | theme1 = manager.create_theme('bootstrap3')
36 | css_urls = theme1.css_urls
37 | self.assertNotIn('/my/bootstrap3.min.css', css_urls)
38 |
39 | theme2 = manager.create_theme('bootstrap3.foo')
40 | css_urls2 = theme2.css_urls
41 | self.assertNotIn('/my/bootstrap3.foo.min.css', css_urls2)
42 |
43 |
44 | class ThemeLocalTestCase(unittest.TestCase):
45 | def test_local_theme(self):
46 | pass
47 |
--------------------------------------------------------------------------------
/tests/test_uri.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from django_echarts.entities.uri import EntityURI, parse_params_choices
3 |
4 |
5 | class EntityURITestCase(unittest.TestCase):
6 | def test_parse(self):
7 | mya_uri = EntityURI.from_params_path('chart', 'demo', 'year/2021/tag/hello/django-echarts',
8 | param_names=['year', 'tag'])
9 | self.assertDictEqual({'year': '2021', 'tag': 'hello/django-echarts'}, mya_uri.params)
10 | mya_uri2 = EntityURI.from_params_path('chart', 'demo', 'year/2021/tag/hello/django-echarts/',
11 | param_names=['year', 'tag'])
12 | self.assertDictEqual({'year': '2021', 'tag': 'hello/django-echarts/'}, mya_uri2.params)
13 |
14 | def test_full_str(self):
15 | my_uri = EntityURI.from_str('chart:name1')
16 | self.assertEqual('chart', my_uri.catalog)
17 | self.assertEqual('name1', my_uri.name)
18 | self.assertEqual(0, len(my_uri.params))
19 |
20 | uri_with_params = EntityURI.from_str('chart:name1/year/2022')
21 | self.assertEqual('chart', uri_with_params.catalog)
22 | self.assertEqual('name1', uri_with_params.name)
23 | self.assertEqual('2022', uri_with_params.params['year'])
24 |
25 | def test_custom_catalog(self):
26 | uri_with_params = EntityURI.from_str('name1/year/2022', catalog='chart')
27 | self.assertEqual('chart', uri_with_params.catalog)
28 | self.assertEqual('name1', uri_with_params.name)
29 | self.assertEqual('2022', uri_with_params.params['year'])
30 |
31 |
32 | class ParamsConfigTestCase(unittest.TestCase):
33 | def test_parse_dict(self):
34 | result = list(parse_params_choices({'year': [2021, 2022], 'month': [1, 2]}))
35 | self.assertEqual(4, len(result))
36 |
--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from django_echarts.utils.burl import burl_kwargs, burl_args, BUrl
3 | from django_echarts.utils.lazy_dict import LazyDict, ParameterMissingError
4 |
5 |
6 | class BurlTestCase(unittest.TestCase):
7 | def test_burl(self):
8 | self.assertEqual(
9 | '/foo/?p=1&q=test',
10 | burl_args('/foo/', 'p', 1, 'q', 'test')
11 | )
12 | self.assertEqual('/foo/?p=1&q=test', burl_kwargs('/foo/', p=1, q='test'))
13 | self.assertEqual('/foo/?p=1&q=test', burl_kwargs('/foo/?p=1', q='test'))
14 | url = BUrl('/foo/')
15 | url.append('p', 1)
16 | url.append('z', 'zoo')
17 | url.delete('z')
18 | self.assertEqual('/foo/?p=1', url.url)
19 |
20 |
21 | class LazyDictTestCase(unittest.TestCase):
22 | def test_lazy_dict(self):
23 | l_dic = LazyDict()
24 | l_dic.register('value1', 'key1')
25 | l_dic.set_ref('ref1', 'key1')
26 |
27 | self.assertEqual(1, len(l_dic))
28 | self.assertTrue('key1' in l_dic)
29 | self.assertTrue('value1' == l_dic.get('key1') == l_dic.get('ref1'))
30 | self.assertTrue('key1' == l_dic.actual_key('key1') == l_dic.actual_key('ref1'))
31 |
32 | with self.assertRaises(TypeError):
33 | l_dic.register('error1')
34 |
35 | @l_dic.inject('key1')
36 | def demo(key1):
37 | return key1
38 |
39 | self.assertEqual('value1', demo())
40 |
41 | def test_func_with_parameters(self):
42 | l_dic = LazyDict()
43 |
44 | @l_dic.register
45 | def func1(year: int):
46 | return year
47 |
48 | @l_dic.register
49 | def func2(year: int, month: int = 2):
50 | pass
51 |
52 | @l_dic.register
53 | def func3(year: int, **kwargs):
54 | pass
55 |
56 | my_params = {'year': '2022'}
57 |
58 | self.assertDictEqual({'year': 2022}, l_dic.validate_caller_params('func1', my_params)[0])
59 | my_params2 = {'year': 2021}
60 | self.assertDictEqual({'year': 2021}, l_dic.validate_caller_params('func1', my_params2)[0])
61 | self.assertEqual(2021, l_dic.get('func1', {'year': 2021}))
62 |
63 | self.assertDictEqual({'year': 2021, 'month': 3},
64 | l_dic.validate_caller_params('func2', {'year': 2021, 'month': 3})[0])
65 | self.assertDictEqual({'year': 2021, 'month': 2}, l_dic.validate_caller_params('func2', {'year': 2021})[0])
66 | self.assertDictEqual({'year': 2021, 'month': 2},
67 | l_dic.validate_caller_params('func3', {'year': 2021, 'month': 2})[0])
68 | with self.assertRaises(ParameterMissingError):
69 | l_dic.validate_caller_params('func1', {'year': 2021, 'month': 2})
70 |
--------------------------------------------------------------------------------
/tests/test_widgets.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from django_echarts.entities import Nav, LinkItem
4 |
5 |
6 | class NavTestCase(unittest.TestCase):
7 | def test_nav(self):
8 | nav = Nav()
9 | nav.add_left_menu('Menu1', slug='menu1', url='/menu1')
10 | nav.add_item_in_left_menu('Menu1', LinkItem('sub1', '/sub/', after_separator=True))
11 |
12 | self.assertEqual(1, len(nav.menus))
13 | self.assertEqual('sub1', nav.menus[0].children[0].text)
14 | self.assertEqual(True, nav.menus[0].children[0].after_separator)
15 |
--------------------------------------------------------------------------------
/validate_release.py:
--------------------------------------------------------------------------------
1 | """Validate whl file by comparing with source files."""
2 | import os
3 | import zipfile
4 | import pathlib
5 | import re
6 | import sys
7 |
8 |
9 | def get_version():
10 | here = pathlib.Path(__file__).parent
11 | txt = (here / 'django_echarts' / '__init__.py').read_text()
12 | __version__ = re.findall(r"^__version__ = '([^']+)'\r?$", txt, re.M)[0]
13 | return __version__
14 |
15 |
16 | def source_file_list():
17 | filenames = []
18 | for cur_dir, dirs, files in os.walk("django_echarts"):
19 | for file in files:
20 | fs = os.path.join(cur_dir, file)
21 | if fs.endswith('.pyc'):
22 | continue
23 | filenames.append(fs.replace('\\', '/'))
24 | return filenames
25 |
26 |
27 | def zip_file_list(version):
28 | whl_file_path = pathlib.Path('dist') / f'django_echarts-{version}-py3-none-any.whl'
29 |
30 | with zipfile.ZipFile(whl_file_path) as archive:
31 | zip_filenames = [zinfo.filename for zinfo in archive.infolist()]
32 | return zip_filenames
33 |
34 |
35 | def validate(version):
36 | s_list = source_file_list()
37 | z_list = zip_file_list(version)
38 |
39 | missing_files = list(set(s_list) - set(z_list))
40 | if len(missing_files) == 0:
41 | print('Validate success!')
42 | else:
43 | print('Error!The following files are missing:')
44 | for name in missing_files:
45 | print(f'\t{name}')
46 |
47 |
48 | if __name__ == '__main__':
49 | if len(sys.argv) > 1:
50 | version = sys.argv[1]
51 | else:
52 | version = get_version()
53 | print(f'Start validating. Version={version}')
54 | validate(version)
55 |
--------------------------------------------------------------------------------