├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── workflow-fail-template.md
└── workflows
│ ├── release.yml
│ ├── test_plugin.yaml
│ └── transifex.yml
├── .gitignore
├── CHANGELOG.md
├── DataPlotly
├── __init__.py
├── core
│ ├── __init__.py
│ ├── core_utils.py
│ ├── plot_expressions.py
│ ├── plot_factory.py
│ ├── plot_settings.py
│ └── plot_types
│ │ ├── __init__.py
│ │ ├── bar_plot.py
│ │ ├── box.py
│ │ ├── contour.py
│ │ ├── histogram.py
│ │ ├── histogram2d.py
│ │ ├── icons
│ │ ├── 2dhistogram.svg
│ │ ├── barplot.svg
│ │ ├── boxplot.svg
│ │ ├── contour.svg
│ │ ├── histogram.svg
│ │ ├── pie.svg
│ │ ├── polar.svg
│ │ ├── scatterplot.svg
│ │ ├── scatterternary.svg
│ │ └── violin.svg
│ │ ├── pie.py
│ │ ├── plot_type.py
│ │ ├── polar.py
│ │ ├── scatter.py
│ │ ├── ternary.py
│ │ └── violin.py
├── data_plotly.py
├── gui
│ ├── __init__.py
│ ├── add_new_dock_dlg.py
│ ├── dock.py
│ ├── gui_utils.py
│ ├── layout_item_gui.py
│ ├── plot_settings_widget.py
│ └── remove_dock_dlg.py
├── icon.png
├── icons
│ ├── circle.svg
│ ├── clean.svg
│ ├── create_plot.svg
│ ├── cross.svg
│ ├── dash.png
│ ├── dataplotly.svg
│ ├── diamond.svg
│ ├── dot.png
│ ├── dotdash.png
│ ├── list_code.svg
│ ├── list_custom.svg
│ ├── list_help.svg
│ ├── list_plot.svg
│ ├── list_properties.svg
│ ├── longdash.png
│ ├── longdashdot.png
│ ├── mActionArrowDown.svg
│ ├── mActionArrowUp.svg
│ ├── mActionDuplicateLayer.svg
│ ├── mIconDataDefineExpression.svg
│ ├── penta.svg
│ ├── refresh.svg
│ ├── reload.svg
│ ├── save_as_html.svg
│ ├── save_as_image.svg
│ ├── solid.png
│ ├── square.svg
│ ├── star.svg
│ ├── symbologyAdd.svg
│ ├── symbologyRemove.svg
│ ├── triangle.svg
│ └── x.svg
├── jsscripts
│ ├── plotly-1.52.2.min.js
│ └── polyfill.min.js
├── layouts
│ └── plot_layout_item.py
├── metadata.txt
├── processing
│ ├── dataplotly_provider.py
│ └── dataplotly_scatterplot.py
├── test
│ ├── __init__.py
│ ├── processing_scatter.html
│ ├── qgis_interface.py
│ ├── scatterplot.json
│ ├── tenbytenraster.asc
│ ├── tenbytenraster.asc.aux.xml
│ ├── tenbytenraster.keywords
│ ├── tenbytenraster.lic
│ ├── tenbytenraster.prj
│ ├── tenbytenraster.qml
│ ├── test_data_plotly_dialog.py
│ ├── test_dock_manager.py
│ ├── test_guiutils.py
│ ├── test_init.py
│ ├── test_layer.cpg
│ ├── test_layer.dbf
│ ├── test_layer.geojson
│ ├── test_layer.prj
│ ├── test_layer.qpj
│ ├── test_layer.shp
│ ├── test_layer.shx
│ ├── test_plot_factory.py
│ ├── test_plot_settings.py
│ ├── test_processing.py
│ ├── test_project_with_state.qgs
│ ├── test_project_without_state.qgs
│ ├── test_qgis_environment.py
│ ├── test_resources.py
│ └── utilities.py
├── test_suite.py
└── ui
│ ├── add_dock_dlg.ui
│ ├── dataplotly_dockwidget_base.ui
│ └── remove_dock_dlg.ui
├── LICENSE
├── Makefile
├── README.md
├── REQUIREMENTS_TESTING.txt
├── img
├── plot_2dhistogram.png
├── plot_bar_stack.png
├── plot_box.png
├── plot_contour.png
├── plot_histogram.png
├── plot_histogram_violin.png
├── plot_interaction_box.gif
├── plot_interaction_scatter.gif
├── plot_interaction_scatter_box.gif
├── plot_pie.png
├── plot_polar.png
├── plot_scatter.png
├── plot_scatter_bar.png
├── plot_scatter_histogram.png
├── plot_ternary.png
└── plot_violin.png
├── publiccode.yml
├── pylintrc
├── scripts
└── run-env-linux.sh
└── setup.cfg
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is. If you hit some error please paste the entire Python stacktrace message.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 |
21 | **Screenshots**
22 | If applicable, add screenshots to help explain your problem.
23 |
24 | **Desktop (please complete the following information):**
25 | - OS: [e.g. iOS]
26 | - QGIS release [e.g. QGIS 3.10]
27 | - DataPlotly release [e.g. 3.0]
28 |
29 | **Additional context**
30 | Add any other context about the problem here.
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots and URL about the feature request here.
18 |
--------------------------------------------------------------------------------
/.github/workflow-fail-template.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Workflow {{ env.WORKFLOW }} failed for {{ env.DOCKER_TAG }}
3 | labels: bug
4 | ---
5 |
6 | Workflow {{ env.WORKFLOW }} failed for {{ env.DOCKER_TAG }} at: {{ date | date('YYYY-MM-DD HH:mm:ss') }}
7 |
8 | {{ env.TEST_RESULT }}
9 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release 🚀
2 |
3 | on:
4 | push:
5 | tags:
6 | - "*"
7 |
8 | jobs:
9 | release:
10 |
11 | runs-on: ubuntu-latest
12 | if: github.repository_owner == 'ghtmtt' && contains(github.ref, 'refs/tags/')
13 |
14 | steps:
15 | - name: Set env
16 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
17 |
18 | - uses: actions/checkout@v2
19 |
20 | - name: Set up Python 3.10
21 | uses: actions/setup-python@v3
22 | with:
23 | python-version: "3.10"
24 |
25 | - name: Install Qt lrelease
26 | run: |
27 | sudo apt-get update
28 | sudo apt-get install qtbase5-dev qt5-qmake qttools5-dev-tools
29 |
30 | - name: Install Python requirements
31 | run: pip install qgis-plugin-ci
32 |
33 | - name : Get current changelog
34 | run: qgis-plugin-ci changelog ${{ env.RELEASE_VERSION }} >> release.md
35 |
36 | - name: Create release on GitHub
37 | uses: ncipollo/release-action@v1.10.0
38 | with:
39 | bodyFile: release.md
40 | token: ${{ secrets.GITHUB_TOKEN }}
41 |
42 | - name: Package and deploy the zip
43 | run: >-
44 | qgis-plugin-ci
45 | release ${{ env.RELEASE_VERSION }}
46 | --github-token ${{ secrets.GITHUB_TOKEN }}
47 | --transifex-token ${{ secrets.TRANSIFEX_TOKEN }}
48 | --osgeo-username ${{ secrets.OSGEO_USERNAME }}
49 | --osgeo-password ${{ secrets.OSGEO_PASSWORD }}
50 | --create-plugin-repo
51 |
--------------------------------------------------------------------------------
/.github/workflows/test_plugin.yaml:
--------------------------------------------------------------------------------
1 | name: Test plugin
2 |
3 | on:
4 | push:
5 | paths:
6 | - "DataPlotly/**"
7 | - ".github/workflows/test_plugin.yaml"
8 | pull_request:
9 | paths:
10 | - "DataPlotly/**"
11 | - ".github/workflows/test_plugin.yaml"
12 |
13 | env:
14 | # plugin name/directory where the code for the plugin is stored
15 | PLUGIN_NAME: DataPlotly
16 | # python notation to test running inside plugin
17 | TESTS_RUN_FUNCTION: DataPlotly.test_suite.test_package
18 | # Docker settings
19 | DOCKER_IMAGE: qgis/qgis
20 |
21 |
22 | jobs:
23 |
24 | Test-plugin-DataPlotly:
25 |
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | matrix:
30 | docker_tags: [release-3_28, latest]
31 |
32 | steps:
33 |
34 | - name: Checkout
35 | uses: actions/checkout@v2
36 |
37 | - name: Docker pull and create qgis-testing-environment
38 | run: |
39 | docker pull "$DOCKER_IMAGE":${{ matrix.docker_tags }}
40 | docker run -d --name qgis-testing-environment -v "$GITHUB_WORKSPACE":/tests_directory -e DISPLAY=:99 "$DOCKER_IMAGE":${{ matrix.docker_tags }}
41 |
42 | - name: Docker set up QGIS
43 | run: |
44 | docker exec qgis-testing-environment sh -c "qgis_setup.sh $PLUGIN_NAME"
45 | docker exec qgis-testing-environment sh -c "rm -f /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/$PLUGIN_NAME"
46 | docker exec qgis-testing-environment sh -c "ln -s /tests_directory/$PLUGIN_NAME /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/$PLUGIN_NAME"
47 | docker exec qgis-testing-environment sh -c "pip3 install -r /tests_directory/REQUIREMENTS_TESTING.txt"
48 | docker exec qgis-testing-environment sh -c "apt-get update"
49 | docker exec qgis-testing-environment sh -c "apt-get install -y python3-pyqt5.qtwebkit"
50 |
51 | - name: Docker run plugin tests
52 | run: |
53 | docker exec qgis-testing-environment sh -c "qgis_testrunner.sh $TESTS_RUN_FUNCTION"
54 |
55 | Check-code-quality:
56 | runs-on: ubuntu-latest
57 | steps:
58 |
59 | - name: Install Python
60 | uses: actions/setup-python@v1
61 | with:
62 | python-version: '3.8'
63 | architecture: 'x64'
64 |
65 | - name: Checkout
66 | uses: actions/checkout@v2
67 |
68 | - name: Install packages
69 | run: |
70 | pip install -r REQUIREMENTS_TESTING.txt
71 | pip install pylint pycodestyle
72 |
73 | - name: Pylint
74 | run: make pylint
75 |
76 | - name: Pycodestyle
77 | run: make pycodestyle
78 |
--------------------------------------------------------------------------------
/.github/workflows/transifex.yml:
--------------------------------------------------------------------------------
1 | name: Transifex 🗺
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths:
8 | - '**.py'
9 | - '**.ui'
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | if: github.repository_owner == 'ghtmtt'
15 | steps:
16 | - uses: actions/checkout@v2
17 |
18 | - name: Set up Python 3.10
19 | uses: actions/setup-python@v3
20 | with:
21 | python-version: "3.10"
22 |
23 | - name: Install Python requirements
24 | run: pip install qgis-plugin-ci
25 |
26 | - name: Push to Transifex
27 | run: qgis-plugin-ci push-translation ${{ secrets.TRANSIFEX_TOKEN }}
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | *.pyc
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
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 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 | cover/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | .pybuilder/
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | # For a library or package, you might want to ignore these files since the code is
88 | # intended to run in multiple environments; otherwise, check them in:
89 | # .python-version
90 |
91 | # pipenv
92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
95 | # install all needed dependencies.
96 | #Pipfile.lock
97 |
98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
99 | __pypackages__/
100 |
101 | # Celery stuff
102 | celerybeat-schedule
103 | celerybeat.pid
104 |
105 | # SageMath parsed files
106 | *.sage.py
107 |
108 | # Environments
109 | .env
110 | .venv
111 | env/
112 | venv/
113 | ENV/
114 | env.bak/
115 | venv.bak/
116 |
117 | # Spyder project settings
118 | .spyderproject
119 | .spyproject
120 |
121 | # Rope project settings
122 | .ropeproject
123 |
124 | # mkdocs documentation
125 | /site
126 |
127 | # mypy
128 | .mypy_cache/
129 | .dmypy.json
130 | dmypy.json
131 |
132 | # Pyre type checker
133 | .pyre/
134 |
135 | # pytype static type analyzer
136 | .pytype/
137 |
138 | # Cython debug symbols
139 | cython_debug/
140 |
141 | # Vscode
142 | .vscode/
143 |
144 | # Windows thumbs
145 | Thumbs.db
146 |
147 | # zip files
148 | *.zip
149 |
150 | # IDE
151 | .idea/*
152 |
153 | # Vscode
154 | .vscode/*
155 |
156 | # Test
157 | *.qgs~
158 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Unreleased
4 |
5 | ## 4.2.0 - 2024-10-24
6 |
7 | - Fix loading of the plugin when used with `qgis_process`, contribution from @Gustry
8 | - Fix incomplete plots when used with atlas, thanks to @nyalldawson
9 | - Fix DataType in pie charts, contribution to @jdugge
10 |
11 | ## 4.1.0 - 2023-11-15
12 |
13 | - Fix loading of the plugin when the Pandas library is not found, contribution from @Gustry
14 |
15 | ## 4.0.3 - 2023-06-27
16 |
17 | - fix bug with old projects (second part) thanks to @jdlom
18 |
19 | ## 4.0.2 - 2023-06-24
20 |
21 | - fix bug with old projects thanks to @jdlom
22 |
23 | ## 4.0.1 - 2023-05-12
24 |
25 | - tweak the bar width values, useful with time series
26 | - minimum QGIS version to 3.28 LTR
27 |
28 | ## 4.0.0 - 2023-04-17
29 |
30 | - multiple plot docks thanks to @jdlom
31 | - add a pie hole parameter to have donuts like charts thanks to @jdlom
32 |
33 | ## 3.9.2 - 2022-08-30
34 |
35 | - test added for Processing
36 | - Processing algorithm refactored using plotly.express (scatter plot)
37 | - fix stacktrace when removing all the plots from the layout
38 | - fix signal not updating the fields in the data defined buttons automatically
39 |
40 | ## 3.9.1 - 2022-08-20
41 |
42 | - Add custom function to have plot color the same as map color
43 | - Fix #237 add data-driven color to Polar Plot marker color. Kudos to @jmonticolo
44 | - Fix "Build a generic plot" processing algorithm. Kudos to @agiudiceandrea
45 |
46 | ## 3.9.0 - 2022-04-11
47 |
48 | - Customize font for plot title and plot axis. Kudos to @giliam
49 | - Support for Python 3.10
50 |
51 | ## 3.8.1 - 2021-09-28
52 |
53 | - bugfix
54 |
55 | ## 3.8.0 - 2021-09-28
56 |
57 | - [feature] expose DataPlotly on QGIS Server for a GetPrint request kudos to @Gustry
58 |
59 | ## 3.7.1 - 2020-05-15
60 |
61 | - bugfix
62 |
63 | ## 3.7.0
64 |
65 | - [feature] histogram and pie chart bar and slices with same color of category! kudos to @jdugge
66 | - [feature] plot background transparent in layouts!
67 |
68 | ## 3.6.0
69 |
70 | - [feature] Multi Plot in layout composer! Ultra kudos to @SGroe
71 | - [bugfix] Fix layout composer issue with many plots (ref #207). Thanks to the Italian Community for testing
72 | - [bugfix] Fix categorical bar plot wrong behavior
73 | - [bugfix] code cleaning
74 |
75 | ## 3.5.0
76 |
77 | - [bugfix] Fix loading old projects
78 |
79 | ## 3.4.0
80 |
81 | - [feature] get labels within the plot itself
82 | - [bugfix] Native datetime support! thanks @jdugge
83 | - [bugfix] Fix histogram selection
84 |
85 | ## 3.3.0
86 |
87 | - [bugfix] better loading project part 2
88 |
89 | ## 3.2.0
90 |
91 | - [bugfix] fix violin plot bug
92 | - [bugfix] better loading project handling
93 |
94 | ## 3.1.0
95 |
96 | - [feature] more data defined options available (in layout customization). Thanks @SGroe
97 | - [feature] X and Y axis bounds limits. Thanks @SGroe
98 | - [feature] add box plot within violin plots
99 | - [feature] renaming of plugin metadata to better search. Thanks @Gustry
100 | - [bugfix] Box plot not working when no group is selected
101 | - [bugfix] Data-defined property overrides do not work in layout
102 |
103 | ## 3.0.0
104 |
105 | - [feature] total refactoring of the code
106 | - [feature] plots also in print composer
107 | - [feature] atlas based plots
108 | - [feature] chance to save/load configuration file of plot setting
109 | - [feature] plot settings saved together with the project
110 | - [feature] more datadefined properties
111 | - [feature] show only selected/visible/filtered features
112 | - [feature] unit tests and continuous integration
113 |
114 | ## 2.3.0
115 |
116 | - [feature] tweaks polar plots, thanks @josephholler
117 |
118 | ## 2.2.0
119 |
120 | - [feature] UI tweaks, thanks @nyalldawson
121 |
122 | ## 2.1.0
123 |
124 | - [fix] typos in UI (thanks @leonmvd and @nyalldawson)
125 | - [fix] better python packages imports (thanks @nyalldawson)
126 |
127 | ## 2.0.0
128 |
129 | - [feature] DataPlotly is updated with plotly 3.3 version
130 |
131 | ## 1.6.0
132 |
133 | - [feature] wheel zoom! Give it a try
134 | - [feature] Edit plot title and X/Y labels in place
135 |
136 | ## 1.5.1
137 |
138 | - [feature] Spanish translation. Special thanks to Luca Bellani
139 | - [bugfix] always open English manual if locale not translated
140 |
141 | ## 1.5.0
142 |
143 | - [feature] **new** Violin plots!
144 | - [feature] **new** Polar plot layout!
145 | - [feature] better default color choice
146 |
147 | ## 1.4.3
148 |
149 | - [bugfix] correct interaction with pie plot
150 | - update plotly.js to v 1.34.0
151 |
152 | ## 1.4.2
153 |
154 | - [bugfix] correct saving html plot
155 |
156 | ## 1.4.1
157 |
158 | - [bugfixing] adaptation for new API
159 |
160 | ## 1.4.0
161 |
162 | - [feature] update plotly.js to v 1.33.1
163 | - [feature] multiple selection with Shift + selection tool
164 | - [feature] DataPlotly as Processing provider, thanks to Michaël Douchin of 3Liz
165 |
--------------------------------------------------------------------------------
/DataPlotly/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | /***************************************************************************
3 | DataPlotly
4 | A QGIS plugin
5 | D3 Plots for QGIS
6 | -------------------
7 | begin : 2017-03-05
8 | copyright : (C) 2017 by matteo ghetta
9 | email : matteo.ghetta@gmail.com
10 | git sha : $Format:%H$
11 | ***************************************************************************/
12 |
13 | /***************************************************************************
14 | * *
15 | * This program is free software; you can redistribute it and/or modify *
16 | * it under the terms of the GNU General Public License as published by *
17 | * the Free Software Foundation; either version 2 of the License, or *
18 | * (at your option) any later version. *
19 | * *
20 | ***************************************************************************/
21 | This script initializes the plugin, making it known to QGIS.
22 | """
23 |
24 |
25 | # noinspection PyPep8Naming
26 | def classFactory(iface): # pylint: disable=invalid-name
27 | """Load DataPlotly class from file DataPlotly.
28 |
29 | :param iface: A QGIS interface instance.
30 | :type iface: QgsInterface
31 | """
32 | # pylint: disable=import-outside-toplevel
33 | from .data_plotly import DataPlotly
34 | return DataPlotly(iface)
35 |
36 |
37 | def serverClassFactory(server_iface):
38 | """Load DataPlotly server.
39 |
40 | :param server_iface: A QGIS Server interface instance.
41 | :type server_iface: QgsServerInterface
42 | """
43 | _ = server_iface
44 | # pylint: disable=import-outside-toplevel
45 | from qgis.core import QgsApplication, QgsMessageLog, Qgis
46 | from DataPlotly.layouts.plot_layout_item import PlotLayoutItemMetadata
47 |
48 | QgsApplication.layoutItemRegistry().addLayoutItemType(PlotLayoutItemMetadata())
49 | QgsMessageLog.logMessage("Custom DataPlotly layout item loaded", "DataPlotly", Qgis.Info)
50 |
--------------------------------------------------------------------------------
/DataPlotly/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/core/__init__.py
--------------------------------------------------------------------------------
/DataPlotly/core/core_utils.py:
--------------------------------------------------------------------------------
1 | """Core Utilities
2 |
3 | .. note:: This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 2 of the License, or
6 | (at your option) any later version.
7 | """
8 | import uuid
9 | from qgis.PyQt.QtCore import QByteArray
10 |
11 | DOC_URL = "https://dataplotly-docs.readthedocs.io"
12 |
13 | def safe_str_xml(s):
14 | """ replaces spaces by .
15 | """
16 | return s.replace(" ", ".")
17 |
18 |
19 | def restore_safe_str_xml(s):
20 | """ replaces . by spaces
21 | """
22 | return s.replace(".", " ")
23 |
24 |
25 | def restore(b_str_64):
26 | """state and geom are stored in str(Base64) in project xml file"""
27 | return QByteArray.fromBase64(QByteArray(b_str_64.encode()))
28 |
29 |
30 | def uuid_suffix(string: str) -> str:
31 | """ uuid4 suffix"""
32 | return f"{string}{uuid.uuid4()}"
33 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_expressions.py:
--------------------------------------------------------------------------------
1 | """
2 | /***************************************************************************
3 | DataPlotly
4 | A QGIS plugin
5 | D3 Plots for QGIS
6 | -------------------
7 | begin : 2022-06-08
8 | git sha : $Format:%H$
9 | copyright : (C) 2020 by matteo ghetta
10 | email : matteo.ghetta@faunalia.it
11 | ***************************************************************************/
12 |
13 | /***************************************************************************
14 | * *
15 | * This program is free software; you can redistribute it and/or modify *
16 | * it under the terms of the GNU General Public License as published by *
17 | * the Free Software Foundation; either version 2 of the License, or *
18 | * (at your option) any later version. *
19 | * *
20 | ***************************************************************************/
21 | """
22 |
23 | from qgis.utils import qgsfunction
24 | from qgis.core import QgsRenderContext
25 |
26 |
27 | @qgsfunction(args='auto', group='DataPlotly')
28 | def get_symbol_colors(feature, parent, context):
29 | """
30 | Retrieve the color of each category as html code. You can use this function
31 | to set the plot items (pie slices, bars, points, etc) to the same color
32 | of the feature visible in the map.
33 |
Syntax
34 |
35 | get_symbol_colors() -> '#da1ddd'
36 |
37 | """
38 | _ = parent
39 | layer = context.variable('layer')
40 | renderer_context = QgsRenderContext()
41 | renderer = layer.renderer()
42 | renderer.startRender(renderer_context, layer.fields())
43 |
44 | symbols = renderer.originalSymbolsForFeature(feature, renderer_context)
45 |
46 | if symbols:
47 | color = symbols[0].color().name()
48 | else:
49 | color = '#000000'
50 |
51 | renderer.stopRender(renderer_context)
52 |
53 | return color
54 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Available plot types
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | from .bar_plot import BarPlotFactory
11 | from .box import BoxPlotFactory
12 | from .contour import ContourFactory
13 | from .histogram2d import Histogram2dFactory
14 | from .histogram import HistogramFactory
15 | from .pie import PieChartFactory
16 | from .polar import PolarChartFactory
17 | from .scatter import ScatterPlotFactory
18 | from .ternary import TernaryFactory
19 | from .violin import ViolinFactory
20 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/bar_plot.py:
--------------------------------------------------------------------------------
1 | """
2 | Bar plot
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class BarPlotFactory(PlotType):
17 | """
18 | Factory for bar plots
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return 'bar'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('Bar Plot')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/barplot.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 | # flip the variables according to the box orientation
36 |
37 | if settings.properties['box_orientation'] == 'h':
38 | y = settings.x
39 | x = settings.y
40 | else:
41 | x = settings.x
42 | y = settings.y
43 |
44 | # tweak the width value
45 | if settings.data_defined_marker_sizes:
46 | width = settings.data_defined_marker_sizes
47 | # set to None if 0.0 or Auto, useful especially for date/datetime data
48 | elif settings.properties['marker_size'] == 0.0:
49 | width = None
50 | else:
51 | width = settings.properties['marker_size']
52 |
53 | return [graph_objs.Bar(
54 | x=x,
55 | y=y,
56 | text=settings.additional_hover_text,
57 | textposition=settings.properties.get('hover_label_position'),
58 | name=settings.data_defined_legend_title if settings.data_defined_legend_title != '' else settings.properties['name'],
59 | ids=settings.feature_ids,
60 | customdata=settings.properties['custom'],
61 | orientation=settings.properties['box_orientation'],
62 | marker={'color': settings.data_defined_colors if settings.data_defined_colors else settings.properties['in_color'],
63 | 'colorscale': settings.properties['color_scale'],
64 | 'showscale': settings.properties['show_colorscale_legend'],
65 | 'reversescale': settings.properties['invert_color_scale'],
66 | 'colorbar': {
67 | 'len': 0.8
68 | },
69 | 'line': {
70 | 'color': settings.data_defined_stroke_colors if settings.data_defined_stroke_colors else settings.properties['out_color'],
71 | 'width': settings.data_defined_stroke_widths if settings.data_defined_stroke_widths else settings.properties['marker_width']}
72 | },
73 | width=width,
74 | opacity=settings.properties['opacity']
75 | )]
76 |
77 | @staticmethod
78 | def create_layout(settings):
79 | layout = super(BarPlotFactory, BarPlotFactory).create_layout(settings)
80 |
81 | layout['barmode'] = settings.layout['bar_mode']
82 |
83 | return layout
84 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/box.py:
--------------------------------------------------------------------------------
1 | """
2 | Box plot
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class BoxPlotFactory(PlotType):
17 | """
18 | Factory for box plots
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return 'box'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('Box Plot')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/boxplot.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 | # flip the variables according to the box orientation
36 |
37 | if settings.properties['box_orientation'] == 'h':
38 | y = settings.x
39 | x = settings.y
40 | else:
41 | x = settings.x
42 | y = settings.y
43 |
44 | return [graph_objs.Box(
45 | x=x or None,
46 | y=y,
47 | name=settings.data_defined_legend_title if settings.data_defined_legend_title != '' else settings.properties['name'],
48 | customdata=settings.properties['custom'],
49 | boxmean=settings.properties['box_stat'],
50 | orientation=settings.properties['box_orientation'],
51 | boxpoints=settings.properties['box_outliers'],
52 | fillcolor=settings.data_defined_colors[0] if settings.data_defined_colors else settings.properties['in_color'],
53 | line={'color': settings.data_defined_stroke_colors[0] if settings.data_defined_stroke_colors else settings.properties['out_color'],
54 | 'width': settings.data_defined_stroke_widths[0] if settings.data_defined_stroke_widths else settings.properties['marker_width']},
55 | opacity=settings.properties['opacity']
56 | )]
57 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/contour.py:
--------------------------------------------------------------------------------
1 | """
2 | Contour chart factory
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class ContourFactory(PlotType):
17 | """
18 | Factory for contour charts
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return 'contour'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('Contour Plot')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/contour.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 | return [graph_objs.Contour(
36 | z=[settings.x, settings.y],
37 | contours={
38 | 'coloring': settings.properties['cont_type'],
39 | 'showlines': settings.properties['show_lines']
40 | },
41 | colorscale=settings.properties['color_scale'],
42 | opacity=settings.properties['opacity']
43 | )]
44 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/histogram.py:
--------------------------------------------------------------------------------
1 | """
2 | Histogram factory
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class HistogramFactory(PlotType):
17 | """
18 | Factory for histogram plots
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return 'histogram'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('Histogram')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/histogram.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 | return [
36 | graph_objs.Histogram(
37 | x=settings.x,
38 | y=settings.x,
39 | name=settings.data_defined_legend_title if settings.data_defined_legend_title != '' else settings.properties['name'],
40 | orientation=settings.properties['box_orientation'],
41 | nbinsx=settings.properties['bins'],
42 | nbinsy=settings.properties['bins'],
43 | marker={
44 | 'color': settings.data_defined_colors if settings.data_defined_colors else settings.properties['in_color'],
45 | 'line': {
46 | 'color': settings.data_defined_stroke_colors if settings.data_defined_stroke_colors else settings.properties['out_color'],
47 | 'width': settings.data_defined_stroke_widths if settings.data_defined_stroke_widths else settings.properties['marker_width']
48 | }
49 | },
50 | histnorm=settings.properties['normalization'],
51 | opacity=settings.properties['opacity'],
52 | cumulative={
53 | 'enabled': settings.properties['cumulative'],
54 | 'direction': settings.properties['invert_hist']
55 | }
56 | )
57 | ]
58 |
59 | @staticmethod
60 | def create_layout(settings):
61 | layout = super(HistogramFactory, HistogramFactory).create_layout(settings)
62 |
63 | layout['barmode'] = settings.layout['bar_mode']
64 | layout['bargroupgap'] = settings.layout['bargaps']
65 |
66 | return layout
67 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/histogram2d.py:
--------------------------------------------------------------------------------
1 | """
2 | 2D histogram factory
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class Histogram2dFactory(PlotType):
17 | """
18 | Factory for 2D histograms
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return '2dhistogram'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('2D Histogram')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/2dhistogram.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 | return [graph_objs.Histogram2d(
36 | x=settings.x,
37 | y=settings.y,
38 | colorscale=settings.properties['color_scale']
39 | )]
40 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/pie.py:
--------------------------------------------------------------------------------
1 | """
2 | Pie chart factory
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class PieChartFactory(PlotType):
17 | """
18 | Factory for pie charts
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return 'pie'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('Pie Chart')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/pie.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 | return [graph_objs.Pie(
36 | labels=settings.x,
37 | values=settings.y,
38 | marker={
39 | 'colors': settings.data_defined_colors if settings.data_defined_colors else [settings.properties['in_color']]
40 | },
41 | name=settings.properties['custom'][0],
42 | hole=settings.properties.get('pie_hole', 0)
43 | )]
44 |
45 | @staticmethod
46 | def create_layout(settings):
47 | layout = super(PieChartFactory, PieChartFactory).create_layout(settings)
48 |
49 | layout['xaxis'].update(title='')
50 | layout['xaxis'].update(showgrid=False)
51 | layout['xaxis'].update(zeroline=False)
52 | layout['xaxis'].update(showline=False)
53 | layout['xaxis'].update(showticklabels=False)
54 | layout['yaxis'].update(title='')
55 | layout['yaxis'].update(showgrid=False)
56 | layout['yaxis'].update(zeroline=False)
57 | layout['yaxis'].update(showline=False)
58 | layout['yaxis'].update(showticklabels=False)
59 |
60 | return layout
61 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/plot_type.py:
--------------------------------------------------------------------------------
1 | """
2 | Base class for plot type subclasses
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | from plotly import graph_objs
11 | from qgis.PyQt.QtCore import QCoreApplication
12 |
13 |
14 | def from_qfont_to_plotly(style, color):
15 | """
16 | Converts a QFont to a Plotly basic font settings dictionary
17 | """
18 | if style is not None and color is not None:
19 | return {
20 | "size": style.pointSize() or 10,
21 | "color": color.name() or "black",
22 | "family": style.family() or "Arial",
23 | }
24 | return {
25 | "size": 10,
26 | "color": "black",
27 | "family": "Arial",
28 | }
29 |
30 |
31 | class PlotType:
32 | """
33 | Base class for plot subclasses
34 | """
35 |
36 | @staticmethod
37 | def type_name():
38 | """
39 | Returns the unique type name for this plot type
40 | """
41 | return ''
42 |
43 | @staticmethod
44 | def name():
45 | """
46 | Returns the friendly translated name for the plot type
47 | """
48 | return ''
49 |
50 | @staticmethod
51 | def icon():
52 | """
53 | Returns a path to an icon for this chart type
54 | """
55 | return ''
56 |
57 | @staticmethod
58 | def create_trace(settings): # pylint: disable=W0613
59 | """
60 | Returns a new trace using the specified plot settings
61 | """
62 | return None
63 |
64 | @staticmethod
65 | def create_layout(settings):
66 | """
67 | Returns a new layout using the specified plot settings
68 | """
69 |
70 | # flip the variables according to the box orientation
71 | if settings.properties['box_orientation'] == 'h':
72 | y_title = settings.data_defined_y_title if settings.data_defined_y_title != ''\
73 | else settings.layout['y_title']
74 | x_title = settings.data_defined_x_title if settings.data_defined_x_title != ''\
75 | else settings.layout['x_title']
76 | else:
77 | x_title = settings.data_defined_x_title if settings.data_defined_x_title != ''\
78 | else settings.layout['x_title']
79 | y_title = settings.data_defined_y_title if settings.data_defined_y_title != ''\
80 | else settings.layout['y_title']
81 |
82 | range_x = None
83 | if settings.layout.get('x_min', None) is not None and settings.layout.get('x_max', None) is not None:
84 | range_x = [
85 | settings.data_defined_x_min if settings.data_defined_x_min else settings.layout['x_min'],
86 | settings.data_defined_x_max if settings.data_defined_x_max else settings.layout['x_max']
87 | ]
88 | range_y = None
89 | if settings.layout.get('y_min', None) is not None and settings.layout.get('y_max', None) is not None:
90 | range_y = [
91 | settings.data_defined_y_min if settings.data_defined_y_min else settings.layout['y_min'],
92 | settings.data_defined_y_max if settings.data_defined_y_max else settings.layout['y_max']
93 | ]
94 |
95 | bg_color = settings.layout.get('bg_color', 'rgb(255,255,255)')
96 |
97 | # add font size parameter from the title setting
98 | title = settings.data_defined_title if settings.data_defined_title else settings.layout['title']
99 | if isinstance(title, str):
100 | title = {"text": title}
101 | title["font"] = {
102 | "size": settings.layout.get('font_title_size', 10),
103 | "color": settings.layout.get('font_title_color', "#000"),
104 | "family": settings.layout.get('font_title_family', "Arial"),
105 | }
106 |
107 | layout = graph_objs.Layout(
108 | showlegend=settings.layout['legend'],
109 | legend={'orientation': settings.layout['legend_orientation']},
110 | title=title,
111 | xaxis={
112 | 'title': x_title,
113 | 'titlefont': {
114 | "size": settings.layout.get('font_xlabel_size', 10),
115 | "color": settings.layout.get('font_xlabel_color', "#000"),
116 | "family": settings.layout.get('font_xlabel_family', "Arial"),
117 | },
118 | 'autorange': settings.layout['x_inv'],
119 | 'range': range_x,
120 | 'tickfont': {
121 | "size": settings.layout.get('font_xticks_size', 10),
122 | "color": settings.layout.get('font_xticks_color', "#000"),
123 | "family": settings.layout.get('font_xticks_family', "Arial"),
124 | },
125 | 'gridcolor': settings.layout.get('gridcolor', '#bdbfc0')
126 | },
127 | yaxis={
128 | 'title': y_title,
129 | 'titlefont': {
130 | "size": settings.layout.get('font_ylabel_size', 10),
131 | "color": settings.layout.get('font_ylabel_color', "#000"),
132 | "family": settings.layout.get('font_ylabel_family', "Arial"),
133 | },
134 | 'autorange': settings.layout['y_inv'],
135 | 'range': range_y,
136 | 'tickfont': {
137 | "size": settings.layout.get('font_yticks_size', 10),
138 | "color": settings.layout.get('font_yticks_color', "#000"),
139 | "family": settings.layout.get('font_yticks_family', "Arial"),
140 | },
141 | 'gridcolor': settings.layout.get('gridcolor', '#bdbfc0')
142 | },
143 | paper_bgcolor=bg_color,
144 | plot_bgcolor=bg_color
145 | )
146 |
147 | # update the x and y axis and add the linear and log only if the data are numeric
148 | # pass if field is empty
149 | try:
150 | if isinstance(settings.x[0], (int, float)):
151 | layout['xaxis'].update(type=settings.layout['x_type'])
152 | except: # pylint:disable=bare-except # noqa: F401
153 | pass
154 | try:
155 | if isinstance(settings.y[0], (int, float)):
156 | layout['yaxis'].update(type=settings.layout['y_type'])
157 | except: # pylint:disable=bare-except # noqa: F401
158 | pass
159 |
160 | return layout
161 |
162 | @staticmethod
163 | def tr(string, context=''):
164 | """
165 | Translates a string
166 | """
167 | if context == '':
168 | context = 'Types'
169 | return QCoreApplication.translate(context, string)
170 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/polar.py:
--------------------------------------------------------------------------------
1 | """
2 | Polar chart factory
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class PolarChartFactory(PlotType):
17 | """
18 | Factory for polar charts
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return 'polar'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('Polar Plot')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/polar.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 | return [graph_objs.Scatterpolar(
36 | r=settings.y,
37 | theta=settings.x,
38 | mode=settings.properties['marker'],
39 | name=settings.properties['y_name'],
40 | marker={
41 | "color": settings.data_defined_colors if settings.data_defined_colors else settings.properties['in_color'],
42 | "size": settings.data_defined_marker_sizes if settings.data_defined_marker_sizes else settings.properties['marker_size'],
43 | "symbol": settings.properties['marker_symbol'],
44 | "line": {
45 | "color": settings.properties['out_color'],
46 | "width": settings.properties['marker_width']
47 | }
48 | },
49 | line={
50 | "color": settings.properties['in_color'],
51 | "width": settings.data_defined_stroke_widths if settings.data_defined_stroke_widths else settings.properties['marker_width'],
52 | "dash": settings.properties['line_dash']
53 | },
54 | opacity=settings.properties['opacity'],
55 | )]
56 |
57 | @staticmethod
58 | def create_layout(settings):
59 | layout = super(PolarChartFactory, PolarChartFactory).create_layout(settings)
60 |
61 | layout['polar'] = settings.layout['polar']
62 |
63 | return layout
64 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/scatter.py:
--------------------------------------------------------------------------------
1 | """
2 | Base class for trace factories
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class ScatterPlotFactory(PlotType):
17 | """
18 | Factory for scatter plots
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return 'scatter'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('Scatter Plot')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/scatterplot.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 |
36 | if settings.properties.get('hover_label_text') is not None:
37 | mode = settings.properties.get('marker') + \
38 | settings.properties.get('hover_label_text')
39 | else:
40 | mode = settings.properties.get('marker')
41 |
42 | return [graph_objs.Scatter(
43 | x=settings.x,
44 | y=settings.y,
45 | mode=mode,
46 | textposition="top center",
47 | name=settings.data_defined_legend_title if settings.data_defined_legend_title != '' else settings.properties['name'],
48 | ids=settings.feature_ids,
49 | customdata=settings.properties['custom'],
50 | text=settings.additional_hover_text,
51 | hoverinfo=settings.properties['hover_text'],
52 | marker={'color': settings.data_defined_colors if settings.data_defined_colors else settings.properties['in_color'],
53 | 'colorscale': settings.properties['color_scale'],
54 | 'showscale': settings.properties['show_colorscale_legend'],
55 | 'reversescale': settings.properties['invert_color_scale'],
56 | 'colorbar': {
57 | 'len': 0.8},
58 | 'size': settings.data_defined_marker_sizes if settings.data_defined_marker_sizes else settings.properties['marker_size'],
59 | 'symbol': settings.properties['marker_symbol'],
60 | 'line': {'color': settings.data_defined_stroke_colors if settings.data_defined_stroke_colors else settings.properties['out_color'],
61 | 'width': settings.data_defined_stroke_widths if settings.data_defined_stroke_widths else settings.properties['marker_width']}
62 | },
63 | line={'width': settings.properties['marker_width'],
64 | 'dash': settings.properties['line_dash']},
65 | opacity=settings.properties['opacity']
66 | )]
67 |
68 | @staticmethod
69 | def create_layout(settings):
70 | layout = super(ScatterPlotFactory, ScatterPlotFactory).create_layout(settings)
71 |
72 | layout['xaxis'].update(rangeslider=settings.layout['range_slider'])
73 |
74 | return layout
75 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/ternary.py:
--------------------------------------------------------------------------------
1 | """
2 | Ternary plot factory
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class TernaryFactory(PlotType):
17 | """
18 | Factory for ternary plots
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return 'ternary'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('Ternary Plot')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/scatterternary.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 | # prepare the hover text to display if the additional combobox is empty or not
36 | # this setting is necessary to overwrite the standard hovering labels
37 | if not settings.additional_hover_text:
38 | text = [
39 | settings.properties['x_name'] + ': {}'.format(
40 | settings.x[k]) + '
{}: {}'.format(
41 | settings.properties['y_name'],
42 | settings.y[k]) + '
{}: {}'.format(
43 | settings.properties['z_name'], settings.z[k]) for k
44 | in
45 | range(len(settings.x))]
46 | else:
47 | text = [
48 | settings.properties['x_name'] + ': {}'.format(
49 | settings.x[k]) + '
{}: {}'.format(
50 | settings.properties['y_name'],
51 | settings.y[k]) + '
{}: {}'.format(
52 | settings.properties['z_name'],
53 | settings.z[k]) + '
{}'.format(
54 | settings.additional_hover_text[k]) for k in
55 | range(len(settings.x))]
56 |
57 | return [graph_objs.Scatterternary(
58 | a=settings.x,
59 | b=settings.y,
60 | c=settings.z,
61 | name='{} + {} + {}'.format(settings.properties['x_name'],
62 | settings.properties['y_name'],
63 | settings.properties['z_name']),
64 | hoverinfo='text',
65 | text=text,
66 | mode='markers',
67 | marker={
68 | 'color': settings.data_defined_colors if settings.data_defined_colors else settings.properties['in_color'],
69 | 'colorscale': settings.properties['color_scale'],
70 | 'showscale': settings.properties['show_colorscale_legend'],
71 | 'reversescale':settings.properties['invert_color_scale'],
72 | 'colorbar': {
73 | 'len': 0.8
74 | },
75 | 'size': settings.data_defined_marker_sizes if settings.data_defined_marker_sizes else settings.properties['marker_size'],
76 | 'symbol': settings.properties['marker_symbol'],
77 | 'line': {
78 | 'color': settings.data_defined_stroke_colors if settings.data_defined_stroke_colors else settings.properties['out_color'],
79 | 'width': settings.data_defined_stroke_widths if settings.data_defined_stroke_widths else settings.properties['marker_width']
80 | }
81 | },
82 | opacity=settings.properties['opacity']
83 | )]
84 |
85 | @staticmethod
86 | def create_layout(settings):
87 | layout = super(TernaryFactory, TernaryFactory).create_layout(settings)
88 |
89 | # flip the variables according to the box orientation
90 | if settings.properties['box_orientation'] == 'h':
91 | x_title = settings.data_defined_y_title if settings.data_defined_y_title != ''\
92 | else settings.layout['y_title']
93 | y_title = settings.data_defined_x_title if settings.data_defined_x_title != ''\
94 | else settings.layout['x_title']
95 | else:
96 | x_title = settings.data_defined_x_title if settings.data_defined_x_title != ''\
97 | else settings.layout['x_title']
98 | y_title = settings.data_defined_y_title if settings.data_defined_y_title != ''\
99 | else settings.layout['y_title']
100 | z_title = settings.data_defined_z_title if settings.data_defined_z_title != '' \
101 | else settings.layout['z_title']
102 |
103 | layout['xaxis'].update(title='')
104 | layout['xaxis'].update(showgrid=False)
105 | layout['xaxis'].update(zeroline=False)
106 | layout['xaxis'].update(showline=False)
107 | layout['xaxis'].update(showticklabels=False)
108 | layout['yaxis'].update(title='')
109 | layout['yaxis'].update(showgrid=False)
110 | layout['yaxis'].update(zeroline=False)
111 | layout['yaxis'].update(showline=False)
112 | layout['yaxis'].update(showticklabels=False)
113 | layout['ternary'] = {
114 | 'sum': 100,
115 | 'aaxis': {
116 | 'title': x_title,
117 | 'ticksuffix': '%',
118 | },
119 | 'baxis': {
120 | 'title': y_title,
121 | 'ticksuffix': '%'
122 | },
123 | 'caxis': {
124 | 'title': z_title,
125 | 'ticksuffix': '%'
126 | },
127 | }
128 |
129 | return layout
130 |
--------------------------------------------------------------------------------
/DataPlotly/core/plot_types/violin.py:
--------------------------------------------------------------------------------
1 | """
2 | Violin chart factory
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 | """
9 |
10 | import os
11 | from plotly import graph_objs
12 | from qgis.PyQt.QtGui import QIcon
13 | from DataPlotly.core.plot_types.plot_type import PlotType
14 |
15 |
16 | class ViolinFactory(PlotType):
17 | """
18 | Factory for violin charts
19 | """
20 |
21 | @staticmethod
22 | def type_name():
23 | return 'violin'
24 |
25 | @staticmethod
26 | def name():
27 | return PlotType.tr('Violin Plot')
28 |
29 | @staticmethod
30 | def icon():
31 | return QIcon(os.path.join(os.path.dirname(__file__), 'icons/violin.svg'))
32 |
33 | @staticmethod
34 | def create_trace(settings):
35 | # flip the variables according to the box orientation
36 | if settings.properties['box_orientation'] == 'h':
37 | y = settings.x
38 | x = settings.y
39 | else:
40 | x = settings.x
41 | y = settings.y
42 |
43 | return [graph_objs.Violin(
44 | x=x or None,
45 | y=y,
46 | name=settings.data_defined_legend_title if settings.data_defined_legend_title != '' else settings.properties[
47 | 'name'],
48 | customdata=settings.properties['custom'],
49 | orientation=settings.properties['box_orientation'],
50 | points=settings.properties['box_outliers'],
51 | fillcolor=settings.properties['in_color'],
52 | line={
53 | 'color': settings.properties['out_color'],
54 | 'width': settings.properties['marker_width']
55 | },
56 | opacity=settings.properties['opacity'],
57 | meanline={
58 | 'visible': settings.properties['show_mean_line']
59 | },
60 | side=settings.properties['violin_side'],
61 | box_visible=settings.properties['violin_box']
62 | )]
63 |
--------------------------------------------------------------------------------
/DataPlotly/gui/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/gui/__init__.py
--------------------------------------------------------------------------------
/DataPlotly/gui/add_new_dock_dlg.py:
--------------------------------------------------------------------------------
1 | """
2 | Dialog to add new DataPlotlyDock with custom validator
3 | """
4 | from qgis.PyQt import uic
5 | from qgis.PyQt.QtCore import pyqtSignal
6 | from qgis.PyQt.QtGui import QValidator
7 | from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox
8 |
9 | from DataPlotly.core.core_utils import uuid_suffix
10 | from DataPlotly.gui.gui_utils import GuiUtils
11 |
12 | WIDGET, _ = uic.loadUiType(GuiUtils.get_ui_file_path('add_dock_dlg.ui'))
13 |
14 |
15 | class DataPlotlyNewDockDialog(QDialog, WIDGET):
16 | """Dialog to add new dock"""
17 |
18 | def __init__(self, dock_widgets=None, parent=None):
19 | super().__init__(parent)
20 | self.setupUi(self)
21 |
22 | self.DockIdInformationLabel.hide()
23 | validator = DataPlotlyNewDockIdValidator(dock_widgets=dock_widgets)
24 | self.DockIdLineEdit.setValidator(validator)
25 | validator.validationChanged.connect(self.update_dlg)
26 | self.DockTitleLineEdit.valueChanged.connect(self.updateDockIdLineEdit)
27 | self.mCustomizeGroupBox.clicked.connect(self.updateDockIdLineEdit)
28 |
29 | def update_dlg(self, state, msg):
30 | """validator slot"""
31 | is_valid = state != QValidator.Intermediate
32 | self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(is_valid)
33 | label = self.DockIdInformationLabel
34 | lineEdit = self.DockIdLineEdit
35 | style_border = ""
36 | if not is_valid:
37 | style_border = "border: 3px solid red"
38 | label.show()
39 | else:
40 | label.hide()
41 | label.setText(msg)
42 | lineEdit.setStyleSheet(style_border)
43 |
44 | def updateDockIdLineEdit(self):
45 | """update the dockid with uuid suffix when
46 | checkbox is unchecked
47 | """
48 | if not self.mCustomizeGroupBox.isChecked():
49 | title = self.DockTitleLineEdit.value()
50 | self.DockIdLineEdit.setValue(uuid_suffix(title))
51 |
52 | def get_params(self):
53 | """greturn dock_title and dock_id"""
54 | dock_title = self.DockTitleLineEdit.value()
55 | dock_id = self.DockIdLineEdit.value()
56 | return dock_title, dock_id
57 |
58 |
59 | # pylint: disable=too-few-public-methods
60 | class DataPlotlyNewDockIdValidator(QValidator):
61 | """Custom validator to prevent some users action"""
62 | validationChanged = pyqtSignal(QValidator.State, str)
63 |
64 | def __init__(self, parent=None, dock_widgets=None):
65 | """Constructor."""
66 | super().__init__(parent)
67 | self.dock_widgets = dock_widgets
68 |
69 | def validate(self, dock_id, pos):
70 | """Checks if dock_id is not empty and not is already present"""
71 | state = QValidator.Acceptable
72 | msg = None
73 |
74 | if dock_id == "":
75 | state = QValidator.Intermediate
76 | msg = self.tr('DockId can not be empty')
77 |
78 | if dock_id in self.dock_widgets:
79 | state = QValidator.Intermediate
80 | msg = self.tr(f'DockId {dock_id} is already taken')
81 |
82 | if '_' in dock_id:
83 | state = QValidator.Intermediate
84 | msg = self.tr('The underscore _ is not allowed')
85 |
86 | self.validationChanged.emit(state, msg)
87 |
88 | return state, dock_id, pos
89 |
--------------------------------------------------------------------------------
/DataPlotly/gui/dock.py:
--------------------------------------------------------------------------------
1 | """Dock widget
2 |
3 | .. note:: This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 2 of the License, or
6 | (at your option) any later version.
7 | """
8 | from qgis.PyQt.QtCore import QCoreApplication, Qt
9 | from qgis.PyQt.QtXml import QDomDocument, QDomElement
10 |
11 | from qgis.core import QgsXmlUtils
12 | from qgis.gui import (
13 | QgsDockWidget,
14 | QgsPanelWidgetStack
15 | )
16 | from DataPlotly.core.core_utils import restore, restore_safe_str_xml, safe_str_xml
17 | from DataPlotly.gui.add_new_dock_dlg import DataPlotlyNewDockDialog
18 | from DataPlotly.gui.remove_dock_dlg import DataPlotlyRemoveDockDialog
19 | from DataPlotly.gui.plot_settings_widget import DataPlotlyPanelWidget
20 |
21 |
22 | class DataPlotlyDock(QgsDockWidget): # pylint: disable=too-few-public-methods
23 | """
24 | Plot settings dock widget
25 | """
26 |
27 | def __init__(self, parent=None, message_bar=None, # pylint: disable=too-many-arguments
28 | dock_title: str = 'DataPlotly', dock_id: str = 'DataPlotly',
29 | project: QDomDocument = None, override_iface=None):
30 | super().__init__(parent)
31 | self.title = restore_safe_str_xml(dock_title)
32 | self.setWindowTitle(self.title)
33 | self.setObjectName(f'DataPlotly-{dock_id}-Dock')
34 |
35 | self.panel_stack = QgsPanelWidgetStack()
36 | self.setWidget(self.panel_stack)
37 |
38 | self.main_panel = DataPlotlyPanelWidget(
39 | message_bar=message_bar, dock_title=dock_title, dock_id=dock_id, project=project, override_iface=override_iface)
40 | self.panel_stack.setMainPanel(self.main_panel)
41 | self.main_panel.setDockMode(True)
42 |
43 |
44 | class DataPlotlyDockManager():
45 | """
46 | Manager to add multiple docks
47 | """
48 |
49 | def __init__(self, iface, dock_widgets):
50 | self.iface = iface
51 | self.dock_widgets = dock_widgets
52 | self.state = None
53 | self.geometry = None
54 |
55 | @staticmethod
56 | def tr(message):
57 | """ Translate function"""
58 | return QCoreApplication.translate('DataPlotly', message)
59 |
60 | def addNewDockFromDlg(self):
61 | """ Open a dlg and add dock"""
62 | dlg = DataPlotlyNewDockDialog(self.dock_widgets)
63 | if dlg.exec_():
64 | dock_title, dock_id = dlg.get_params()
65 | self.addNewDock(dock_title, dock_id, False)
66 |
67 | def removeDockFromDlg(self):
68 | """ Open a dlg to remove a dock"""
69 | dlg = DataPlotlyRemoveDockDialog(self.dock_widgets)
70 | if dlg.exec_():
71 | dock_id = dlg.get_param()
72 | self.removeDock(dock_id)
73 |
74 | def addNewDock(self, dock_title='DataPlotly', dock_id='DataPlotly', # pylint: disable=too-many-arguments
75 | hide=True, message_bar=None, project=None):
76 | """ Add new dock """
77 | dock_title = safe_str_xml(dock_title)
78 | dock_id = safe_str_xml(dock_id)
79 | if dock_id in self.dock_widgets:
80 | if dock_id == 'DataPlotly':
81 | return self.dock_widgets[dock_id]
82 | self.iface.messageBar().pushWarning(self.tr('Warning'), self.tr(
83 | f'DataPlotlyDock can not be added because {dock_id} is already present'))
84 | return False
85 | message_bar = message_bar or self.iface.messageBar()
86 | dock = DataPlotlyDock(
87 | dock_title=dock_title, message_bar=message_bar, dock_id=dock_id, project=project, override_iface=self.iface)
88 | self.dock_widgets[dock_id] = dock
89 | self.iface.addDockWidget(Qt.RightDockWidgetArea, dock)
90 | if hide:
91 | dock.hide()
92 | return dock
93 |
94 | def removeDock(self, dock_id):
95 | """ Remove dock with id """
96 | dock = self.dock_widgets.pop(dock_id, None)
97 | if dock:
98 | self.iface.removeDockWidget(dock)
99 | # TODO remove dock_id in project file
100 |
101 | def removeDocks(self):
102 | """ Remove all docks except the main one """
103 | dock_widgets = self.dock_widgets.copy()
104 | for dock_id in dock_widgets.keys():
105 | if dock_id == 'DataPlotly':
106 | continue
107 | self.removeDock(dock_id)
108 |
109 | # self.dock_project_empty = True
110 |
111 | def addDocksFromProject(self, document: QDomDocument):
112 | """ Add docks from project instance """
113 | root_node = document.elementsByTagName("qgis").item(0)
114 | if root_node.isNull():
115 | return False
116 | # loop to find matching dock
117 | nodes = root_node.childNodes()
118 | for i in range(nodes.length()):
119 | tag_name = nodes.at(i).toElement().tagName()
120 | if tag_name.startswith('DataPlotly_'):
121 | _, dock_title, dock_id = tag_name.split('_')
122 | self.addNewDock(dock_title=restore_safe_str_xml(dock_title),
123 | dock_id=restore_safe_str_xml(dock_id),
124 | hide=False,
125 | message_bar=None,
126 | project=document)
127 | # FIXME : trigger the plot creation (not working)
128 | # main_panel = self.getDock(tag_name).main_panel
129 | # main_panel.create_plot()
130 | if self.iface and self.read_from_project(document):
131 | self.iface.mainWindow().restoreGeometry(self.geometry)
132 | self.iface.mainWindow().restoreState(self.state, version=999)
133 | return True
134 |
135 | def getDock(self, dock_id: str) -> DataPlotlyDock:
136 | """ Return the dock from the dock_id """
137 | dock = self.dock_widgets.get(dock_id)
138 | if not dock:
139 | self.iface.messageBar().pushWarning(
140 | self.tr('Warning'),
141 | self.tr(f'DataPlotlyDock {dock_id} can not be found'))
142 | return dock
143 |
144 | # TODO: Refactor : this functions are almost the same in plot_settings.py
145 | def write_xml(self, document: QDomDocument):
146 | """
147 | Writes the docks position settings to an XML element
148 | """
149 | mw = self.iface.mainWindow()
150 | state = mw.saveState(version=999).toBase64()
151 | geometry = mw.saveGeometry().toBase64()
152 |
153 | element = QgsXmlUtils.writeVariant({
154 | 'state': str(state, "utf-8"),
155 | 'geometry': str(geometry, "utf-8")
156 | }, document)
157 | return element
158 |
159 | def read_xml(self, element: QDomElement) -> bool:
160 | """
161 | Reads the docs state settings from an XML element
162 | """
163 | res = QgsXmlUtils.readVariant(element)
164 | if not isinstance(res, dict) or \
165 | 'geometry' not in res or \
166 | 'state' not in res:
167 | return False
168 |
169 | self.state = restore(res['state'])
170 | self.geometry = restore(res['geometry'])
171 | return True
172 |
173 | def write_to_project(self, document: QDomDocument):
174 | """
175 | Writes the settings to a project (represented by the given DOM document)
176 | """
177 | elem = self.write_xml(document)
178 | parent_elem = document.createElement('StateDataPlotly')
179 | parent_elem.appendChild(elem)
180 | root_node = document.elementsByTagName("qgis").item(0)
181 | root_node.appendChild(parent_elem)
182 |
183 | def read_from_project(self, document: QDomDocument):
184 | """
185 | Reads the settings from a project (represented by the given DOM document)
186 | """
187 | root_node = document.elementsByTagName("qgis").item(0)
188 | if root_node.isNull():
189 | return False
190 |
191 | node = root_node.toElement().firstChildElement('StateDataPlotly')
192 | if node.isNull():
193 | return False
194 |
195 | elem = node.toElement()
196 | return self.read_xml(elem.firstChildElement())
197 |
--------------------------------------------------------------------------------
/DataPlotly/gui/gui_utils.py:
--------------------------------------------------------------------------------
1 | """GUI Utilities
2 |
3 | .. note:: This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 2 of the License, or
6 | (at your option) any later version.
7 | """
8 |
9 | import os
10 | from qgis.PyQt.QtGui import QIcon
11 |
12 |
13 | class GuiUtils:
14 | """
15 | Utilities for GUI plugin components
16 | """
17 |
18 | @staticmethod
19 | def get_icon(icon: str) -> QIcon:
20 | """
21 | Returns a plugin icon
22 | :param icon: icon name (svg file name)
23 | :return: QIcon
24 | """
25 | path = GuiUtils.get_icon_svg(icon)
26 | if not path:
27 | return QIcon()
28 |
29 | return QIcon(path)
30 |
31 | @staticmethod
32 | def get_icon_svg(icon: str) -> str:
33 | """
34 | Returns a plugin icon's SVG file path
35 | :param icon: icon name (svg file name)
36 | :return: icon svg path
37 | """
38 | path = os.path.join(
39 | os.path.dirname(__file__),
40 | '..',
41 | 'icons',
42 | icon)
43 | if not os.path.exists(path):
44 | return ''
45 |
46 | return path
47 |
48 | @staticmethod
49 | def get_ui_file_path(file: str) -> str:
50 | """
51 | Returns a UI file's path
52 | :param file: file name (uifile name)
53 | :return: ui file path
54 | """
55 | path = os.path.join(
56 | os.path.dirname(__file__),
57 | '..',
58 | 'ui',
59 | file)
60 | if not os.path.exists(path):
61 | return ''
62 |
63 | return path
64 |
--------------------------------------------------------------------------------
/DataPlotly/gui/layout_item_gui.py:
--------------------------------------------------------------------------------
1 | """Plot Layout Gui Handling
2 |
3 | .. note:: This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 2 of the License, or
6 | (at your option) any later version.
7 | """
8 |
9 | from qgis.PyQt.QtCore import (
10 | QCoreApplication,
11 | QItemSelectionModel
12 | )
13 | from qgis.PyQt.QtWidgets import (
14 | QListWidget,
15 | QHBoxLayout,
16 | QPushButton,
17 | QVBoxLayout
18 | )
19 | from qgis.gui import (
20 | QgsLayoutItemAbstractGuiMetadata,
21 | QgsLayoutItemBaseWidget,
22 | QgsLayoutItemPropertiesWidget
23 | )
24 |
25 | from DataPlotly.layouts.plot_layout_item import ITEM_TYPE
26 | from DataPlotly.gui.gui_utils import GuiUtils
27 | from DataPlotly.gui.plot_settings_widget import DataPlotlyPanelWidget
28 |
29 |
30 | class PlotLayoutItemWidget(QgsLayoutItemBaseWidget):
31 | """
32 | Configuration widget for layout plot items
33 | """
34 |
35 | def __init__(self, parent, layout_object):
36 | super().__init__(parent, layout_object)
37 | self.plot_item = layout_object
38 | self.message_bar = None
39 |
40 | vl = QVBoxLayout()
41 | vl.setContentsMargins(0, 0, 0, 0)
42 |
43 | plot_tools_layout = QHBoxLayout()
44 |
45 | plot_add_button = QPushButton()
46 | plot_add_button.setIcon(GuiUtils.get_icon('symbologyAdd.svg'))
47 | plot_add_button.setToolTip('Add a new plot')
48 | plot_tools_layout.addWidget(plot_add_button)
49 | plot_add_button.clicked.connect(self.add_plot)
50 |
51 | plot_remove_button = QPushButton()
52 | plot_remove_button.setIcon(GuiUtils.get_icon('symbologyRemove.svg'))
53 | plot_remove_button.setToolTip('Remove selected plot')
54 | plot_tools_layout.addWidget(plot_remove_button)
55 | plot_remove_button.clicked.connect(self.remove_plot)
56 |
57 | plot_duplicate_button = QPushButton()
58 | plot_duplicate_button.setIcon(GuiUtils.get_icon('mActionDuplicateLayer.svg'))
59 | plot_duplicate_button.setToolTip('Duplicates the selected plot')
60 | plot_tools_layout.addWidget(plot_duplicate_button)
61 | plot_duplicate_button.clicked.connect(self.duplicate_plot)
62 |
63 | plot_move_up_button = QPushButton()
64 | plot_move_up_button.setIcon(GuiUtils.get_icon('mActionArrowUp.svg'))
65 | plot_move_up_button.setToolTip('Move selected plot up')
66 | plot_tools_layout.addWidget(plot_move_up_button)
67 | plot_move_up_button.clicked.connect(self.move_up_plot)
68 |
69 | plot_move_down_button = QPushButton()
70 | plot_move_down_button.setIcon(GuiUtils.get_icon('mActionArrowDown.svg'))
71 | plot_move_down_button.setToolTip('Move selected plot down')
72 | plot_tools_layout.addWidget(plot_move_down_button)
73 | plot_move_down_button.clicked.connect(self.move_down_plot)
74 |
75 | vl.addLayout(plot_tools_layout)
76 |
77 | self.plot_list = QListWidget()
78 | self.plot_list.setSelectionMode(QListWidget.SingleSelection)
79 | self.plot_list.doubleClicked.connect(self.show_properties)
80 | vl.addWidget(self.plot_list)
81 | self.populate_plot_list()
82 |
83 | plot_properties_button = QPushButton(self.tr('Setup Selected Plot'))
84 | vl.addWidget(plot_properties_button)
85 | plot_properties_button.clicked.connect(self.show_properties)
86 |
87 | self.panel = None
88 | self.setPanelTitle(self.tr('Plot Properties'))
89 | self.item_properties_widget = QgsLayoutItemPropertiesWidget(self, layout_object)
90 | vl.addWidget(self.item_properties_widget)
91 | self.setLayout(vl)
92 |
93 | def populate_plot_list(self):
94 | """
95 | Clears and re-populates the plot list widget. The currently selection is retained
96 | """
97 | selected_index = self.plot_list.currentRow()
98 | self.plot_list.clear()
99 | for setting in self.plot_item.plot_settings:
100 | plot_type = setting.plot_type if setting.plot_type is not None else '(not set)'
101 | legend_title = ('[' + setting.properties.get('name') + ']') \
102 | if setting.properties.get('name', '') != '' else ''
103 | self.plot_list.addItem(plot_type + ' ' + legend_title)
104 |
105 | # select index within range [0, len(plot_settings)-1]
106 | selected_index = max(0, min(len(self.plot_item.plot_settings) - 1, selected_index))
107 | self.plot_list.setCurrentRow(selected_index, QItemSelectionModel.SelectCurrent)
108 |
109 | def add_plot(self):
110 | """
111 | Adds a new plot and updates the plot list and the plot item
112 | """
113 | self.plot_item.add_plot()
114 | self.populate_plot_list()
115 | self.plot_item.refresh()
116 |
117 | def duplicate_plot(self):
118 | """
119 | Duplicates an existing plot and updates the plot list and the plot item
120 | """
121 |
122 | selected_plot_index = self.plot_list.currentRow()
123 | if selected_plot_index < 0:
124 | return
125 |
126 | self.plot_item.duplicate_plot(selected_plot_index)
127 | self.populate_plot_list()
128 | self.plot_item.refresh()
129 |
130 | def remove_plot(self):
131 | """
132 | Removes the selected plot and updates the plot list and the plot item
133 | """
134 | selected_index = self.plot_list.currentRow()
135 | if selected_index < 0:
136 | return
137 |
138 | self.plot_item.remove_plot(selected_index)
139 | self.populate_plot_list()
140 | self.plot_item.refresh()
141 |
142 | def move_up_plot(self):
143 | """
144 | Moves the selected plot up and updates the plot list and the plot item
145 | """
146 | selected_index = self.plot_list.currentRow()
147 | if selected_index <= 0:
148 | return
149 |
150 | item = self.plot_item.plot_settings.pop(selected_index)
151 | self.plot_item.plot_settings.insert(selected_index - 1, item)
152 | self.plot_list.setCurrentRow(selected_index - 1, QItemSelectionModel.SelectCurrent)
153 | self.populate_plot_list()
154 | self.plot_item.refresh()
155 |
156 | def move_down_plot(self):
157 | """
158 | Moves the selected plot down and updates the plot list and the plot item
159 | """
160 | selected_index = self.plot_list.currentRow()
161 | if selected_index >= len(self.plot_item.plot_settings) - 1:
162 | return
163 |
164 | item = self.plot_item.plot_settings.pop(selected_index)
165 | self.plot_item.plot_settings.insert(selected_index + 1, item)
166 | self.plot_list.setCurrentRow(selected_index + 1, QItemSelectionModel.SelectCurrent)
167 | self.populate_plot_list()
168 | self.plot_item.refresh()
169 |
170 | def show_properties(self):
171 | """
172 | Shows the plot properties panel
173 | """
174 | selected_plot_index = self.plot_list.currentRow()
175 | if selected_plot_index < 0:
176 | return
177 |
178 | self.panel = DataPlotlyPanelWidget(mode=DataPlotlyPanelWidget.MODE_LAYOUT, message_bar=self.message_bar)
179 |
180 | # not quite right -- we ideally want to also add the source layer scope into the context given by plot item,
181 | # but that causes a hard lock in the Python GIL (because PyQt doesn't release the GIL when creating the menu
182 | # for the property override buttons). Nothing much we can do about that here (or in QGIS,
183 | # it's a Python/PyQt limitation)
184 | self.panel.registerExpressionContextGenerator(self.plot_item)
185 | self.panel.set_print_layout(self.plot_item.layout())
186 |
187 | self.panel.linked_map_combo.blockSignals(True)
188 | self.panel.linked_map_combo.setItem(self.plot_item.linked_map)
189 | self.panel.linked_map_combo.blockSignals(False)
190 |
191 | self.panel.linked_map_combo.itemChanged.connect(self.linked_map_changed)
192 |
193 | self.panel.set_settings(self.plot_item.plot_settings[selected_plot_index])
194 | # self.panel.set_settings(self.layoutItem().plot_settings)
195 | self.openPanel(self.panel)
196 | self.panel.widgetChanged.connect(self.update_item_settings)
197 | self.panel.panelAccepted.connect(self.set_item_settings)
198 |
199 | def update_item_settings(self):
200 | """
201 | Updates the plot item without dismissing the properties panel
202 | """
203 | if not self.panel:
204 | return
205 |
206 | self.plot_item.set_plot_settings(self.plot_list.currentRow(), self.panel.get_settings())
207 | self.populate_plot_list()
208 | self.plot_item.update()
209 |
210 | def set_item_settings(self):
211 | """
212 | Updates the plot item based on the settings from the properties panel
213 | """
214 | if not self.panel:
215 | return
216 |
217 | self.plot_item.set_plot_settings(self.plot_list.currentRow(), self.panel.get_settings())
218 | self.populate_plot_list()
219 | self.panel = None
220 | self.plot_item.update()
221 |
222 | def linked_map_changed(self, linked_map):
223 | """
224 | Triggered when the linked map is changed
225 | """
226 | self.plot_item.set_linked_map(linked_map)
227 | self.plot_item.update()
228 |
229 | def setNewItem(self, item): # pylint: disable=missing-docstring
230 | if item.type() != ITEM_TYPE:
231 | return False
232 |
233 | self.plot_item = item
234 | self.item_properties_widget.setItem(item)
235 | self.populate_plot_list()
236 |
237 | if self.panel is not None:
238 | self.panel.set_settings(self.plot_item.plot_settings[0])
239 |
240 | self.panel.linked_map_combo.blockSignals(True)
241 | self.panel.linked_map_combo.setItem(self.plot_item.linked_map)
242 | self.panel.linked_map_combo.blockSignals(False)
243 |
244 | return True
245 |
246 | def setDesignerInterface(self, iface): # pylint: disable=missing-docstring
247 | super().setDesignerInterface(iface)
248 | self.message_bar = iface.messageBar()
249 | if self.panel:
250 | self.panel.message_bar = self.message_bar
251 |
252 |
253 | class PlotLayoutItemGuiMetadata(QgsLayoutItemAbstractGuiMetadata):
254 | """
255 | Metadata for plot item GUI classes
256 | """
257 |
258 | def __init__(self):
259 | super().__init__(ITEM_TYPE, QCoreApplication.translate('DataPlotly', 'Plot Item'))
260 |
261 | def creationIcon(self): # pylint: disable=missing-docstring, no-self-use
262 | return GuiUtils.get_icon('dataplotly.svg')
263 |
264 | def createItemWidget(self, item): # pylint: disable=missing-docstring, no-self-use
265 | return PlotLayoutItemWidget(None, item)
266 |
--------------------------------------------------------------------------------
/DataPlotly/gui/remove_dock_dlg.py:
--------------------------------------------------------------------------------
1 | """
2 | Minimal dlg with combobox to remove DataPlotlyDialog
3 | """
4 | from qgis.PyQt import uic
5 |
6 | from qgis.PyQt.QtWidgets import QDialog
7 | from qgis.PyQt.QtCore import Qt
8 |
9 | from DataPlotly.gui.gui_utils import GuiUtils
10 |
11 |
12 | WIDGET, _ = uic.loadUiType(GuiUtils.get_ui_file_path('remove_dock_dlg.ui'))
13 |
14 |
15 | # pylint: disable=too-few-public-methods
16 | class DataPlotlyRemoveDockDialog(QDialog, WIDGET):
17 | """Dialog to remove new dock"""
18 | def __init__(self, dock_widgets=None, parent=None):
19 | super().__init__(parent)
20 | self.setupUi(self)
21 | dock_ids = [dock_id for dock_id in dock_widgets.keys() if dock_id != 'DataPlotly']
22 | self.DockIdsComboBox.addItems(dock_ids)
23 | for i, dock_id in enumerate(dock_ids):
24 | self.DockIdsComboBox.setItemData(i, dock_widgets[dock_id].title, Qt.ToolTipRole)
25 |
26 | def get_param(self):
27 | """Return the dock_id to delete"""
28 | return self.DockIdsComboBox.currentText()
29 |
--------------------------------------------------------------------------------
/DataPlotly/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/icon.png
--------------------------------------------------------------------------------
/DataPlotly/icons/create_plot.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
236 |
--------------------------------------------------------------------------------
/DataPlotly/icons/dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/icons/dash.png
--------------------------------------------------------------------------------
/DataPlotly/icons/dataplotly.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
208 |
--------------------------------------------------------------------------------
/DataPlotly/icons/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/icons/dot.png
--------------------------------------------------------------------------------
/DataPlotly/icons/dotdash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/icons/dotdash.png
--------------------------------------------------------------------------------
/DataPlotly/icons/list_code.svg:
--------------------------------------------------------------------------------
1 |
2 |
115 |
--------------------------------------------------------------------------------
/DataPlotly/icons/list_custom.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/DataPlotly/icons/list_help.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/DataPlotly/icons/list_plot.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
190 |
--------------------------------------------------------------------------------
/DataPlotly/icons/list_properties.svg:
--------------------------------------------------------------------------------
1 |
41 |
--------------------------------------------------------------------------------
/DataPlotly/icons/longdash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/icons/longdash.png
--------------------------------------------------------------------------------
/DataPlotly/icons/longdashdot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/icons/longdashdot.png
--------------------------------------------------------------------------------
/DataPlotly/icons/mActionArrowDown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/DataPlotly/icons/mActionArrowUp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/DataPlotly/icons/mActionDuplicateLayer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/DataPlotly/icons/mIconDataDefineExpression.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/DataPlotly/icons/solid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/icons/solid.png
--------------------------------------------------------------------------------
/DataPlotly/icons/symbologyAdd.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/DataPlotly/icons/symbologyRemove.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/DataPlotly/metadata.txt:
--------------------------------------------------------------------------------
1 | [general]
2 | name=Data Plotly
3 | qgisMinimumVersion=3.28
4 | qgisMaximumVersion=3.98
5 | description=D3 Plots for QGIS
6 | author=Matteo Ghetta (Faunalia)
7 | email=matteo.ghetta@gmail.com
8 |
9 | about=Draw D3 plots in QGIS
10 |
11 | homepage=https://dataplotly-docs.readthedocs.io
12 | tracker=https://github.com/ghtmtt/DataPlotly/issues
13 | repository=https://github.com/ghtmtt/DataPlotly
14 | # End of mandatory metadata
15 |
16 | # Done by qgis-plugin-ci
17 | version=master
18 | changelog=
19 | commitNumber=
20 | commitSha1=
21 | dateTime=
22 | # End of qgis-plugin-ci
23 |
24 | # Recommended items:
25 |
26 | # Tags are comma separated with spaces allowed
27 | tags=python, d3, plots, vector, graphs, datavis, dataviz, dataplotly
28 |
29 | category=Plugins
30 | icon=icon.png
31 | # experimental flag
32 | experimental=False
33 |
34 | # deprecated flag (applies to the whole plugin, not just a single version)
35 | deprecated=False
36 |
37 | hasProcessingProvider=yes
38 | server=True
39 |
--------------------------------------------------------------------------------
/DataPlotly/processing/dataplotly_provider.py:
--------------------------------------------------------------------------------
1 | """
2 | /***************************************************************************
3 | DataPlotly
4 | A QGIS plugin
5 | D3 Plots for QGIS
6 | -------------------
7 | begin : 2017-03-05
8 | git sha : $Format:%H$
9 | copyright : (C) 2017 by matteo ghetta
10 | email : matteo.ghetta@gmail.com
11 | ***************************************************************************/
12 |
13 | /***************************************************************************
14 | * *
15 | * This program is free software; you can redistribute it and/or modify *
16 | * it under the terms of the GNU General Public License as published by *
17 | * the Free Software Foundation; either version 2 of the License, or *
18 | * (at your option) any later version. *
19 | * *
20 | ***************************************************************************/
21 | """
22 | from qgis.core import Qgis, QgsMessageLog, QgsProcessingProvider
23 | from DataPlotly.gui.gui_utils import GuiUtils
24 |
25 | try:
26 | # 🐼
27 | from DataPlotly.processing.dataplotly_scatterplot import DataPlotlyProcessingScatterPlot
28 | WITH_PANDAS = True
29 | except ImportError:
30 | WITH_PANDAS = False
31 | QgsMessageLog.logMessage(
32 | "Pandas has not been found. The processing algorithm will not be loaded. "
33 | "Please install qgis-full or qgis standalone", "DataPlotly", Qgis.Warning)
34 |
35 |
36 | class DataPlotlyProvider(QgsProcessingProvider):
37 |
38 | def __init__(self, plugin_version):
39 | super().__init__()
40 | self.plugin_version = plugin_version
41 |
42 | def load(self):
43 | """In this method we add settings needed to configure our
44 | provider.
45 | """
46 | self.refreshAlgorithms()
47 | return True
48 |
49 | def id(self):
50 | """This is the name that will appear on the toolbox group.
51 |
52 | It is also used to create the command line name of all the
53 | algorithms from this provider.
54 | """
55 | return 'DataPlotly'
56 |
57 | def name(self):
58 | """This is the localised full name.
59 | """
60 | return 'DataPlotly'
61 |
62 | def longName(self):
63 | return 'DataPlotly'
64 |
65 | def icon(self):
66 | return GuiUtils.get_icon('dataplotly.svg')
67 |
68 | def svgIconPath(self) -> str:
69 | return GuiUtils.get_icon_svg('dataplotly.svg')
70 |
71 | def versionInfo(self) -> str:
72 | return self.plugin_version
73 |
74 | def loadAlgorithms(self):
75 | """Here we fill the list of algorithms in self.algs.
76 |
77 | This method is called whenever the list of algorithms should
78 | be updated. If the list of algorithms can change (for instance,
79 | if it contains algorithms from user-defined scripts and a new
80 | script might have been added), you should create the list again
81 | here.
82 |
83 | In this case, since the list is always the same, we assign from
84 | the pre-made list. This assignment has to be done in this method
85 | even if the list does not change, since the self.algs list is
86 | cleared before calling this method.
87 | """
88 | if WITH_PANDAS:
89 | self.addAlgorithm(DataPlotlyProcessingScatterPlot())
90 |
--------------------------------------------------------------------------------
/DataPlotly/test/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | DataPlotly test suite
3 | """
4 |
5 | # import qgis libs so that we set the correct sip api version
6 | import qgis # pylint: disable=W0401,W0614
7 |
--------------------------------------------------------------------------------
/DataPlotly/test/processing_scatter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/DataPlotly/test/qgis_interface.py:
--------------------------------------------------------------------------------
1 | """QGIS plugin implementation.
2 |
3 | .. note:: This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 2 of the License, or
6 | (at your option) any later version.
7 |
8 | .. note:: This source code was copied from the 'postgis viewer' application
9 | with original authors:
10 | Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk
11 | Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org
12 | Copyright (c) 2014 Tim Sutton, tim@linfiniti.com
13 |
14 | """
15 |
16 | __author__ = 'tim@linfiniti.com'
17 | __revision__ = '$Format:%H$'
18 | __date__ = '10/01/2011'
19 | __copyright__ = (
20 | 'Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and '
21 | 'Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org'
22 | 'Copyright (c) 2014 Tim Sutton, tim@linfiniti.com'
23 | )
24 |
25 | import logging
26 | from typing import List
27 | from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QSize
28 | from qgis.PyQt.QtWidgets import QDockWidget
29 | from qgis.core import QgsProject, QgsMapLayer
30 | from qgis.gui import (QgsMapCanvas,
31 | QgsMessageBar)
32 |
33 | LOGGER = logging.getLogger('QGIS')
34 |
35 |
36 | # noinspection PyMethodMayBeStatic,PyPep8Naming
37 | # pylint: disable=too-many-public-methods
38 | class QgisInterface(QObject):
39 | """Class to expose QGIS objects and functions to plugins.
40 |
41 | This class is here for enabling us to run unit tests only,
42 | so most methods are simply stubs.
43 | """
44 | currentLayerChanged = pyqtSignal(QgsMapLayer)
45 |
46 | def __init__(self, canvas: QgsMapCanvas):
47 | """Constructor
48 | :param canvas:
49 | """
50 | QObject.__init__(self)
51 | self.canvas = canvas
52 | # Set up slots so we can mimic the behaviour of QGIS when layers
53 | # are added.
54 | LOGGER.debug('Initialising canvas...')
55 | # noinspection PyArgumentList
56 | QgsProject.instance().layersAdded.connect(self.addLayers)
57 | # noinspection PyArgumentList
58 | QgsProject.instance().layerWasAdded.connect(self.addLayer)
59 | # noinspection PyArgumentList
60 | QgsProject.instance().removeAll.connect(self.removeAllLayers)
61 |
62 | # For processing module
63 | self.destCrs = None
64 |
65 | self.message_bar = QgsMessageBar()
66 |
67 | def addLayers(self, layers: List[QgsMapLayer]):
68 | """Handle layers being added to the registry so they show up in canvas.
69 |
70 | :param layers: list list of map layers that were added
71 |
72 | .. note:: The QgsInterface api does not include this method,
73 | it is added here as a helper to facilitate testing.
74 | """
75 | # LOGGER.debug('addLayers called on qgis_interface')
76 | # LOGGER.debug('Number of layers being added: %s' % len(layers))
77 | # LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers()))
78 | current_layers = self.canvas.layers()
79 | final_layers = []
80 | for layer in current_layers:
81 | final_layers.append(layer)
82 | for layer in layers:
83 | final_layers.append(layer)
84 |
85 | self.canvas.setLayers(final_layers)
86 | # LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers()))
87 |
88 | def addLayer(self, layer: QgsMapLayer):
89 | """Handle a layer being added to the registry so it shows up in canvas.
90 |
91 | :param layer: list list of map layers that were added
92 |
93 | .. note: The QgsInterface api does not include this method, it is added
94 | here as a helper to facilitate testing.
95 |
96 | .. note: The addLayer method was deprecated in QGIS 1.8 so you should
97 | not need this method much.
98 | """
99 | pass # pylint: disable=unnecessary-pass
100 |
101 | @pyqtSlot()
102 | def removeAllLayers(self): # pylint: disable=no-self-use
103 | """Remove layers from the canvas before they get deleted."""
104 | self.canvas.setLayers([])
105 |
106 | def newProject(self): # pylint: disable=no-self-use
107 | """Create new project."""
108 | # noinspection PyArgumentList
109 | QgsProject.instance().clear()
110 |
111 | # ---------------- API Mock for QgsInterface follows -------------------
112 |
113 | def zoomFull(self):
114 | """Zoom to the map full extent."""
115 | pass # pylint: disable=unnecessary-pass
116 |
117 | def zoomToPrevious(self):
118 | """Zoom to previous view extent."""
119 | pass # pylint: disable=unnecessary-pass
120 |
121 | def zoomToNext(self):
122 | """Zoom to next view extent."""
123 | pass # pylint: disable=unnecessary-pass
124 |
125 | def zoomToActiveLayer(self):
126 | """Zoom to extent of active layer."""
127 | pass # pylint: disable=unnecessary-pass
128 |
129 | def addVectorLayer(self, path: str, base_name: str, provider_key: str):
130 | """Add a vector layer.
131 |
132 | :param path: Path to layer.
133 | :type path: str
134 |
135 | :param base_name: Base name for layer.
136 | :type base_name: str
137 |
138 | :param provider_key: Provider key e.g. 'ogr'
139 | :type provider_key: str
140 | """
141 | pass # pylint: disable=unnecessary-pass
142 |
143 | def addRasterLayer(self, path: str, base_name: str):
144 | """Add a raster layer given a raster layer file name
145 |
146 | :param path: Path to layer.
147 | :type path: str
148 |
149 | :param base_name: Base name for layer.
150 | :type base_name: str
151 | """
152 | pass # pylint: disable=unnecessary-pass
153 |
154 | def activeLayer(self) -> QgsMapLayer: # pylint: disable=no-self-use
155 | """Get pointer to the active layer (layer selected in the legend)."""
156 | # noinspection PyArgumentList
157 | layers = QgsProject.instance().mapLayers()
158 | for item in layers:
159 | return layers[item]
160 |
161 | def addToolBarIcon(self, action):
162 | """Add an icon to the plugins toolbar.
163 |
164 | :param action: Action to add to the toolbar.
165 | :type action: QAction
166 | """
167 | pass # pylint: disable=unnecessary-pass
168 |
169 | def removeToolBarIcon(self, action):
170 | """Remove an action (icon) from the plugin toolbar.
171 |
172 | :param action: Action to add to the toolbar.
173 | :type action: QAction
174 | """
175 | pass # pylint: disable=unnecessary-pass
176 |
177 | def addToolBar(self, name):
178 | """Add toolbar with specified name.
179 |
180 | :param name: Name for the toolbar.
181 | :type name: str
182 | """
183 | pass # pylint: disable=unnecessary-pass
184 |
185 | def mapCanvas(self) -> QgsMapCanvas:
186 | """Return a pointer to the map canvas."""
187 | return self.canvas
188 |
189 | def mainWindow(self):
190 | """Return a pointer to the main window.
191 |
192 | In case of QGIS it returns an instance of QgisApp.
193 | """
194 | pass # pylint: disable=unnecessary-pass
195 |
196 | def addDockWidget(self, area, dock_widget: QDockWidget):
197 | """Add a dock widget to the main window.
198 |
199 | :param area: Where in the ui the dock should be placed.
200 | :type area:
201 |
202 | :param dock_widget: A dock widget to add to the UI.
203 | :type dock_widget: QDockWidget
204 | """
205 | pass # pylint: disable=unnecessary-pass
206 |
207 | def removeDockWidget(self, dock_widget: QDockWidget):
208 | """Remove a dock widget to the main window.
209 |
210 | :param area: Where in the ui the dock should be placed.
211 | :type area:
212 |
213 | :param dock_widget: A dock widget to add to the UI.
214 | :type dock_widget: QDockWidget
215 | """
216 | pass # pylint: disable=unnecessary-pass
217 |
218 | def legendInterface(self):
219 | """Get the legend."""
220 | return self.canvas
221 |
222 | def iconSize(self, dockedToolbar) -> int: # pylint: disable=no-self-use
223 | """
224 | Returns the toolbar icon size.
225 | :param dockedToolbar: If True, the icon size
226 | for toolbars contained within docks is returned.
227 | """
228 | if dockedToolbar:
229 | return QSize(16, 16)
230 |
231 | return QSize(24, 24)
232 |
233 | def messageBar(self) -> QgsMessageBar:
234 | """
235 | Return the message bar of the main app
236 | """
237 | return self.message_bar
238 |
--------------------------------------------------------------------------------
/DataPlotly/test/tenbytenraster.asc:
--------------------------------------------------------------------------------
1 | NCOLS 10
2 | NROWS 10
3 | XLLCENTER 1535380.000000
4 | YLLCENTER 5083260.000000
5 | DX 10
6 | DY 10
7 | NODATA_VALUE -9999
8 | 0 1 2 3 4 5 6 7 8 9
9 | 0 1 2 3 4 5 6 7 8 9
10 | 0 1 2 3 4 5 6 7 8 9
11 | 0 1 2 3 4 5 6 7 8 9
12 | 0 1 2 3 4 5 6 7 8 9
13 | 0 1 2 3 4 5 6 7 8 9
14 | 0 1 2 3 4 5 6 7 8 9
15 | 0 1 2 3 4 5 6 7 8 9
16 | 0 1 2 3 4 5 6 7 8 9
17 | 0 1 2 3 4 5 6 7 8 9
18 | CRS
19 | NOTES
20 |
--------------------------------------------------------------------------------
/DataPlotly/test/tenbytenraster.asc.aux.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Point
4 |
5 |
6 |
7 | 9
8 | 4.5
9 | 0
10 | 2.872281323269
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/DataPlotly/test/tenbytenraster.keywords:
--------------------------------------------------------------------------------
1 | title: Tenbytenraster
2 |
--------------------------------------------------------------------------------
/DataPlotly/test/tenbytenraster.lic:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Tim Sutton, Linfiniti Consulting CC
5 |
6 |
7 |
8 | tenbytenraster.asc
9 | 2700044251
10 | Yes
11 | Tim Sutton
12 | Tim Sutton (QGIS Source Tree)
13 | Tim Sutton
14 | This data is publicly available from QGIS Source Tree. The original
15 | file was created and contributed to QGIS by Tim Sutton.
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/DataPlotly/test/tenbytenraster.prj:
--------------------------------------------------------------------------------
1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
--------------------------------------------------------------------------------
/DataPlotly/test/tenbytenraster.qml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 0
26 |
27 |
--------------------------------------------------------------------------------
/DataPlotly/test/test_dock_manager.py:
--------------------------------------------------------------------------------
1 | """Plot factory test
2 |
3 | .. note:: This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 2 of the License, or
6 | (at your option) any later version.
7 |
8 | """
9 |
10 | import os.path
11 | import unittest
12 |
13 | from qgis.PyQt.QtCore import QByteArray, QFile, QIODevice
14 | from qgis.PyQt.QtGui import QValidator
15 | from qgis.PyQt.QtXml import QDomDocument
16 |
17 | from DataPlotly.core.core_utils import restore, restore_safe_str_xml, safe_str_xml
18 | from DataPlotly.test.utilities import get_qgis_app
19 | from DataPlotly.gui.dock import (DataPlotlyDock, DataPlotlyDockManager)
20 | from DataPlotly.gui.add_new_dock_dlg import DataPlotlyNewDockIdValidator
21 |
22 | QGIS_APP, CANVAS, IFACE, PARENT = get_qgis_app()
23 |
24 |
25 | def read_project(project_path):
26 | """Retur a document from qgs file
27 |
28 | Args:
29 | project_path (str): path to qgs file
30 |
31 | Returns:
32 | QDocument: document
33 | """
34 | xml_file = QFile(project_path)
35 | if xml_file.open(QIODevice.ReadOnly):
36 | xml_doc = QDomDocument()
37 | xml_doc.setContent(xml_file)
38 | xml_file.close()
39 | return xml_doc
40 | return None
41 |
42 |
43 | class DataPlotlyDockManagerTest(unittest.TestCase):
44 | """
45 | Test DataPlotlyDockManager
46 | """
47 |
48 | def setUp(self):
49 | self.dock_widgets = {}
50 | self.dock_manager = DataPlotlyDockManager(
51 | iface=IFACE, dock_widgets=self.dock_widgets)
52 |
53 | def test_001_constructor(self):
54 | """
55 | Test the constructor of DataPlotlyDockManager
56 | """
57 | self.assertIs(self.dock_widgets, self.dock_manager.dock_widgets)
58 |
59 | def test_002_add_new_dock(self):
60 | """
61 | Test addNewDock of DataPlotlyDockManager
62 | """
63 | # checks DataPlotly main dock
64 | self.assertNotIn('DataPlotly', self.dock_widgets)
65 | dock_widget = self.dock_manager.addNewDock()
66 | self.assertIsInstance(dock_widget, DataPlotlyDock)
67 | self.assertIn('DataPlotly', self.dock_widgets)
68 | self.assertIs(dock_widget, self.dock_widgets['DataPlotly'])
69 |
70 | # checks it's not possible to add a new dock with DataPlotly as dock_id
71 | dock_widget2 = self.dock_manager.addNewDock(dock_title='NewDataPlotly',
72 | dock_id='DataPlotly')
73 | self.assertIs(dock_widget2, dock_widget)
74 |
75 | # checks we can not add new dock with same dock_id
76 | dock_params = {'dock_title': 'DataPlotly2', 'dock_id': 'DataPlotly2'}
77 | self.dock_manager.addNewDock(**dock_params)
78 | self.assertIn('DataPlotly2', self.dock_widgets)
79 | new_dock_widget = self.dock_manager.addNewDock(
80 | dock_title='DataPlotly2b', dock_id='DataPlotly2')
81 | self.assertFalse(new_dock_widget)
82 |
83 | def test_003_remove_dock(self):
84 | """
85 | Test removeDock
86 | """
87 | dock_id = 'dock_to_remove'
88 | self.dock_manager.addNewDock(dock_id=dock_id)
89 | self.assertIn(dock_id, self.dock_widgets)
90 | self.dock_manager.removeDock(dock_id)
91 | self.assertNotIn(dock_id, self.dock_widgets)
92 |
93 | def test_004_remove_docks(self):
94 | """
95 | Test removeDocks
96 | """
97 | docks = ['DataPlotly', 'DataPlotly2', 'DataPlotly3']
98 | for dock in docks:
99 | self.dock_manager.addNewDock(dock_id=dock)
100 | self.dock_manager.removeDocks()
101 | # do not remove DataPlotly main dock
102 | self.assertIn('DataPlotly', self.dock_widgets)
103 | self.assertEqual(len(self.dock_widgets), 1)
104 |
105 | def test_005_get_dock(self):
106 | """
107 | Test getDock
108 | """
109 | docks = ['DataPlotly', 'DataPlotly2', 'DataPlotly3']
110 | for dock in docks:
111 | self.dock_manager.addNewDock(dock_id=dock)
112 | dock = self.dock_manager.getDock('DataPloty4_wrong_id')
113 | self.assertIsNone(dock)
114 | dock = self.dock_manager.getDock('DataPlotly3')
115 | self.assertIsInstance(dock, DataPlotlyDock)
116 | self.assertIs(dock, self.dock_widgets['DataPlotly3'])
117 |
118 | def test_006_read_project(self):
119 | """
120 | Test read_project with or without StateDataPlotly
121 | """
122 | # project with StateDataPlotly dom
123 | project_path = os.path.join(os.path.dirname(
124 | __file__), 'test_project_with_state.qgs')
125 | document = read_project(project_path)
126 | ok = self.dock_manager.read_from_project(document)
127 | self.assertTrue(ok)
128 |
129 | # project without StateDataPlotly dom
130 | project_path = os.path.join(os.path.dirname(
131 | __file__), 'test_project_without_state.qgs')
132 | document = read_project(project_path)
133 | ko = self.dock_manager.read_from_project(document)
134 | self.assertFalse(ko)
135 |
136 | def test_007_utils_xml_function(self):
137 | """
138 | Test restore, restore_safe_str_xml, safe_str_xml
139 | """
140 | test_string = "My test"
141 | self.assertEqual(test_string, restore_safe_str_xml(
142 | safe_str_xml(test_string)))
143 | test_string = b'test'
144 | str_b64 = str(QByteArray(test_string).toBase64(), 'utf-8')
145 | self.assertEqual(test_string, restore(str_b64))
146 |
147 | def test_008_add_docks_from_project(self):
148 | """
149 | Test docks are added, custom project without StateDataPlotly node
150 | """
151 | project_path = os.path.join(os.path.dirname(
152 | __file__), 'test_project_without_state.qgs')
153 | document = read_project(project_path)
154 | self.dock_manager.addDocksFromProject(document)
155 | # all docks except main DataPlotlyDock are created
156 | dock_id = 'my-test'
157 | dock_title = "My Test"
158 | self.assertIn(dock_id, self.dock_widgets)
159 | # . is replace by space My.Test -> My Test
160 | self.assertEqual(self.dock_widgets[dock_id].title, dock_title)
161 |
162 | def test_009_add_new_dock_validator(self):
163 | """
164 | Test DockIdValidator
165 | """
166 | validator = DataPlotlyNewDockIdValidator(
167 | dock_widgets=self.dock_widgets)
168 | docks = ['DataPlotly', 'DataPlotly2', 'DataPlotly3']
169 | for dock in docks:
170 | self.dock_manager.addNewDock(dock_id=dock)
171 |
172 | # Dockid is valid
173 | state, _, _ = validator.validate('NewDockId', None)
174 | self.assertEqual(
175 | QValidator.Acceptable,
176 | state
177 | )
178 | # Dockid can not be empty / No underscore / Not already taken
179 | for bad_dock_id in ['', 'with_underscore', 'DataPlotly2']:
180 | state, _, _ = validator.validate(bad_dock_id, None)
181 | self.assertEqual(QValidator.Intermediate, state)
182 |
183 |
184 | if __name__ == "__main__":
185 | suite = unittest.defaultTestLoader.loadTestsFromTestCase(DataPlotlyDockManagerTest)
186 | runner = unittest.TextTestRunner(verbosity=2)
187 | runner.run(suite)
188 |
--------------------------------------------------------------------------------
/DataPlotly/test/test_guiutils.py:
--------------------------------------------------------------------------------
1 | """GUI Utils Test.
2 |
3 | .. note:: This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 2 of the License, or
6 | (at your option) any later version.
7 |
8 | """
9 |
10 | __author__ = '(C) 2018 by Nyall Dawson'
11 | __date__ = '20/04/2018'
12 | __copyright__ = 'Copyright 2018, North Road'
13 | # This will get replaced with a git SHA1 when you do a git archive
14 | __revision__ = '$Format:%H$'
15 |
16 | import unittest
17 | from DataPlotly.gui.gui_utils import GuiUtils
18 | from .utilities import get_qgis_app
19 |
20 | QGIS_APP = get_qgis_app()
21 |
22 |
23 | class GuiUtilsTest(unittest.TestCase):
24 | """Test GuiUtils work."""
25 |
26 | def testGetIcon(self):
27 | """
28 | Tests get_icon
29 | """
30 | self.assertFalse(
31 | GuiUtils.get_icon('dataplotly.svg').isNull())
32 | self.assertTrue(GuiUtils.get_icon('not_an_icon.svg').isNull())
33 |
34 | def testGetIconSvg(self):
35 | """
36 | Tests get_icon svg path
37 | """
38 | self.assertTrue(
39 | GuiUtils.get_icon_svg('dataplotly.svg'))
40 | self.assertIn('dataplotly.svg',
41 | GuiUtils.get_icon_svg('dataplotly.svg'))
42 | self.assertFalse(GuiUtils.get_icon_svg('not_an_icon.svg'))
43 |
44 |
45 | if __name__ == "__main__":
46 | suite = unittest.defaultTestLoader.loadTestsFromTestCase(GuiUtilsTest)
47 | runner = unittest.TextTestRunner(verbosity=2)
48 | runner.run(suite)
49 |
--------------------------------------------------------------------------------
/DataPlotly/test/test_init.py:
--------------------------------------------------------------------------------
1 | """Tests QGIS plugin init."""
2 |
3 | __author__ = 'Tim Sutton '
4 | __revision__ = '$Format:%H$'
5 | __date__ = '17/10/2010'
6 | __license__ = "GPL"
7 | __copyright__ = 'Copyright 2012, Australia Indonesia Facility for '
8 | __copyright__ += 'Disaster Reduction'
9 |
10 | import os
11 | import unittest
12 | import logging
13 | import configparser
14 |
15 | LOGGER = logging.getLogger('QGIS')
16 |
17 |
18 | class TestInit(unittest.TestCase):
19 | """Test that the plugin init is usable for QGIS.
20 |
21 | Based heavily on the validator class by Alessandro
22 | Passoti available here:
23 |
24 | http://github.com/qgis/qgis-django/blob/master/qgis-app/
25 | plugins/validator.py
26 |
27 | """
28 |
29 | def test_read_init(self):
30 | """Test that the plugin __init__ will validate on plugins.qgis.org."""
31 |
32 | # You should update this list according to the latest in
33 | # https://github.com/qgis/qgis-django/blob/master/qgis-app/
34 | # plugins/validator.py
35 |
36 | required_metadata = [
37 | 'name',
38 | 'description',
39 | 'version',
40 | 'qgisMinimumVersion',
41 | 'email',
42 | 'author']
43 |
44 | file_path = os.path.abspath(os.path.join(
45 | os.path.dirname(__file__), os.pardir,
46 | 'metadata.txt'))
47 | LOGGER.info(file_path)
48 | metadata = []
49 | parser = configparser.ConfigParser()
50 | parser.optionxform = str
51 | parser.read(file_path)
52 | message = 'Cannot find a section named "general" in %s' % file_path
53 | assert parser.has_section('general'), message
54 | metadata.extend(parser.items('general'))
55 |
56 | for expectation in required_metadata:
57 | message = ('Cannot find metadata "{}" in metadata source ({}).'.format(
58 | expectation, file_path))
59 |
60 | self.assertIn(expectation, dict(metadata), message)
61 |
62 |
63 | if __name__ == '__main__':
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/DataPlotly/test/test_layer.cpg:
--------------------------------------------------------------------------------
1 | UTF-8
--------------------------------------------------------------------------------
/DataPlotly/test/test_layer.dbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/test/test_layer.dbf
--------------------------------------------------------------------------------
/DataPlotly/test/test_layer.geojson:
--------------------------------------------------------------------------------
1 | {
2 | "type": "FeatureCollection",
3 | "name": "test_layer",
4 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
5 | "features": [
6 | { "type": "Feature", "properties": { "id": 27, "profo": "d", "so4": 1322, "ca": 249.28, "mg": 546.89, "profm": -16.504 }, "geometry": { "type": "Point", "coordinates": [ 10.795744304058722, 43.63420441224001 ] } },
7 | { "type": "Feature", "properties": { "id": 26, "profo": "d", "so4": 1055, "ca": 269.56, "mg": 480.43, "profm": -17.54 }, "geometry": { "type": "Point", "coordinates": [ 10.571051969329464, 43.379752033530515 ] } },
8 | { "type": "Feature", "properties": { "id": 25, "profo": "d", "so4": 632, "ca": 263.33, "mg": 518.19, "profm": -11.011 }, "geometry": { "type": "Point", "coordinates": [ 10.556071420648362, 43.460472554560745 ] } },
9 | { "type": "Feature", "properties": { "id": 24, "profo": "d", "so4": 1122, "ca": 270.45, "mg": 513.81, "profm": -15.074 }, "geometry": { "type": "Point", "coordinates": [ 10.549813079578296, 43.461304882390593 ] } },
10 | { "type": "Feature", "properties": { "id": 23, "profo": "d", "so4": 536, "ca": 53.6, "mg": 556.7, "profm": -13.408 }, "geometry": { "type": "Point", "coordinates": [ 10.623759088971619, 43.695950190510729 ] } },
11 | { "type": "Feature", "properties": { "id": 22, "profo": "d", "so4": 680, "ca": 81.57, "mg": 237.99, "profm": -10.191 }, "geometry": { "type": "Point", "coordinates": [ 10.884430431231918, 43.403842570916481 ] } },
12 | { "type": "Feature", "properties": { "id": 21, "profo": "d", "so4": 296, "ca": 83.0, "mg": 222.64, "profm": -11.809 }, "geometry": { "type": "Point", "coordinates": [ 10.754811441622472, 43.363470396557496 ] } },
13 | { "type": "Feature", "properties": { "id": 20, "profo": "d", "so4": 265, "ca": 62.91, "mg": 442.42, "profm": -14.202 }, "geometry": { "type": "Point", "coordinates": [ 10.586789564057552, 43.348636989224019 ] } },
14 | { "type": "Feature", "properties": { "id": 19, "profo": "prof", "so4": 788, "ca": 64.94, "mg": 244.03, "profm": -11.628 }, "geometry": { "type": "Point", "coordinates": [ 10.956367033291345, 43.386988748159688 ] } },
15 | { "type": "Feature", "properties": { "id": 18, "profo": "d", "so4": 791, "ca": 93.39, "mg": 563.42, "profm": -10.434 }, "geometry": { "type": "Point", "coordinates": [ 10.359406317962845, 43.675277001825656 ] } },
16 | { "type": "Feature", "properties": { "id": 17, "profo": "prof", "so4": 683, "ca": 143.38, "mg": 456.59, "profm": -19.291 }, "geometry": { "type": "Point", "coordinates": [ 10.575430392806258, 43.395006140010288 ] } },
17 | { "type": "Feature", "properties": { "id": 16, "profo": "prof", "so4": 457, "ca": 85.97, "mg": 460.99, "profm": -16.101 }, "geometry": { "type": "Point", "coordinates": [ 10.695380311244154, 43.784477487882988 ] } },
18 | { "type": "Feature", "properties": { "id": 15, "profo": "prof", "so4": 267, "ca": 147.26, "mg": 407.14, "profm": -14.022 }, "geometry": { "type": "Point", "coordinates": [ 10.670823816601224, 43.623648723727136 ] } },
19 | { "type": "Feature", "properties": { "id": 14, "profo": "prof", "so4": 513, "ca": 115.89, "mg": 272.89, "profm": -16.546 }, "geometry": { "type": "Point", "coordinates": [ 10.709919420883274, 43.250832320754803 ] } },
20 | { "type": "Feature", "properties": { "id": 13, "profo": "prof", "so4": 306, "ca": 149.01, "mg": 538.67, "profm": -15.825 }, "geometry": { "type": "Point", "coordinates": [ 10.348281111366386, 43.664350523807812 ] } },
21 | { "type": "Feature", "properties": { "id": 12, "profo": "prof", "so4": 627, "ca": 61.46, "mg": 62.17, "profm": -16.911 }, "geometry": { "type": "Point", "coordinates": [ 10.55229480012574, 43.683029492650505 ] } },
22 | { "type": "Feature", "properties": { "id": 11, "profo": "s", "so4": 100, "ca": 47.43, "mg": 128.51, "profm": -18.632 }, "geometry": { "type": "Point", "coordinates": [ 10.319380041202738, 43.730005294368354 ] } },
23 | { "type": "Feature", "properties": { "id": 10, "profo": "s", "so4": 84, "ca": 42.94, "mg": 94.87, "profm": -15.048 }, "geometry": { "type": "Point", "coordinates": [ 10.31589787706001, 43.747451729218653 ] } },
24 | { "type": "Feature", "properties": { "id": 9, "profo": "s", "so4": 98, "ca": 81.87, "mg": 72.31, "profm": -13.488 }, "geometry": { "type": "Point", "coordinates": [ 10.63025252959865, 43.490724296197371 ] } },
25 | { "type": "Feature", "properties": { "id": 8, "profo": "s", "so4": 88, "ca": 22.26, "mg": 86.03, "profm": -17.352 }, "geometry": { "type": "Point", "coordinates": [ 10.428647512874122, 43.661024534699273 ] } },
26 | { "type": "Feature", "properties": { "id": 7, "profo": "s", "so4": 267, "ca": 74.16, "mg": 85.26, "profm": -19.026 }, "geometry": { "type": "Point", "coordinates": [ 10.987186384487241, 43.385498650024061 ] } },
27 | { "type": "Feature", "properties": { "id": 6, "profo": "s", "so4": 329, "ca": 35.05, "mg": 81.11, "profm": -14.719 }, "geometry": { "type": "Point", "coordinates": [ 10.588879752350103, 43.294943020923668 ] } },
28 | { "type": "Feature", "properties": { "id": 5, "profo": "s", "so4": 319, "ca": 46.64, "mg": 131.59, "profm": -18.841 }, "geometry": { "type": "Point", "coordinates": [ 10.701604657200324, 43.263530250269049 ] } },
29 | { "type": "Feature", "properties": { "id": 4, "profo": "s", "so4": 137, "ca": 126.73, "mg": 95.36, "profm": -18.183 }, "geometry": { "type": "Point", "coordinates": [ 10.763194062178599, 43.415012407312666 ] } },
30 | { "type": "Feature", "properties": { "id": 3, "profo": "s", "so4": 350, "ca": 116.44, "mg": 112.88, "profm": -16.874 }, "geometry": { "type": "Point", "coordinates": [ 10.51819290071276, 43.675356405437903 ] } },
31 | { "type": "Feature", "properties": { "id": 2, "profo": "s", "so4": 151, "ca": 108.25, "mg": 80.55, "profm": -10.111 }, "geometry": { "type": "Point", "coordinates": [ 10.419553200464033, 43.655474054584019 ] } },
32 | { "type": "Feature", "properties": { "id": 1, "profo": "s", "so4": 203, "ca": 110.45, "mg": 78.34, "profm": -16.597 }, "geometry": { "type": "Point", "coordinates": [ 10.395171539231754, 43.727005546503719 ] } }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/DataPlotly/test/test_layer.prj:
--------------------------------------------------------------------------------
1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
--------------------------------------------------------------------------------
/DataPlotly/test/test_layer.qpj:
--------------------------------------------------------------------------------
1 | GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]
2 |
--------------------------------------------------------------------------------
/DataPlotly/test/test_layer.shp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/test/test_layer.shp
--------------------------------------------------------------------------------
/DataPlotly/test/test_layer.shx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/DataPlotly/test/test_layer.shx
--------------------------------------------------------------------------------
/DataPlotly/test/test_processing.py:
--------------------------------------------------------------------------------
1 | """Tests for processing algorithms."""
2 |
3 | import os
4 | import json
5 |
6 | import processing
7 |
8 | from qgis.core import QgsApplication, QgsVectorLayer
9 | from qgis.testing import unittest
10 | from qgis.PyQt.QtGui import QColor
11 |
12 | from DataPlotly.processing.dataplotly_provider import DataPlotlyProvider
13 |
14 | __copyright__ = 'Copyright 2022, Faunalia'
15 | __license__ = 'GPL version 3'
16 | __email__ = 'info@faunalia.eu'
17 |
18 |
19 | class TestProcessing(unittest.TestCase):
20 | """Tests for processing algorithms."""
21 |
22 | def setUp(self) -> None:
23 | """Set up the processing tests."""
24 | if not QgsApplication.processingRegistry().providers():
25 | self.provider = DataPlotlyProvider(plugin_version='2.3')
26 | QgsApplication.processingRegistry().addProvider(self.provider)
27 | self.maxDiff = None
28 |
29 | def test_scatterplot_figure(self):
30 | """Test for the Processing scatterplot"""
31 |
32 | layer_path = os.path.join(
33 | os.path.dirname(__file__), 'test_layer.shp')
34 |
35 | vl = QgsVectorLayer(layer_path, 'test_layer', 'ogr')
36 |
37 | # plot_path = os.path.join(
38 | # os.path.dirname(__file__), 'scatterplot.json')
39 | # with open(plot_path, 'r') as f:
40 | # template_dict = json.load(f)
41 |
42 | plot_param = {
43 | 'INPUT': vl,
44 | 'XEXPRESSION': '"so4"',
45 | 'YEXPRESSION': '"ca"',
46 | 'SIZE': 10,
47 | 'COLOR': QColor(142, 186, 217),
48 | 'FACET_COL': '',
49 | 'FACET_ROW': '',
50 | 'OFFLINE': False,
51 | 'OUTPUT_HTML_FILE': 'TEMPORARY_OUTPUT',
52 | 'OUTPUT_JSON_FILE': 'TEMPORARY_OUTPUT'
53 | }
54 |
55 | result = processing.run("DataPlotly:dataplotly_scatterplot", plot_param)
56 |
57 | with open(result['OUTPUT_JSON_FILE'], encoding='utf8') as f:
58 | result_dict = json.load(f)
59 |
60 | self.assertListEqual(
61 | result_dict['data'][0]['x'],
62 | [98, 88, 267, 329, 319, 137, 350, 151, 203]
63 | )
64 | self.assertListEqual(
65 | result_dict['data'][0]['y'],
66 | [81.87, 22.26, 74.16, 35.05, 46.64, 126.73, 116.44, 108.25, 110.45]
67 | )
68 |
69 |
70 | if __name__ == '__main__':
71 | unittest.main()
72 |
--------------------------------------------------------------------------------
/DataPlotly/test/test_qgis_environment.py:
--------------------------------------------------------------------------------
1 | """Tests for QGIS functionality.
2 |
3 | .. note:: This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 2 of the License, or
6 | (at your option) any later version.
7 |
8 | """
9 | __author__ = 'tim@linfiniti.com'
10 | __date__ = '20/01/2011'
11 | __copyright__ = ('Copyright 2012, Australia Indonesia Facility for '
12 | 'Disaster Reduction')
13 |
14 | import unittest
15 | from qgis.core import (QgsProviderRegistry,
16 | QgsCoordinateReferenceSystem)
17 | from .utilities import get_qgis_app
18 |
19 |
20 | QGIS_APP = get_qgis_app()
21 |
22 |
23 | class QGISTest(unittest.TestCase):
24 | """Test the QGIS Environment"""
25 |
26 | def test_qgis_environment(self):
27 | """QGIS environment has the expected providers"""
28 |
29 | r = QgsProviderRegistry.instance()
30 | self.assertIn('gdal', r.providerList())
31 | self.assertIn('ogr', r.providerList())
32 | self.assertIn('postgres', r.providerList())
33 |
34 | def test_projection(self):
35 | """Test that QGIS properly parses a wkt string.
36 | """
37 | crs = QgsCoordinateReferenceSystem()
38 | wkt = (
39 | 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",'
40 | 'SPHEROID["WGS_1984",6378137.0,298.257223563]],'
41 | 'PRIMEM["Greenwich",0.0],UNIT["Degree",'
42 | '0.0174532925199433]]')
43 | crs.createFromWkt(wkt)
44 | auth_id = crs.authid()
45 | expected_auth_id = 'EPSG:4326'
46 | self.assertEqual(auth_id, expected_auth_id)
47 |
48 |
49 | if __name__ == '__main__':
50 | unittest.main()
51 |
--------------------------------------------------------------------------------
/DataPlotly/test/test_resources.py:
--------------------------------------------------------------------------------
1 | """Resources test.
2 |
3 | .. note:: This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 2 of the License, or
6 | (at your option) any later version.
7 |
8 | """
9 |
10 | __author__ = 'matteo.ghetta@gmail.com'
11 | __date__ = '2017-03-05'
12 | __copyright__ = 'Copyright 2017, matteo ghetta'
13 |
14 | import unittest
15 |
16 | from qgis.PyQt.QtGui import QIcon
17 |
18 |
19 | class DataPlotlyResourcesTest(unittest.TestCase):
20 | """Test resources work."""
21 |
22 | def setUp(self):
23 | """Runs before each test."""
24 | pass
25 |
26 | def tearDown(self):
27 | """Runs after each test."""
28 | pass
29 |
30 | def test_icon_png(self):
31 | """Test we can load resources."""
32 | path = ':/plugins/DataPlotly/icon.png'
33 | icon = QIcon(path)
34 | self.assertFalse(icon.isNull())
35 |
36 |
37 | if __name__ == "__main__":
38 | suite = unittest.defaultTestLoader.loadTestsFromTestCase(DataPlotlyResourcesTest)
39 | runner = unittest.TextTestRunner(verbosity=2)
40 | runner.run(suite)
41 |
--------------------------------------------------------------------------------
/DataPlotly/test/utilities.py:
--------------------------------------------------------------------------------
1 | """Common functionality used by regression tests."""
2 |
3 | import sys
4 | import logging
5 | import os
6 | import atexit
7 | from qgis.core import QgsApplication
8 | from qgis.gui import QgsMapCanvas
9 | from qgis.PyQt.QtCore import QSize
10 | from qgis.PyQt.QtWidgets import QWidget
11 | from qgis.utils import iface
12 | from .qgis_interface import QgisInterface
13 |
14 | LOGGER = logging.getLogger('QGIS')
15 | QGIS_APP = None # Static variable used to hold hand to running QGIS app
16 | CANVAS = None
17 | PARENT = None
18 | IFACE = None
19 |
20 |
21 | def get_qgis_app(cleanup=True):
22 | """ Start one QGIS application to test against.
23 |
24 | :returns: Handle to QGIS app, canvas, iface and parent. If there are any
25 | errors the tuple members will be returned as None.
26 | :rtype: (QgsApplication, CANVAS, IFACE, PARENT)
27 |
28 | If QGIS is already running the handle to that app will be returned.
29 | """
30 |
31 | global QGIS_APP, PARENT, IFACE, CANVAS # pylint: disable=W0603
32 |
33 | if iface:
34 | QGIS_APP = QgsApplication
35 | CANVAS = iface.mapCanvas()
36 | PARENT = iface.mainWindow()
37 | IFACE = iface
38 | return QGIS_APP, CANVAS, IFACE, PARENT
39 |
40 | global QGISAPP # pylint: disable=global-variable-undefined
41 |
42 | try:
43 | QGISAPP # pylint: disable=used-before-assignment
44 | except NameError:
45 | myGuiFlag = True # All test will run qgis in gui mode
46 |
47 | # In python3 we need to convert to a bytes object (or should
48 | # QgsApplication accept a QString instead of const char* ?)
49 | try:
50 | argvb = list(map(os.fsencode, sys.argv))
51 | except AttributeError:
52 | argvb = sys.argv
53 |
54 | # Note: QGIS_PREFIX_PATH is evaluated in QgsApplication -
55 | # no need to mess with it here.
56 | QGISAPP = QgsApplication(argvb, myGuiFlag)
57 |
58 | QGISAPP.initQgis()
59 | s = QGISAPP.showSettings()
60 | LOGGER.debug(s)
61 |
62 | def debug_log_message(message, tag, level):
63 | """
64 | Prints a debug message to a log
65 | :param message: message to print
66 | :param tag: log tag
67 | :param level: log message level (severity)
68 | :return:
69 | """
70 | print(f'{tag}({level}): {message}')
71 |
72 | QgsApplication.instance().messageLog().messageReceived.connect(
73 | debug_log_message)
74 |
75 | if cleanup:
76 | @atexit.register
77 | def exitQgis(): # pylint: disable=unused-variable
78 | """
79 | Gracefully closes the QgsApplication instance
80 | """
81 | try:
82 | QGISAPP.exitQgis() # pylint: disable=used-before-assignment
83 | QGISAPP = None # pylint: disable=redefined-outer-name
84 | except NameError:
85 | pass
86 |
87 | if PARENT is None:
88 | # noinspection PyPep8Naming
89 | PARENT = QWidget()
90 |
91 | if CANVAS is None:
92 | # noinspection PyPep8Naming
93 | CANVAS = QgsMapCanvas(PARENT)
94 | CANVAS.resize(QSize(400, 400))
95 |
96 | if IFACE is None:
97 | # QgisInterface is a stub implementation of the QGIS plugin interface
98 | # noinspection PyPep8Naming
99 | IFACE = QgisInterface(CANVAS)
100 |
101 | return QGISAPP, CANVAS, IFACE, PARENT
102 |
--------------------------------------------------------------------------------
/DataPlotly/test_suite.py:
--------------------------------------------------------------------------------
1 | """
2 | Test Suite.
3 |
4 | .. note:: This program is free software; you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation; either version 2 of the License, or
7 | (at your option) any later version.
8 |
9 | """
10 |
11 | import sys
12 | import os
13 | import unittest
14 | import tempfile
15 | from osgeo import gdal
16 | import qgis # pylint: disable=unused-import
17 |
18 | try:
19 | from pip import main as pipmain
20 | except ImportError:
21 | from pip._internal import main as pipmain
22 |
23 | try:
24 | import coverage
25 | except ImportError:
26 | pipmain(['install', 'coverage'])
27 | import coverage
28 |
29 | __author__ = 'Alessandro Pasotti'
30 | __revision__ = '$Format:%H$'
31 | __date__ = '30/04/2018'
32 | __copyright__ = (
33 | 'Copyright 2018, North Road')
34 |
35 |
36 | def _run_tests(test_suite, package_name, with_coverage=False):
37 | """Core function to test a test suite."""
38 | count = test_suite.countTestCases()
39 | print('########')
40 | print('{} tests has been discovered in {}'.format(count, package_name))
41 | print('Python GDAL : %s' % gdal.VersionInfo('VERSION_NUM'))
42 | print('########')
43 | if with_coverage:
44 | cov = coverage.Coverage(
45 | source=['/DataPlotly'],
46 | omit=['*/test/*'],
47 | )
48 | cov.start()
49 |
50 | unittest.TextTestRunner(verbosity=3, stream=sys.stdout).run(test_suite)
51 |
52 | if with_coverage:
53 | cov.stop()
54 | cov.save()
55 | with tempfile.NamedTemporaryFile(delete=False) as report:
56 | cov.report(file=report)
57 | # Produce HTML reports in the `htmlcov` folder and open index.html
58 | # cov.html_report()
59 | with open(report.name, encoding='utf8') as fin:
60 | print(fin.read())
61 |
62 |
63 | def test_package(package='DataPlotly'):
64 | """Test package.
65 | This function is called by travis without arguments.
66 |
67 | :param package: The package to test.
68 | :type package: str
69 | """
70 | test_loader = unittest.defaultTestLoader
71 | try:
72 | test_suite = test_loader.discover(package)
73 | except ImportError:
74 | test_suite = unittest.TestSuite()
75 | _run_tests(test_suite, package)
76 |
77 |
78 | def test_environment():
79 | """Test package with an environment variable."""
80 | package = os.environ.get('TESTING_PACKAGE', 'DataPlotly')
81 | test_loader = unittest.defaultTestLoader
82 | test_suite = test_loader.discover(package)
83 | _run_tests(test_suite, package)
84 |
85 |
86 | if __name__ == '__main__':
87 | test_package()
88 |
--------------------------------------------------------------------------------
/DataPlotly/ui/add_dock_dlg.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | DialogAddNewDock
4 |
5 |
6 |
7 | 0
8 | 0
9 | 586
10 | 187
11 |
12 |
13 |
14 |
15 | 586
16 | 0
17 |
18 |
19 |
20 | DataPlotly
21 |
22 |
23 | -
24 |
25 |
26 | Add a new dock
27 |
28 |
29 |
-
30 |
31 |
-
32 |
33 |
34 | Dock Title
35 |
36 |
37 |
38 | -
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | -
48 |
49 |
50 | QgsCollapsibleGroupBoxBasic::title, QgsCollapsibleGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; margin-left: 20px; margin-right: 10px; left: 0px; top: 2px;}QgsCollapsibleGroupBoxBasic::title, QgsCollapsibleGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; margin-left: 20px; margin-right: 5px; left: 0px; top: 1px;} QgsCollapsibleGroupBoxBasic { border: none; }
51 |
52 |
53 | Customize Dock Id
54 |
55 |
56 | true
57 |
58 |
59 | false
60 |
61 |
62 | true
63 |
64 |
65 |
-
66 |
67 |
-
68 |
69 |
70 | Dock id must be unique
71 |
72 |
73 | Dock Id
74 |
75 |
76 |
77 | -
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | -
87 |
88 |
89 |
90 | true
91 |
92 |
93 |
94 | Qt::LeftToRight
95 |
96 |
97 | *{ margin-right:5px;}
98 |
99 |
100 | Dock id error information
101 |
102 |
103 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
104 |
105 |
106 | 0
107 |
108 |
109 | Qt::NoTextInteraction
110 |
111 |
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | Qt::Vertical
120 |
121 |
122 |
123 | 20
124 | 40
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | -
133 |
134 |
135 | Qt::Horizontal
136 |
137 |
138 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | QgsCollapsibleGroupBox
147 | QGroupBox
148 |
149 | 1
150 |
151 |
152 | QgsFilterLineEdit
153 | QLineEdit
154 |
155 |
156 |
157 |
158 |
159 |
160 | buttonBox
161 | accepted()
162 | DialogAddNewDock
163 | accept()
164 |
165 |
166 | 227
167 | 250
168 |
169 |
170 | 157
171 | 186
172 |
173 |
174 |
175 |
176 | buttonBox
177 | rejected()
178 | DialogAddNewDock
179 | reject()
180 |
181 |
182 | 295
183 | 250
184 |
185 |
186 | 286
187 | 186
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/DataPlotly/ui/remove_dock_dlg.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | DialogAddNewDock
4 |
5 |
6 |
7 | 0
8 | 0
9 | 586
10 | 187
11 |
12 |
13 |
14 |
15 | 586
16 | 0
17 |
18 |
19 |
20 | DataPlotly
21 |
22 |
23 | -
24 |
25 |
26 | Remove a dock
27 |
28 |
29 |
-
30 |
31 |
-
32 |
33 |
34 |
35 | 0
36 | 0
37 |
38 |
39 |
40 |
41 | 100
42 | 16777215
43 |
44 |
45 |
46 | Dock Id
47 |
48 |
49 |
50 | -
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | -
60 |
61 |
62 | Qt::Vertical
63 |
64 |
65 |
66 | 20
67 | 40
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | -
76 |
77 |
78 | Qt::Horizontal
79 |
80 |
81 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | buttonBox
91 | accepted()
92 | DialogAddNewDock
93 | accept()
94 |
95 |
96 | 227
97 | 250
98 |
99 |
100 | 157
101 | 186
102 |
103 |
104 |
105 |
106 | buttonBox
107 | rejected()
108 | DialogAddNewDock
109 | reject()
110 |
111 |
112 | 295
113 | 250
114 |
115 |
116 | 286
117 | 186
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | #/***************************************************************************
2 | # DataPlotly
3 | #
4 | # D3 Plots for QGIS
5 | # -------------------
6 | # begin : 2017-03-05
7 | # git sha : $Format:%H$
8 | # copyright : (C) 2017 by matteo ghetta
9 | # email : matteo.ghetta@gmail.com
10 | # ***************************************************************************/
11 | #
12 | #/***************************************************************************
13 | # * *
14 | # * This program is free software; you can redistribute it and/or modify *
15 | # * it under the terms of the GNU General Public License as published by *
16 | # * the Free Software Foundation; either version 2 of the License, or *
17 | # * (at your option) any later version. *
18 | # * *
19 | # ***************************************************************************/
20 |
21 | #################################################
22 | # Edit the following to match your sources lists
23 | #################################################
24 |
25 |
26 | PEP8EXCLUDE=pydev,conf.py,third_party,ui,.venv,venv,
27 |
28 |
29 | #################################################
30 | # Normally you would not need to edit below here
31 | #################################################
32 |
33 | test:
34 | @echo
35 | @echo "----------------------"
36 | @echo "Regression Test Suite"
37 | @echo "----------------------"
38 |
39 | @# Preceding dash means that make will continue in case of errors
40 | @-export PYTHONPATH=`pwd`:$(PYTHONPATH); \
41 | export QGIS_DEBUG=0; \
42 | export QGIS_LOG_FILE=/dev/null; \
43 | nosetests3 -v -s --with-id --with-coverage --cover-package=. DataPlotly.test \
44 | 3>&1 1>&2 2>&3 3>&- || true
45 | @echo "----------------------"
46 | @echo "If you get a 'no module named qgis.core error, try sourcing"
47 | @echo "the helper script we have provided first then run make test."
48 | @echo "e.g. source run-env-linux.sh ; make test"
49 | @echo "----------------------"
50 |
51 | pylint:
52 | @echo
53 | @echo "-----------------"
54 | @echo "Pylint violations"
55 | @echo "-----------------"
56 | @pylint --reports=n --rcfile=pylintrc DataPlotly
57 | @echo
58 | @echo "----------------------"
59 | @echo "If you get a 'no module named qgis.core' error, try sourcing"
60 | @echo "the helper script we have provided first then run make pylint."
61 | @echo "e.g. source run-env-linux.sh ; make pylint"
62 | @echo "----------------------"
63 |
64 |
65 | # Run pep8/pycodestyle style checking
66 | #http://pypi.python.org/pypi/pep8
67 | pycodestyle:
68 | @echo
69 | @echo "-----------"
70 | @echo "pycodestyle PEP8 issues"
71 | @echo "-----------"
72 | @pycodestyle --repeat --ignore=E203,E121,E122,E123,E124,E125,E126,E127,E128,E402,E501,W504 --exclude $(PEP8EXCLUDE) .
73 | @echo "-----------"
74 | @echo "Ignored in PEP8 check:"
75 | @echo $(PEP8EXCLUDE)
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DataPlotly
2 |
3 | [](https://plugins.qgis.org/plugins/DataPlotly/)
4 | [](https://github.com/ghtmtt/DataPlotly/actions/workflows/test_plugin.yaml)
5 | [](https://github.com/ghtmtt/DataPlotly/actions/workflows/transifex.yml)
6 |
7 | **Documentation: https://dataplotly-docs.readthedocs.io/en/latest/intro.html**
8 |
9 | The DataPlotly plugin allows creation of [D3](https://d3js.org/)-like
10 | interactive plots directly within QGIS, thanks to the [Plotly](https://plot.ly/python/)
11 | library and its Python API.
12 |
13 | DataPlotly makes plot creation and customization easy for every needs.
14 |
15 | Besides all the plot and customization available, the plot is **linked** with
16 | the QGIS map canvas:
17 |
18 | 
19 |
20 | 
21 |
22 | ## Usage
23 | DataPlotly works **only with QGIS 3**. No additional libraries are necessary.
24 |
25 | ## Gallery
26 |
27 | ### Single Plot
28 |
29 | Some examples of single plot type with some options. The list is far away to show all the possibilities.
30 |
31 | #### Scatter Plot
32 | 
33 |
34 | #### Box Plot with statistics
35 | 
36 |
37 | #### Violin Plot
38 | 
39 |
40 | #### Stacked Bar Plot
41 | 
42 |
43 | #### Probability Histogram
44 | 
45 |
46 | #### Pie Chart
47 | 
48 |
49 | #### 2D Histogram
50 | 
51 |
52 | #### Polar Plot
53 | 
54 |
55 | #### Ternary Plot
56 | 
57 |
58 | #### Contour Plot
59 | 
60 |
61 | ### Multi Plots
62 | DataPloty allows creation of different plot type in the same *plot canvas* but also allows the chance to separate each plot in a different canvas.
63 |
64 |
67 |
68 |
69 | ## Overlapped Plots
70 | 
71 |
72 | ## Subplots in row
73 | 
74 |
75 | ## Subplots in column
76 | 
77 |
--------------------------------------------------------------------------------
/REQUIREMENTS_TESTING.txt:
--------------------------------------------------------------------------------
1 | # For tests execution:
2 | deepdiff
3 | mock
4 | flake8
5 | pep257
6 | plotly
7 | pylint
8 | pandas
--------------------------------------------------------------------------------
/img/plot_2dhistogram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_2dhistogram.png
--------------------------------------------------------------------------------
/img/plot_bar_stack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_bar_stack.png
--------------------------------------------------------------------------------
/img/plot_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_box.png
--------------------------------------------------------------------------------
/img/plot_contour.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_contour.png
--------------------------------------------------------------------------------
/img/plot_histogram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_histogram.png
--------------------------------------------------------------------------------
/img/plot_histogram_violin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_histogram_violin.png
--------------------------------------------------------------------------------
/img/plot_interaction_box.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_interaction_box.gif
--------------------------------------------------------------------------------
/img/plot_interaction_scatter.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_interaction_scatter.gif
--------------------------------------------------------------------------------
/img/plot_interaction_scatter_box.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_interaction_scatter_box.gif
--------------------------------------------------------------------------------
/img/plot_pie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_pie.png
--------------------------------------------------------------------------------
/img/plot_polar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_polar.png
--------------------------------------------------------------------------------
/img/plot_scatter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_scatter.png
--------------------------------------------------------------------------------
/img/plot_scatter_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_scatter_bar.png
--------------------------------------------------------------------------------
/img/plot_scatter_histogram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_scatter_histogram.png
--------------------------------------------------------------------------------
/img/plot_ternary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_ternary.png
--------------------------------------------------------------------------------
/img/plot_violin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghtmtt/DataPlotly/f57b842703ebb259091b4490a9821518a14b01b9/img/plot_violin.png
--------------------------------------------------------------------------------
/publiccode.yml:
--------------------------------------------------------------------------------
1 | # This repository adheres to the publiccode.yml standard by including this
2 | # metadata file that makes public software easily discoverable.
3 | # More info at https://github.com/italia/publiccode.yml
4 |
5 | publiccodeYmlVersion: '0.2'
6 | name: DataPlotly
7 | applicationSuite: QGIS
8 | url: 'https://github.com/ghtmtt/DataPlotly'
9 | landingURL: 'https://www.faunalia.eu/it/dev/dataplotly'
10 | softwareVersion: '3.7'
11 | releaseDate: '2020-05-20'
12 | logo: DataPlotly/icons/dataplotly.svg
13 | platforms:
14 | - linux
15 | - windows
16 | - mac
17 | categories:
18 | - geographic-information-systems
19 | - data-analytics
20 | - data-visualization
21 | developmentStatus: stable
22 | softwareType: addon
23 | dependsOn:
24 | open:
25 | - name: QGIS
26 | versionMin: '3.4'
27 | versionMax: '3.99'
28 | optional: no
29 | maintenance:
30 | type: internal
31 | contacts:
32 | - name: Matteo Ghetta
33 | email: matteo.ghetta@faunalia.eu
34 | affiliation: Faunalia
35 | legal:
36 | license: GPL-2.0
37 | mainCopyrightOwner: Matteo Ghetta
38 | repoOwner: Faunalia
39 | intendedAudience:
40 | scope:
41 | - science-and-technology
42 | - research
43 | - infrastructures
44 | - environment
45 | localisation:
46 | localisationReady: yes
47 | availableLanguages:
48 | - en
49 | - it
50 | - sv
51 | - nl
52 | - es
53 | description:
54 | en:
55 | localisedName: DataPlotly
56 | genericName: DataPlotly
57 | shortDescription: >
58 | DataPlotly is a QGIS plugin that allows the user to create interactive D3
59 | like charts based on vector data.
60 | longDescription: >
61 | With the DataPlotly plugin for QGIS the user can create fully customized
62 | and fully interactive D3 like charts withing QGIS itself. Currently 10
63 | different charts are available (scatter plot, pie chart, bar plot, polar
64 | plot, boxplot, contour plot, density chart, histogram, polar chart, violin
65 | plot, ternary plot). The plugin is also available in the Layout and Report
66 | composer in order to enrich the cartographic export as a dataviz like
67 | tool. Within
68 | the Layout and Report composer it is also available in Atlas mode.
69 | documentation: 'https://dataplotly-docs.readthedocs.io/en/latest/'
70 | features:
71 | - qgis
72 | - plotly
73 | - dataviz
74 | - python
75 | - charts
76 | screenshots:
77 | - img/plot_scatter.png
78 | it:
79 | localisedName: DataPlotly
80 | genericName: DataPlotly
81 | shortDescription: >
82 | DataPlotly è un plugin di QGIS che permette di creare grafici interattivi
83 | in stile D3 di dati vettoriali.
84 | longDescription: >
85 | Con il plugin DatapPlotly per QGIS l'utente può creare grafici
86 | completamente personalizzabili e interettivi all'interno di QGIS. Al
87 | momento sono disponibli 10 grafici diversi (diagramma a dispersione,
88 | grafico a torta, diagramma a barre, diagramma polare, grafici a scatola e
89 | baffi, grafici di densità, istogramma, grafico polare, grafico a violino,
90 | grafico ternario) con molte personalizzazioni disponibili. Il plugin è
91 | abilitato anche nel Compositore di Stampe, ovvero è in grado di arricchire
92 | l'esportazione cartografica con grafici, anche bastati su atlante.
93 | documentation: 'https://dataplotly-docs.readthedocs.io/en/latest/'
94 | features:
95 | - qgis
96 | - plotly
97 | - dataviz
98 | - python
99 | - charts
100 |
--------------------------------------------------------------------------------
/pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 |
3 | # Specify a configuration file.
4 | #rcfile=
5 |
6 | # Python code to execute, usually for sys.path manipulation such as
7 | # pygtk.require().
8 | #init-hook=
9 |
10 | # Profiled execution.
11 | # profile=no
12 |
13 | # Add files or directories to the blacklist. They should be base names, not
14 | # paths.
15 | ignore=CVS
16 |
17 | # Pickle collected data for later comparisons.
18 | persistent=yes
19 |
20 | # List of plugins (as comma separated values of python modules names) to load,
21 | # usually to register additional checkers.
22 | load-plugins=pylint.extensions.no_self_use
23 |
24 |
25 | [MESSAGES CONTROL]
26 |
27 | # Enable the message, report, category or checker with the given id(s). You can
28 | # either give multiple identifier separated by comma (,) or put this option
29 | # multiple time. See also the "--disable" option for examples.
30 | #enable=
31 |
32 | # Disable the message, report, category or checker with the given id(s). You
33 | # can either give multiple identifiers separated by comma (,) or put this
34 | # option multiple times (only on the command line, not in the configuration
35 | # file where it should appear only once).You can also use "--disable=all" to
36 | # disable everything first and then reenable specific checks. For example, if
37 | # you want to run only the similarities checker, you can use "--disable=all
38 | # --enable=similarities". If you want to run only the classes checker, but have
39 | # no Warning level messages displayed, use"--disable=all --enable=classes
40 | # --disable=W"
41 | # see http://stackoverflow.com/questions/21487025/pylint-locally-defined-disables-still-give-warnings-how-to-suppress-them
42 | disable=locally-disabled,C0103,C0301,E0611,W0511,W0107,R0801,import-error,
43 | # Wait for QGIS 3.18 minimum version for f-strings
44 | C0209,
45 |
46 |
47 | [REPORTS]
48 |
49 | # Set the output format. Available formats are text, parseable, colorized, msvs
50 | # (visual studio) and html. You can also give a reporter class, eg
51 | # mypackage.mymodule.MyReporterClass.
52 | output-format=text
53 |
54 | # Put messages in a separate file for each module / package specified on the
55 | # command line instead of printing them on stdout. Reports (if any) will be
56 | # written in a file name "pylint_global.[txt|html]".
57 | # files-output=no
58 |
59 | # Tells whether to display a full report or only the messages
60 | reports=yes
61 |
62 | # Python expression which should return a note less than 10 (10 is the highest
63 | # note). You have access to the variables errors warning, statement which
64 | # respectively contain the number of errors / warnings messages and the total
65 | # number of statements analyzed. This is used by the global evaluation report
66 | # (RP0004).
67 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
68 |
69 | # Add a comment according to your evaluation note. This is used by the global
70 | # evaluation report (RP0004).
71 | # comment=no
72 |
73 | # Template used to display messages. This is a python new-style format string
74 | # used to format the message information. See doc for all details
75 | #msg-template=
76 |
77 |
78 | [BASIC]
79 |
80 | # Required attributes for module, separated by a comma
81 | # required-attributes=
82 |
83 | # List of builtins function names that should not be used, separated by a comma
84 | # bad-functions=map,filter,apply,input
85 |
86 | # Regular expression which should only match correct module names
87 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
88 |
89 | # Regular expression which should only match correct module level names
90 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
91 |
92 | # Regular expression which should only match correct class names
93 | class-rgx=[A-Z_][a-zA-Z0-9]+$
94 |
95 | # Regular expression which should only match correct function names
96 | function-rgx=[a-z_][a-z0-9_]{2,30}$
97 |
98 | # Regular expression which should only match correct method names
99 | method-rgx=[a-z_][a-z0-9_]{2,30}$
100 |
101 | # Regular expression which should only match correct instance attribute names
102 | attr-rgx=[a-z_][a-z0-9_]{2,30}$
103 |
104 | # Regular expression which should only match correct argument names
105 | argument-rgx=[a-z_][a-z0-9_]{2,30}$
106 |
107 | # Regular expression which should only match correct variable names
108 | variable-rgx=[a-z_][a-z0-9_]{2,30}$
109 |
110 | # Regular expression which should only match correct attribute names in class
111 | # bodies
112 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
113 |
114 | # Regular expression which should only match correct list comprehension /
115 | # generator expression variable names
116 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
117 |
118 | # Good variable names which should always be accepted, separated by a comma
119 | good-names=i,j,k,ex,Run,_
120 |
121 | # Bad variable names which should always be refused, separated by a comma
122 | bad-names=foo,bar,baz,toto,tutu,tata
123 |
124 | # Regular expression which should only match function or class names that do
125 | # not require a docstring.
126 | no-docstring-rgx=__.*__
127 |
128 | # Minimum line length for functions/classes that require docstrings, shorter
129 | # ones are exempt.
130 | docstring-min-length=-1
131 |
132 |
133 | [MISCELLANEOUS]
134 |
135 | # List of note tags to take in consideration, separated by a comma.
136 | notes=FIXME,XXX,TODO
137 |
138 |
139 | [TYPECHECK]
140 |
141 | # Tells whether missing members accessed in mixin class should be ignored. A
142 | # mixin class is detected if its name ends with "mixin" (case insensitive).
143 | ignore-mixin-members=yes
144 |
145 | # List of classes names for which member attributes should not be checked
146 | # (useful for classes with attributes dynamically set).
147 | ignored-classes=SQLObject
148 |
149 | # When zope mode is activated, add a predefined set of Zope acquired attributes
150 | # to generated-members.
151 | # zope=no
152 |
153 | # List of members which are set dynamically and missed by pylint inference
154 | # system, and so shouldn't trigger E0201 when accessed. Python regular
155 | # expressions are accepted.
156 | generated-members=REQUEST,acl_users,aq_parent
157 |
158 |
159 | [VARIABLES]
160 |
161 | # Tells whether we should check for unused import in __init__ files.
162 | init-import=no
163 |
164 | # A regular expression matching the beginning of the name of dummy variables
165 | # (i.e. not used).
166 | dummy-variables-rgx=_$|dummy
167 |
168 | # List of additional names supposed to be defined in builtins. Remember that
169 | # you should avoid to define new builtins when possible.
170 | additional-builtins=
171 |
172 |
173 | [FORMAT]
174 |
175 | # Maximum number of characters on a single line.
176 | max-line-length=80
177 |
178 | # Regexp for a line that is allowed to be longer than the limit.
179 | ignore-long-lines=^\s*(# )??$
180 |
181 | # Allow the body of an if to be on the same line as the test if there is no
182 | # else.
183 | single-line-if-stmt=no
184 |
185 | # List of optional constructs for which whitespace checking is disabled
186 | # no-space-check=trailing-comma,dict-separator
187 |
188 | # Maximum number of lines in a module
189 | max-module-lines=1000
190 |
191 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
192 | # tab).
193 | indent-string=' '
194 |
195 |
196 | [SIMILARITIES]
197 |
198 | # Minimum lines number of a similarity.
199 | min-similarity-lines=4
200 |
201 | # Ignore comments when computing similarities.
202 | ignore-comments=yes
203 |
204 | # Ignore docstrings when computing similarities.
205 | ignore-docstrings=yes
206 |
207 | # Ignore imports when computing similarities.
208 | ignore-imports=no
209 |
210 |
211 | [IMPORTS]
212 |
213 | # Deprecated modules which should not be used, separated by a comma
214 | deprecated-modules=regsub,TERMIOS,Bastion,rexec
215 |
216 | # Create a graph of every (i.e. internal and external) dependencies in the
217 | # given file (report RP0402 must not be disabled)
218 | import-graph=
219 |
220 | # Create a graph of external dependencies in the given file (report RP0402 must
221 | # not be disabled)
222 | ext-import-graph=
223 |
224 | # Create a graph of internal dependencies in the given file (report RP0402 must
225 | # not be disabled)
226 | int-import-graph=
227 |
228 |
229 | [DESIGN]
230 |
231 | # Maximum number of arguments for function / method
232 | max-args=5
233 |
234 | # Argument names that match this expression will be ignored. Default to name
235 | # with leading underscore
236 | ignored-argument-names=_.*
237 |
238 | # Maximum number of locals for function / method body
239 | max-locals=15
240 |
241 | # Maximum number of return / yield for function / method body
242 | max-returns=6
243 |
244 | # Maximum number of branch for function / method body
245 | max-branches=12
246 |
247 | # Maximum number of statements in function / method body
248 | max-statements=50
249 |
250 | # Maximum number of parents for a class (see R0901).
251 | max-parents=7
252 |
253 | # Maximum number of attributes for a class (see R0902).
254 | max-attributes=7
255 |
256 | # Minimum number of public methods for a class (see R0903).
257 | min-public-methods=2
258 |
259 | # Maximum number of public methods for a class (see R0904).
260 | max-public-methods=20
261 |
262 |
263 | [CLASSES]
264 |
265 | # List of interface methods to ignore, separated by a comma. This is used for
266 | # instance to not check methods defines in Zope's Interface base class.
267 | # ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
268 |
269 | # List of method names used to declare (i.e. assign) instance attributes.
270 | defining-attr-methods=__init__,__new__,setUp
271 |
272 | # List of valid names for the first argument in a class method.
273 | valid-classmethod-first-arg=cls
274 |
275 | # List of valid names for the first argument in a metaclass class method.
276 | valid-metaclass-classmethod-first-arg=mcs
277 |
278 |
279 | [EXCEPTIONS]
280 |
281 | # Exceptions that will emit a warning when being caught. Defaults to
282 | # "Exception"
283 | overgeneral-exceptions=Exception
284 |
--------------------------------------------------------------------------------
/scripts/run-env-linux.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | QGIS_PREFIX_PATH=/usr/local/qgis-3.0
4 | if [ -n "$1" ]; then
5 | QGIS_PREFIX_PATH=$1
6 | fi
7 |
8 | echo ${QGIS_PREFIX_PATH}
9 |
10 |
11 | export QGIS_PREFIX_PATH=${QGIS_PREFIX_PATH}
12 | export QGIS_PATH=${QGIS_PREFIX_PATH}
13 | export LD_LIBRARY_PATH=${QGIS_PREFIX_PATH}/lib
14 | export PYTHONPATH=${QGIS_PREFIX_PATH}/share/qgis/python:${QGIS_PREFIX_PATH}/python:${QGIS_PREFIX_PATH}/python/plugins:${PYTHONPATH}
15 |
16 | echo "QGIS PATH: $QGIS_PREFIX_PATH"
17 | export QGIS_DEBUG=0
18 | export QGIS_LOG_FILE=/tmp/dataplotly/realtime/logs/qgis.log
19 |
20 | export PATH=${QGIS_PREFIX_PATH}/bin:$PATH
21 |
22 | echo "This script is intended to be sourced to set up your shell to"
23 | echo "use a QGIS 3.0 built in $QGIS_PREFIX_PATH"
24 | echo
25 | echo "To use it do:"
26 | echo "source $BASH_SOURCE /your/optional/install/path"
27 | echo
28 | echo "Then use the make file supplied here e.g. make guitest"
29 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [qgis-plugin-ci]
2 | plugin_path = DataPlotly
3 | github_organization_slug = ghtmtt
4 | project_slug = DataPlotly
5 | transifex_organization = dataplotly
6 | transifex_project = dataplotly-ui
7 | transifex_resource = application
8 | transifex_coordinator = ghtmtt
9 |
10 | [flake8]
11 | exclude =
12 | .venv/,
13 |
--------------------------------------------------------------------------------