diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 808b806cb3d..2a5392b2b68 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,7 +4,7 @@ - + Fixes # @@ -15,3 +15,7 @@ Fixes # - [ ] Add new public functions/methods/classes to `doc/api/index.rst`. - [ ] Write detailed docstrings for all functions/methods. - [ ] If adding new functionality, add an example to docstrings or tutorials. + +**Notes** + +- You can write `/format` in the first line of a comment to lint the code automatically diff --git a/.github/workflows/cache_data.yaml b/.github/workflows/cache_data.yaml index 8682712faa7..9f4d779c713 100644 --- a/.github/workflows/cache_data.yaml +++ b/.github/workflows/cache_data.yaml @@ -37,8 +37,8 @@ jobs: @tut_bathy.nc @tut_quakes.ngdc @tut_ship.xyz \ @usgs_quakes_22.txt - # Upload the downloaded files as artifacts to Github - - name: Upload artifacts to Github + # Upload the downloaded files as artifacts to GitHub + - name: Upload artifacts to GitHub uses: actions/upload-artifact@v2 with: name: gmt-cache diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index b4818fd89a1..5d941d2bb3b 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -28,7 +28,7 @@ jobs: python-version: 3.7 - name: Install packages - run: pip install black flake8 pylint + run: pip install black blackdoc flake8 pylint - name: Formatting check (black and flake8) run: make check @@ -89,8 +89,8 @@ jobs: shell: bash -l {0} run: conda list - # Download cached remote files (artifacts) from Github - - name: Download remote data from Github + # Download cached remote files (artifacts) from GitHub + - name: Download remote data from GitHub uses: dawidd6/action-download-artifact@v2.6.3 with: workflow: cache_data.yaml diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml index 8e08e5cf836..98790ab96aa 100644 --- a/.github/workflows/ci_tests_dev.yaml +++ b/.github/workflows/ci_tests_dev.yaml @@ -21,7 +21,7 @@ jobs: matrix: python-version: [3.8] os: [ubuntu-20.04, macOS-10.15] - gmt_git_ref: [6.1, master] + gmt_git_ref: [master] env: # LD_LIBRARY_PATH: ${{ github.workspace }}/gmt/lib:$LD_LIBRARY_PATH GMT_INSTALL_DIR: ${{ github.workspace }}/gmt-install-dir @@ -61,8 +61,8 @@ jobs: env: GMT_GIT_REF: ${{ matrix.gmt_git_ref }} - # Download cached remote files (artifacts) from Github - - name: Download remote data from Github + # Download cached remote files (artifacts) from GitHub + - name: Download remote data from GitHub uses: dawidd6/action-download-artifact@v2.6.3 with: workflow: cache_data.yaml diff --git a/.github/workflows/format-command.yml b/.github/workflows/format-command.yml new file mode 100644 index 00000000000..c0a320ec204 --- /dev/null +++ b/.github/workflows/format-command.yml @@ -0,0 +1,47 @@ +name: format-command +on: + repository_dispatch: + types: [format-command] +jobs: + format: + runs-on: ubuntu-latest + steps: + # Generate token from GenericMappingTools bot + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + + # Checkout the pull request branch + - uses: actions/checkout@v2 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.event.client_payload.pull_request.head.repo.full_name }} + ref: ${{ github.event.client_payload.pull_request.head.ref }} + + # Setup Python environment + - uses: actions/setup-python@v1 + + # Install formatting tools + - name: Install formatting tools + run: pip install black blackdoc flake8 + + # Run "make format" and commit the change to the PR branch + - name: Commit to the PR branch if any changes + run: | + make format + if [[ $(git ls-files -m) ]]; then + git config --global user.name 'actions-bot' + git config --global user.email '58130806+actions-bot@users.noreply.github.com' + git commit -am "[format-command] fixes" + git push + fi + + - name: Add reaction + uses: peter-evans/create-or-update-comment@v1 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.event.client_payload.github.payload.repository.full_name }} + comment-id: ${{ github.event.client_payload.github.payload.comment.id }} + reaction-type: hooray diff --git a/.github/workflows/slash-command-dispatch.yml b/.github/workflows/slash-command-dispatch.yml new file mode 100644 index 00000000000..85a489ac211 --- /dev/null +++ b/.github/workflows/slash-command-dispatch.yml @@ -0,0 +1,24 @@ +name: Slash Command Dispatch +on: + issue_comment: + types: [created] + # Add "edited" type for test purposes. Where possible, avoid using to prevent processing unnecessary events. + # types: [created, edited] +jobs: + slashCommandDispatch: + runs-on: ubuntu-latest + steps: + # Generate token from GenericMappingTools bot + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Slash Command Dispatch + uses: peter-evans/slash-command-dispatch@v2 + with: + token: ${{ steps.generate-token.outputs.token }} + commands: | + format + issue-type: pull-request diff --git a/.travis.yml b/.travis.yml index aa23e5e261d..ab57e494a9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ branches: env: global: # Encrypted variables - # Github Token for pushing the built docs (GH_TOKEN) + # GitHub Token for pushing the built docs (GH_TOKEN) - secure: "QII0477v0mmCCW3qSNXLCOtqraJaCICtSghiyrxYsuUdJTrXzXBNhX2KLIjcKYXOK1HdwYOFGf8xBVLl44clHlAW7R32ecEGeTJizr0yqTBvT3rNG1Xb7+E6jdXqrIs//PmPRaF8zOZxPl1SJKDK4jJpCx5HnAflg7wl/6tQLD6K3/dQ6FG2s3UKsc8o4qchOiEfxYhOuKo3jt2S0HdsNAQFw3mFHCCrclxDr3llSQtWSY0mirZnta7AI4nMvzxl2nUhdHEpxgzIjWxCWLAwmj3/NxLz0VSgNCtl2bNYk6AYrc5RcANGk2fcYaZr9mTU3Aax60S4389B39Pq95hBN21jYdbw9vCN810dYpTUk2siLysx8gF6r2JWEF8SskXlF79r3phtaFTMOS4GqeiuwjifZeaLAL/H1PTQFDDG/UKEwBpLuzrPMDw/84iRtyWKqWR/f14YdKhH4YAkcOuRglEXiI/1A0qWKiZ1iZfky8Tys+wN5nyss23w/JeYXVgBdTkNzvp3diFWK8+Wl9j3HYpX9LlEHJwASA1wHLL85t4ToymgLjo9gvLvwzB7T+fWNtEbh4ELbvI7jaKrvir8uSGYy4bGbfRclh5CktD//mTLhDyAsQDS8obF/Ri9mVqFzjK6417ORfu8qnpXU+mIHPRBoKvpS2WqnPtSwF8KPv8=" # TWINE_PASSWORD to deploy to PyPI - secure: "md4fgPt9RC/sCoN5//5PcNHLUd9gWQGewV5hFpWW88MRTjxTng1Zfs8r7SqlF2AkEEepFfyzq0BEe9c3FMAnFbec3KmqdlQen4V8xDbLrcTlvkPlTrYGbAScUvdhhqojB//hMHoTD4KvxAv9CiUwFBO4hCMmj2buWHUbV9Ksu5WCW9mF/gkt/hIuYAU6Mbwt8PiYyMgUpzMHO1vruofcWRaVnvKwmBqHB0ae86D4/drpwn4CWjlM12WUnphT2bssiyPkw24FZtCN6kPVta6bLZKBxu0bZpw2vbXuUG+Yh19Q4mp8wNYT3XSHJf8Hl5LfujF48+cLWu+6rlCkdcelyVylhWLFc3rGOONAv4G8jWW2yNSz/bLQfJnMpd81fQEu5eySmFxB7mdB0uyKpvIG1jMJQ73LlYKakKLAPdYhMFyQAHoX9gvCE3S4QR95DBMi5gM/pZubOCcMLdjPHB5JKpJHSjxbOzyVwgmsUIEgd5Bi2vZvvYQXn1plk4xpQ3PhXc+/gi33bzY89mKcfOn0HJ2pD1vLqDXRCBsMCakoLZ0JB/6bacaz4FngbsGWuQ+I1cz20lJGL/MSi9bW1G7Uoidt3GXXWDmXrWt70vIXlLIxr8XV0Mu/rPbauGgWE+ZSYEfvdM5sP+FNF7vQ5de+Fkvzg5Z3tTfR+O1W+d7+vM4=" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 919b381dc26..539ef51da3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ for the project where you can ask questions. ## Reporting a Bug -Find the *Issues* tab on the top of the Github repository and click *New Issue*. +Find the *Issues* tab on the top of the GitHub repository and click *New Issue*. You'll be prompted to choose between different types of issue, like bug reports and feature requests. Choose the one that best matches your need. @@ -92,7 +92,7 @@ download and install anything: * On each documentation page, there should be an "Improve This Page" link at the very top. * Click on that link to open the respective source file (usually an `.rst` file in the - `doc` folder) on Github for editing online (you'll need a Github account). + `doc` folder) on GitHub for editing online (you'll need a GitHub account). * Make your desired changes. * When you're done, scroll to the bottom of the page. * Fill out the two fields under "Commit changes": the first is a short title describing @@ -245,8 +245,8 @@ directory). ### Code style -We use [Black](https://github.com/ambv/black) to format the code so we don't have to -think about it. +We use [Black](https://github.com/ambv/black) and [blackdoc](https://github.com/keewis/blackdoc) +to format the code so we don't have to think about it. Black loosely follows the [PEP8](http://pep8.org) guide but with a few differences. Regardless, you won't have to worry about formatting the code yourself. Before committing, run it to automatically format your code: @@ -255,9 +255,10 @@ Before committing, run it to automatically format your code: make format ``` -Don't worry if you forget to do it. -Our continuous integration systems will warn us and you can make a new commit with the -formatted code. +Don't worry if you forget to do it. Our continuous integration systems will +warn us and you can make a new commit with the formatted code. +Even better, you can just write `/format` in the first line of any comment in a +Pull Request to lint the code automatically. We also use [flake8](http://flake8.pycqa.org/en/latest/) and [pylint](https://www.pylint.org/) to check the quality of the code and quickly catch @@ -265,7 +266,7 @@ common errors. The [`Makefile`](Makefile) contains rules for running both checks: ```bash -make check # Runs flake8 and black (in check mode) +make check # Runs flake8, black and blackdoc (in check mode) make lint # Runs pylint, which is a bit slower ``` @@ -308,6 +309,14 @@ in your browser. **Strive to get 100% coverage for the lines you changed.** It's OK if you can't or don't know how to test something. Leave a comment in the PR and we'll help you out. +You can also run tests in just one test script using: + + pytest --verbose --mpl --mpl-results-path=results --doctest_modules pygmt/tests/NAME_OF_TEST_FILE.py + +or run tests which contain names that match a specific keyword expression: + + pytest --verbose --mpl --mpl-results-path=results --doctest_modules -k KEYWORD pygmt/tests + ### Testing plots Writing an image-based test is only slightly more difficult than a simple test. @@ -428,7 +437,7 @@ Some things that will increase the chance that your pull request is accepted qui Pull requests will automatically have tests run by TravisCI. This includes running both the unit tests as well as code linters. -Github will show the status of these checks on the pull request. +GitHub will show the status of these checks on the pull request. Try to get them all passing (green). If you have any trouble, leave a comment in the PR or [get in touch](#how-can-i-talk-to-you). diff --git a/MAINTENANCE.md b/MAINTENANCE.md index bdeae96f04b..0060289e26d 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -11,7 +11,7 @@ If you want to make a contribution to the project, see the * *master*: Always tested and ready to become a new version. Don't push directly to this branch. Make a new branch and submit a pull request instead. -* *gh-pages*: Holds the HTML documentation and is served by Github. Pages for the master +* *gh-pages*: Holds the HTML documentation and is served by GitHub. Pages for the master branch are in the `dev` folder. Pages for each release are in their own folders. **Automatically updated by TravisCI** so you shouldn't have to make commits here. @@ -40,12 +40,12 @@ The main advantages of this are: ## Continuous Integration -We use Github Actions and TravisCI continuous integration (CI) services to +We use GitHub Actions and TravisCI continuous integration (CI) services to build and test the project on Linux, macOS and Windows. They rely on the `requirements.txt` file to install required dependencies using conda and the `Makefile` to run the tests and checks. -### Github Actions +### GitHub Actions There are 3 configuration files located in `.github/workflows`: @@ -59,7 +59,7 @@ It is also scheduled to run daily on the *master* branch. This is only triggered when a review is requested or re-requested on a PR. It is also scheduled to run daily on the *master* branch. -3. `cache_data.yaml` (Caches GMT remote data files needed for Github Actions CI) +3. `cache_data.yaml` (Caches GMT remote data files needed for GitHub Actions CI) This is scheduled to run every Sunday at 12 noon. If new remote files are needed urgently, maintainers can manually uncomment @@ -85,7 +85,7 @@ submit pull requests to that repository. ## Continuous Documentation -We use the [Zeit Now for Github integration](https://zeit.co/github) to preview changes +We use the [Zeit Now for GitHub integration](https://zeit.co/github) to preview changes made to our documentation website every time we make a commit in a pull request. The integration service has a configuration file `now.json`, with a list of options to change the default behaviour at https://zeit.co/docs/configuration. @@ -103,10 +103,10 @@ There are a few steps that still must be done manually, though. ### Updating the changelog -The Release Drafter Github Action will automatically keep a draft changelog at +The Release Drafter GitHub Action will automatically keep a draft changelog at https://github.com/GenericMappingTools/pygmt/releases, adding a new entry every time a Pull Request (with a proper label) is merged into the master branch. -This release drafter tool has two configuration files, one for the Github Action +This release drafter tool has two configuration files, one for the GitHub Action at .github/workflows/release-drafter.yml, and one for the changelog template at .github/release-drafter.yml. Configuration settings can be found at https://github.com/release-drafter/release-drafter. @@ -122,7 +122,7 @@ publishing the actual release notes at https://www.pygmt.org/latest/changes.html 2. Edit the changes list to remove any trivial changes (updates to the README, typo fixes, CI configuration, etc). -3. Replace the PR number in the commit titles with a link to the Github PR page. +3. Replace the PR number in the commit titles with a link to the GitHub PR page. Use ``sed -i.bak -E 's$\(#([0-9]*)\)$(`#\1 `__)$g' changes.rst`` to make the change automatically. 4. Copy the remaining changes to `doc/changes.rst` under a new section for the @@ -142,7 +142,7 @@ publishing the actual release notes at https://www.pygmt.org/latest/changes.html ### Check the README syntax -Github is a bit forgiving when it comes to the RST syntax in the README but PyPI is not. +GitHub is a bit forgiving when it comes to the RST syntax in the README but PyPI is not. So slightly broken RST can cause the PyPI page to not render the correct content. Check using the `rst2html.py` script that comes with docutils: @@ -167,7 +167,7 @@ this new folder. ### Archiving on Zenodo -Grab a zip file from the Github release and upload to Zenodo using the previously +Grab a zip file from the GitHub release and upload to Zenodo using the previously reserved DOI. ### Updating the conda package diff --git a/Makefile b/Makefile index d6672c19f5e..9f5fdd40e6b 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ PYTEST_ARGS=--cov=$(PROJECT) --cov-config=../.coveragerc \ --doctest-modules -v --mpl --mpl-results-path=results \ --pyargs ${PYTEST_EXTRA} BLACK_FILES=$(PROJECT) setup.py doc/conf.py examples +BLACKDOC_OPTIONS=--line-length 79 FLAKE8_FILES=$(PROJECT) setup.py doc/conf.py LINT_FILES=$(PROJECT) setup.py doc/conf.py @@ -14,8 +15,8 @@ help: @echo "" @echo " install install in editable mode" @echo " test run the test suite (including doctests) and report coverage" - @echo " format run black to automatically format the code" - @echo " check run code style and quality checks (black and flake8)" + @echo " format run black and blackdoc to automatically format the code" + @echo " check run code style and quality checks (black, blackdoc and flake8)" @echo " lint run pylint for a deeper (and slower) quality check" @echo " clean clean up build and generated files" @echo "" @@ -36,9 +37,11 @@ test: format: black $(BLACK_FILES) + blackdoc $(BLACKDOC_OPTIONS) $(BLACK_FILES) check: black --check $(BLACK_FILES) + blackdoc --check $(BLACKDOC_OPTIONS) $(BLACK_FILES) flake8 $(FLAKE8_FILES) lint: diff --git a/README.rst b/README.rst index d0c04e6e224..6d4004527fd 100644 --- a/README.rst +++ b/README.rst @@ -38,6 +38,22 @@ PyGMT .. placeholder-for-doc-index +Why PyGMT? +---------- + +A beautiful map is worth a thousand words. +To truly understand how powerful PyGMT is, play with it online on `Binder `__! +But if you need some convincing first, watch this **1 hour introduction** to PyGMT! + +Afterwards, feel free to look at our `Tutorials `__ +or visit the `PyGMT Gallery `__. + +.. image:: https://user-images.githubusercontent.com/23487320/95393255-c0b72e80-0956-11eb-9471-24429461802b.png + :alt: Remote Online Sessions for Emerging Seismologists (ROSES): Unit 8 - PyGMT + :align: center + :target: https://www.youtube.com/watch?v=SSIGJEe0BIk + + Disclaimer ---------- @@ -50,7 +66,7 @@ implement new features. **This is not a finished product, use with caution.** We welcome any feedback and ideas! Let us know by submitting -`issues on Github `__ +`issues on GitHub `__ or by posting on our `Discourse forum `__. About @@ -80,7 +96,7 @@ Project goals Contacting Us ------------- -* Most discussion happens `on Github +* Most discussion happens `on GitHub `__. Feel free to `open an issue `__ or comment on any open issue or pull request. @@ -134,7 +150,7 @@ Who we are PyGMT is a community developed project. See the `AUTHORS.md `__ -file on Github for a list of the people involved and a definition of the term "PyGMT +file on GitHub for a list of the people involved and a definition of the term "PyGMT Developers". @@ -170,7 +186,7 @@ Other Python wrappers for GMT: Documentation for other versions -------------------------------- * `Development `__ (reflects the *master* branch on - Github) + GitHub) * `Latest release `__ * `v0.2.0 `__ * `v0.1.2 `__ diff --git a/doc/_templates/autosummary/function.rst b/doc/_templates/autosummary/function.rst index 6a6cfd4d443..e4ae37539e8 100644 --- a/doc/_templates/autosummary/function.rst +++ b/doc/_templates/autosummary/function.rst @@ -4,6 +4,7 @@ .. autofunction:: {{ objname }} +.. include:: backreferences/{{ fullname }}.examples .. raw:: html diff --git a/doc/_templates/autosummary/method.rst b/doc/_templates/autosummary/method.rst new file mode 100644 index 00000000000..9c5d7313e32 --- /dev/null +++ b/doc/_templates/autosummary/method.rst @@ -0,0 +1,11 @@ +{{ fullname | escape | underline }} + +.. currentmodule:: {{ module }} + +.. automethod:: {{ objname }} + +.. include:: backreferences/{{ fullname }}.examples + +.. raw:: html + +
diff --git a/doc/_templates/breadcrumbs.html b/doc/_templates/breadcrumbs.html index ee8e5f31028..35fe1383d3f 100644 --- a/doc/_templates/breadcrumbs.html +++ b/doc/_templates/breadcrumbs.html @@ -1,4 +1,4 @@ -{# Extend the RTD template to include "Edit on Github" and option to download +{# Extend the RTD template to include "Edit on GitHub" and option to download notebook generated pages from nbsphinx #} {% extends "!breadcrumbs.html" %} diff --git a/doc/api/index.rst b/doc/api/index.rst index c4f199b3dbc..09e822bc87a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -71,6 +71,7 @@ Operations on grids: :toctree: generated grdcut + grdfilter grdtrack Crossover analysis with x2sys: diff --git a/doc/changes.rst b/doc/changes.rst index 61f015f9162..ce0473bcd95 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -59,10 +59,10 @@ Maintenance: * Eliminate unnecessary jobs from Travis CI (`#567 `__) and Azure Pipelines (`#513 `__) * Improve the workflow to test both GMT master (`#485 `__) and 6.1 branches (`#554 `__) * Automatically cancel in-progress CI runs of old commits (`#544 `__) -* Remove the Stickler CI configuration file (`#538 `__), run style checks using Github Actions (`#519 `__) -* Cache GMT remote data as artifacts on Github (`#530 `__) +* Remove the Stickler CI configuration file (`#538 `__), run style checks using GitHub Actions (`#519 `__) +* Cache GMT remote data as artifacts on GitHub (`#530 `__) * Let pytest generate both HTML and XML coverage reports (`#512 `__) -* Run Continuous Integration tests on Github Actions (`#475 `__) +* Run Continuous Integration tests on GitHub Actions (`#475 `__) Contributors: @@ -225,7 +225,7 @@ Bug Fixes: Maintenance: * Quickfix Zeit Now miniconda installer link to anaconda.com (`#413 `__) -* Fix Github Pages deployment from Travis (`#410 `__) +* Fix GitHub Pages deployment from Travis (`#410 `__) * Update and clean TravisCI configuration (`#404 `__) * Quickfix min elevation for new SRTM15+V2.1 earth relief grids (`#401 `__) * Wrap docstrings to 79 chars and check with flake8 (`#384 `__) diff --git a/doc/index.rst b/doc/index.rst index b57c05e992d..4c3fd3d34e3 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -32,6 +32,7 @@ projections/index.rst tutorials/coastlines.rst tutorials/plot.rst + tutorials/text.rst .. toctree:: :maxdepth: 2 diff --git a/doc/install.rst b/doc/install.rst index 60f5032e8a0..ce87d390843 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -9,7 +9,7 @@ Installing We welcome any feedback and ideas! Let us know by submitting - `issues on Github `__ + `issues on GitHub `__ or by posting on our `Discourse forum `__. @@ -98,7 +98,7 @@ or use ``pip`` to install from `PyPI `__:: pip install pygmt -Alternatively, you can install the development version from the Github repository:: +Alternatively, you can install the development version from the GitHub repository:: pip install https://github.com/GenericMappingTools/pygmt/archive/master.zip diff --git a/doc/overview.rst b/doc/overview.rst index 7296bb8a19d..0262017724a 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -35,6 +35,17 @@ Presentations These are conference presentations about the development of PyGMT (previously "GMT/Python"): +* "Remote Online Sessions for Emerging Seismologists (ROSES): Unit 8 - PyGMT". + 2020. + Liam Toney. + Presented at *ROSES 2020*. + url: https://www.iris.edu/hq/inclass/lesson/728 + +.. figure:: https://img.youtube.com/vi/SSIGJEe0BIk/maxresdefault.jpg + :target: https://www.youtube.com/watch?v=SSIGJEe0BIk + :align: center + :alt: ROSES 2020 youtube video + * "PyGMT: Accessing the Generic Mapping Tools from Python". 2019. Leonardo Uieda and Paul Wessel. diff --git a/environment.yml b/environment.yml index 0590f52b8ca..39de11039a6 100644 --- a/environment.yml +++ b/environment.yml @@ -19,6 +19,7 @@ dependencies: - pytest-mpl - coverage - black + - blackdoc - pylint - flake8 - sphinx=2.2.1 diff --git a/examples/gallery/plot/points-transparency.py b/examples/gallery/plot/points-transparency.py new file mode 100644 index 00000000000..be6b0f56e36 --- /dev/null +++ b/examples/gallery/plot/points-transparency.py @@ -0,0 +1,25 @@ +""" +Points with varying transparency +-------------------------------- + +Points can be plotted with different transparency levels by passing in an array to the +``transparency`` argument of :meth:`pygmt.Figure.plot`. +""" + +import numpy as np +import pygmt + +# prepare the input x and y data +x = np.arange(0, 105, 5) +y = np.ones(x.size) +# transparency level in percentage from 0 (i.e., opaque) to 100 +transparency = x + +fig = pygmt.Figure() +fig.basemap( + region=[-5, 105, 0, 2], + frame=['xaf+l"Transparency level"+u%', "WSrt"], + projection="X15c/6c", +) +fig.plot(x=x, y=y, style="c0.6c", color="blue", pen="1p,red", transparency=transparency) +fig.show() diff --git a/examples/tutorials/text.py b/examples/tutorials/text.py new file mode 100644 index 00000000000..403f7ec8e72 --- /dev/null +++ b/examples/tutorials/text.py @@ -0,0 +1,143 @@ +""" +Plotting text +============= + +It is often useful to add annotations to a map plot. This is handled by +:meth:`pygmt.Figure.text`. +""" + +import os +import pygmt + +############################################################################### +# Basic map annotation +# -------------------- +# +# Text annotations can be added to a map using the :meth:`pygmt.Figure.text` +# method of the :class:`pygmt.Figure` class. +# +# Here we create a simple map and add an annotation using the ``text``, ``x``, +# and ``y`` arguments to specify the annotation text and position in the +# projection frame. ``text`` accepts 'str' types, while ``x``, and ``y`` +# accepts either 'int'/'float' numbers, or a list/array of numbers. + +fig = pygmt.Figure() +with pygmt.config(MAP_FRAME_TYPE="plain"): + fig.basemap(region=[108, 120, -5, 8], projection="M20c", frame="a") +fig.coast(land="black", water="skyblue") + +# Plotting text annotations using single elements +fig.text(text="SOUTH CHINA SEA", x=112, y=6) + +# Plotting text annotations using lists of elements +fig.text(text=["CELEBES SEA", "JAVA SEA"], x=[119, 112], y=[3.25, -4.6]) + +fig.show() + +############################################################################### +# Changing font style +# ------------------- +# The size, family/weight, and color of an annotation can be specified using +# the ``font`` argument. +# +# A list of all recognised fonts can be found at +# :gmt-docs:`cookbook/postscript-fonts.html`, including details of how to use +# non-default fonts. + +fig = pygmt.Figure() +with pygmt.config(MAP_FRAME_TYPE="plain"): + fig.basemap(region=[108, 120, -5, 8], projection="M20c", frame="a") +fig.coast(land="black", water="skyblue") + +# Customising the font style +fig.text(text="BORNEO", x=114.0, y=0.5, font="22p,Helvetica-Bold,white") + +fig.show() + +############################################################################### +# Plotting from a text file +# ------------------------- +# +# It is also possible to add annotations from a file containing `x`, `y`, and +# `text` fields. Here we give a complete example. + +fig = pygmt.Figure() +with pygmt.config(MAP_FRAME_TYPE="plain"): + fig.basemap(region=[108, 120, -5, 8], projection="M20c", frame="a") +fig.coast(land="black", water="skyblue") + +# Create space-delimited file +with open("examples.txt", "w") as f: + f.write("114 0.5 0 22p,Helvetica-Bold,white CM BORNEO\n") + f.write("119 3.25 0 12p,Helvetica-Bold,black CM CELEBES SEA\n") + f.write("112 -4.6 0 12p,Helvetica-Bold,black CM JAVA SEA\n") + f.write("112 6 40 12p,Helvetica-Bold,black CM SOUTH CHINA SEA\n") + f.write("119.12 7.25 -40 12p,Helvetica-Bold,black CM SULU SEA\n") + f.write("118.4 -1 65 12p,Helvetica-Bold,black CM MAKASSAR STRAIT\n") + +# Plot region names / sea names from a text file, where +# the longitude (x) and latitude (y) coordinates are in the first two columns. +# Setting angle/font/justiry to True will indicate that those columns are +# present in the text file too (Note: must be in that order!). +# Finally, the text to be printed will be in the last column +fig.text(textfiles="examples.txt", angle=True, font=True, justify=True) + +# Cleanups +os.remove("examples.txt") + +fig.show() + +############################################################################### +# ``justify`` argument +# -------------------- +# +# ``justify`` is used to define the anchor point for the bounding box for text +# being added to a plot. The following code segment demonstrates the +# positioning of the anchor point relative to the text. +# +# The anchor is specified with a two letter (order independent) code, chosen +# from: +# * Vertical anchor: T(op), M(iddle), B(ottom) +# * Horizontal anchor: L(eft), C(entre), R(ight) + +fig = pygmt.Figure() +fig.basemap(region=[0, 3, 0, 3], projection="X10c", frame=["WSne", "af0.5g"]) +for position in ("TL", "TC", "TR", "ML", "MC", "MR", "BL", "BC", "BR"): + fig.text( + text=position, + position=position, + font="28p,Helvetica-Bold,black", + justify=position, + ) +fig.show() + +############################################################################### +# ``angle`` argument +# ------------------ +# ``angle`` is an optional argument used to specify the clockwise rotation of +# the text from the horizontal. + +fig = pygmt.Figure() +fig.basemap(region=[0, 4, 0, 4], projection="X5c", frame="WSen") +for i in range(0, 360, 30): + fig.text(text=f"` {i}@.", x=2, y=2, justify="LM", angle=i) +fig.show() + +############################################################################### +# ``fill`` argument +# ----------------- +# +# ``fill`` is used to set the fill color of the area surrounding the text. + +fig = pygmt.Figure() +fig.basemap(region=[0, 1, 0, 1], projection="X5c", frame="WSen") +fig.text(text="Green", x=0.5, y=0.5, fill="green") +fig.show() + +############################################################################### +# Advanced configuration +# ---------------------- +# +# For crafting more advanced styles, be sure to check out the GMT documentation +# at :gmt-docs:`text.html` and also the cookbook at +# :gmt-docs:`cookbook/features.html#placement-of-text`. Good luck! diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index e02eb03d310..396b0c86d31 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -15,6 +15,7 @@ fmt_docstring, use_alias, kwargs_to_strings, + is_nonstr_iter, ) @@ -48,7 +49,7 @@ def _preprocess(self, **kwargs): # pylint: disable=no-self-use -------- >>> base = BasePlotting() - >>> base._preprocess(resolution='low') + >>> base._preprocess(resolution="low") {'resolution': 'low'} """ @@ -68,6 +69,7 @@ def _preprocess(self, **kwargs): # pylint: disable=no-self-use G="land", S="water", U="timestamp", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -129,6 +131,7 @@ def coast(self, **kwargs): water : str Select filling or clipping of “wet” areas. {U} + {V} shorelines : str ``'[level/]pen'`` Draw shorelines [Default is no shorelines]. Append pen attributes. @@ -151,6 +154,7 @@ def coast(self, **kwargs): F="box", G="truncate", W="scale", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -216,6 +220,7 @@ def colorbar(self, **kwargs): scale : float Multiply all z-values in the CPT by the provided scale. By default the CPT is used as is. + {V} {XY} {p} {t} @@ -237,6 +242,7 @@ def colorbar(self, **kwargs): R="region", S="resample", U="timestamp", + V="verbose", W="pen", l="label", X="xshift", @@ -291,6 +297,7 @@ def grdcontour(self, grid, **kwargs): {B} {G} {U} + {V} {W} {XY} label : str @@ -488,6 +495,7 @@ def grdimage(self, grid, **kwargs): Wm="meshpen", Wf="facadepen", I="shading", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -564,6 +572,7 @@ def grdview(self, grid, **kwargs): intensity, and ambient arguments for that module, or just give ``+d`` to select the default arguments (``+a-45+nt1+m0``). + {V} {XY} {p} {t} @@ -602,11 +611,13 @@ def grdview(self, grid, **kwargs): B="frame", S="style", G="color", + N="no_clip", W="pen", i="columns", l="label", C="cmap", U="timestamp", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -673,17 +684,33 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): ``'[x|y|X|Y][+a][+cl|f][+n][+wcap][+ppen]'``. Draw symmetrical error bars. {G} + no_clip : bool or str + ``'[c|r]'``. + Do NOT clip symbols that fall outside map border [Default plots + points whose coordinates are strictly inside the map border only]. + The option does not apply to lines and polygons which are always + clipped to the map region. For periodic (360-longitude) maps we + must plot all symbols twice in case they are clipped by the + repeating boundary. ``no_clip=True`` will turn off clipping and not + plot repeating symbols. Use ``no_clip="r"`` to turn off clipping + but retain the plotting of such repeating symbols, or use + ``no_clip="c"`` to retain clipping but turn off plotting of + repeating symbols. style : str Plot symbols (including vectors, pie slices, fronts, decorated or quoted lines). {W} {U} + {V} {XY} label : str Add a legend entry for the symbol or line being plotted. {p} {t} + *transparency* can also be a 1d array to set varying transparency + for symbols. + """ kwargs = self._preprocess(**kwargs) @@ -706,6 +733,10 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): ) extra_arrays.append(sizes) + if "t" in kwargs and is_nonstr_iter(kwargs["t"]): + extra_arrays.append(kwargs["t"]) + kwargs["t"] = "" + with Session() as lib: # Choose how data will be passed in to the module if kind == "file": @@ -730,9 +761,11 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): G="label_placement", W="pen", L="triangular_mesh_pen", + N="no_clip", i="columns", l="label", C="levels", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -779,8 +812,9 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs): Color the triangles using CPT triangular_mesh_pen : str Pen to draw the underlying triangulation (default none) - N : bool - Do not clip contours + no_clip : bool + Do NOT clip contours or image at the boundaries [Default will clip + to fit inside region]. Q : float or str Do not draw contours with less than cut number of points. ``'[cut[unit]][+z]'`` @@ -794,6 +828,7 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs): to be of the format [*annotcontlabel*][/*contlabel*]. If either label contains a slash (/) character then use ``|`` as the separator for the two labels instead. + {V} {XY} {p} {t} @@ -827,6 +862,7 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs): Td="rose", Tm="compass", U="timestamp", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -864,6 +900,7 @@ def basemap(self, **kwargs): Draws a map magnetic rose on the map at the location defined by the reference and anchor points {U} + {V} {XY} {p} {t} @@ -879,25 +916,26 @@ def basemap(self, **kwargs): @use_alias( R="region", J="projection", - U="timestamp", D="position", F="box", + S="style", + U="timestamp", + V="verbose", X="xshift", Y="yshift", - p="perspective", t="transparency", ) @kwargs_to_strings(R="sequence", p="sequence") def logo(self, **kwargs): """ - Place the GMT graphics logo on a map. + Plot the GMT logo. By default, the GMT logo is 2 inches wide and 1 inch high and will be positioned relative to the current plot origin. Use various options to change this and to place a transparent or opaque rectangular map panel behind the GMT logo. - Full option list at :gmt-docs:`logo.html` + Full option list at :gmt-docs:`gmtlogo.html`. {aliases} @@ -911,15 +949,21 @@ def logo(self, **kwargs): box : bool or str Without further options, draws a rectangular border around the GMT logo. + style : str + ``l|n|u``. + Control what is written beneath the map portion of the logo. + + - **l** to plot the text label "The Generic Mapping Tools" + [Default] + - **n** to skip the label placement + - **u** to place the URL to the GMT site {U} + {V} {XY} - {p} {t} """ kwargs = self._preprocess(**kwargs) - if "D" not in kwargs: - raise GMTInvalidInput("Option D must be specified.") with Session() as lib: lib.call_module("logo", build_arg_string(kwargs)) @@ -930,6 +974,7 @@ def logo(self, **kwargs): D="position", F="box", M="monochrome", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -968,6 +1013,7 @@ def image(self, imagefile, **kwargs): monochrome : bool Convert color image to monochrome grayshades using the (television) YIQ-transformation. + {V} {XY} {p} {t} @@ -983,6 +1029,7 @@ def image(self, imagefile, **kwargs): J="projection", D="position", F="box", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -1022,6 +1069,7 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg rectangular border around the legend using **MAP_FRAME_PEN**. By default, uses '+gwhite+p1p' which draws a box around the legend using a 1 point black pen and adds a white background. + {V} {XY} {p} {t} @@ -1052,6 +1100,8 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg C="clearance", D="offset", G="fill", + N="no_clip", + V="verbose", W="pen", X="xshift", Y="yshift", @@ -1163,6 +1213,10 @@ def text( Sets the pen used to draw a rectangle around the text string (see *clearance*) [Default is width = default, color = black, style = solid]. + no_clip : bool + Do NOT clip text at map boundaries [Default is False, i.e. will + clip]. + {V} {XY} {p} {t} @@ -1220,6 +1274,8 @@ def text( J="projection", B="frame", C="offset", + N="no_clip", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -1317,9 +1373,14 @@ def meca( circle is plotted at the initial location and a line connects the beachball to the circle. Specify pen and optionally append ``+ssize`` to change the line style and/or size of the circle. + no_clip : bool + Does NOT skip symbols that fall outside frame boundary specified by + *region* [Default is False, i.e. plot symbols inside map frame + only]. {J} {R} {B} + {V} {XY} {p} {t} diff --git a/pygmt/clib/conversion.py b/pygmt/clib/conversion.py index 8dd8adbeb93..e851211050d 100644 --- a/pygmt/clib/conversion.py +++ b/pygmt/clib/conversion.py @@ -47,7 +47,7 @@ def dataarray_to_matrix(grid): >>> from pygmt.datasets import load_earth_relief >>> # Use the global Earth relief grid with 1 degree spacing - >>> grid = load_earth_relief(resolution='01d') + >>> grid = load_earth_relief(resolution="01d") >>> matrix, region, inc = dataarray_to_matrix(grid) >>> print(region) [-180.0, 180.0, -90.0, 90.0] @@ -61,7 +61,7 @@ def dataarray_to_matrix(grid): True >>> # Using a slice of the grid, the matrix will be copied to guarantee >>> # that it's C-contiguous in memory. The increment should be unchanged. - >>> matrix, region, inc = dataarray_to_matrix(grid[10:41,30:101]) + >>> matrix, region, inc = dataarray_to_matrix(grid[10:41, 30:101]) >>> matrix.flags.c_contiguous True >>> print(matrix.shape) @@ -71,7 +71,7 @@ def dataarray_to_matrix(grid): >>> print(inc) [1.0, 1.0] >>> # but not if only taking every other grid point. - >>> matrix, region, inc = dataarray_to_matrix(grid[10:41:2,30:101:2]) + >>> matrix, region, inc = dataarray_to_matrix(grid[10:41:2, 30:101:2]) >>> matrix.flags.c_contiguous True >>> print(matrix.shape) @@ -231,11 +231,12 @@ def kwargs_to_ctypes_array(argument, kwargs, dtype): -------- >>> import ctypes as ct - >>> value = kwargs_to_ctypes_array('bla', {'bla': [10, 10]}, ct.c_long*2) + >>> value = kwargs_to_ctypes_array("bla", {"bla": [10, 10]}, ct.c_long * 2) >>> type(value) >>> should_be_none = kwargs_to_ctypes_array( - ... 'swallow', {'bla': 1, 'foo': [20, 30]}, ct.c_int*2) + ... "swallow", {"bla": 1, "foo": [20, 30]}, ct.c_int * 2 + ... ) >>> print(should_be_none) None @@ -313,9 +314,9 @@ def array_to_datetime(array): >>> # Mixed datetime types >>> x = [ - ... "2018-01-01", - ... np.datetime64("2018-01-01"), - ... datetime.datetime(2018, 1, 1), + ... "2018-01-01", + ... np.datetime64("2018-01-01"), + ... datetime.datetime(2018, 1, 1), ... ] >>> array_to_datetime(x) # doctest: +NORMALIZE_WHITESPACE DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'], diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index d5ce3a8a4cb..73cead5b47d 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -270,8 +270,9 @@ def get_libgmt_func(self, name, argtypes=None, restype=None): >>> from ctypes import c_void_p, c_int >>> with Session() as lib: - ... func = lib.get_libgmt_func('GMT_Destroy_Session', - ... argtypes=[c_void_p], restype=c_int) + ... func = lib.get_libgmt_func( + ... "GMT_Destroy_Session", argtypes=[c_void_p], restype=c_int + ... ) >>> type(func) ._FuncPtr'> @@ -702,15 +703,15 @@ def _check_dtype_and_dim(self, array, ndim): -------- >>> import numpy as np - >>> data = np.array([1, 2, 3], dtype='float64') + >>> data = np.array([1, 2, 3], dtype="float64") >>> with Session() as ses: ... gmttype = ses._check_dtype_and_dim(data, ndim=1) ... gmttype == ses["GMT_DOUBLE"] True - >>> data = np.ones((5, 2), dtype='float32') + >>> data = np.ones((5, 2), dtype="float32") >>> with Session() as ses: ... gmttype = ses._check_dtype_and_dim(data, ndim=2) - ... gmttype == ses['GMT_FLOAT'] + ... gmttype == ses["GMT_FLOAT"] True """ @@ -1022,23 +1023,23 @@ def open_virtual_file(self, family, geometry, direction, data): >>> x = np.array([0, 1, 2, 3, 4]) >>> y = np.array([5, 6, 7, 8, 9]) >>> with Session() as lib: - ... family = 'GMT_IS_DATASET|GMT_VIA_VECTOR' - ... geometry = 'GMT_IS_POINT' + ... family = "GMT_IS_DATASET|GMT_VIA_VECTOR" + ... geometry = "GMT_IS_POINT" ... dataset = lib.create_data( ... family=family, ... geometry=geometry, - ... mode='GMT_CONTAINER_ONLY', + ... mode="GMT_CONTAINER_ONLY", ... dim=[2, 5, 1, 0], # columns, lines, segments, type ... ) ... lib.put_vector(dataset, column=0, vector=x) ... lib.put_vector(dataset, column=1, vector=y) ... # Add the dataset to a virtual file - ... vfargs = (family, geometry, 'GMT_IN|GMT_IS_REFERENCE', dataset) + ... vfargs = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset) ... with lib.open_virtual_file(*vfargs) as vfile: ... # Send the output to a temp file so that we can read it ... with GMTTempFile() as ofile: - ... args = '{} ->{}'.format(vfile, ofile.name) - ... lib.call_module('info', args) + ... args = "{} ->{}".format(vfile, ofile.name) + ... lib.call_module("info", args) ... print(ofile.read().strip()) : N = 5 <0/4> <5/9> @@ -1133,7 +1134,7 @@ def virtualfile_from_vectors(self, *vectors): ... # Send the output to a file so that we can read it ... with GMTTempFile() as fout: ... ses.call_module( - ... 'info', '{} ->{}'.format(fin, fout.name) + ... "info", "{} ->{}".format(fin, fout.name) ... ) ... print(fout.read().strip()) : N = 3 <1/3> <4/6> <7/9> @@ -1245,7 +1246,7 @@ def virtualfile_from_matrix(self, matrix): ... # Send the output to a file so that we can read it ... with GMTTempFile() as fout: ... ses.call_module( - ... 'info', '{} ->{}'.format(fin, fout.name) + ... "info", "{} ->{}".format(fin, fout.name) ... ) ... print(fout.read().strip()) : N = 4 <0/9> <1/10> <2/11> @@ -1314,7 +1315,7 @@ def virtualfile_from_grid(self, grid): >>> from pygmt.datasets import load_earth_relief >>> from pygmt.helpers import GMTTempFile - >>> data = load_earth_relief(resolution='01d') + >>> data = load_earth_relief(resolution="01d") >>> print(data.shape) (180, 360) >>> print(data.lon.values.min(), data.lon.values.max()) @@ -1327,8 +1328,8 @@ def virtualfile_from_grid(self, grid): ... with ses.virtualfile_from_grid(data) as fin: ... # Send the output to a file so that we can read it ... with GMTTempFile() as fout: - ... args = '{} -L0 -Cn ->{}'.format(fin, fout.name) - ... ses.call_module('grdinfo', args) + ... args = "{} -L0 -Cn ->{}".format(fin, fout.name) + ... ses.call_module("grdinfo", args) ... print(fout.read().strip()) -180 180 -90 90 -8182 5651.5 1 1 360 180 1 1 >>> # The output is: w e s n z0 z1 dx dy n_columns n_rows reg gtype @@ -1378,22 +1379,27 @@ def extract_region(self): >>> import pygmt >>> fig = pygmt.Figure() - >>> fig.coast(region=[0, 10, -20, -10], projection="M6i", frame=True, - ... land='black') + >>> fig.coast( + ... region=[0, 10, -20, -10], + ... projection="M6i", + ... frame=True, + ... land="black", + ... ) >>> with Session() as lib: ... wesn = lib.extract_region() - >>> print(', '.join(['{:.2f}'.format(x) for x in wesn])) + >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) 0.00, 10.00, -20.00, -10.00 Using ISO country codes for the regions (for example ``'US.HI'`` for Hawaii): >>> fig = pygmt.Figure() - >>> fig.coast(region='US.HI', projection="M6i", frame=True, - ... land='black') + >>> fig.coast( + ... region="US.HI", projection="M6i", frame=True, land="black" + ... ) >>> with Session() as lib: ... wesn = lib.extract_region() - >>> print(', '.join(['{:.2f}'.format(x) for x in wesn])) + >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) -164.71, -154.81, 18.91, 23.58 The country codes can have an extra argument that rounds the region a @@ -1401,11 +1407,12 @@ def extract_region(self): region to multiples of 5): >>> fig = pygmt.Figure() - >>> fig.coast(region='US.HI+r5', projection="M6i", frame=True, - ... land='black') + >>> fig.coast( + ... region="US.HI+r5", projection="M6i", frame=True, land="black" + ... ) >>> with Session() as lib: ... wesn = lib.extract_region() - >>> print(', '.join(['{:.2f}'.format(x) for x in wesn])) + >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) -165.00, -150.00, 15.00, 25.00 """ diff --git a/pygmt/figure.py b/pygmt/figure.py index d32588ae85d..7f91492aad3 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -43,21 +43,21 @@ class Figure(BasePlotting): -------- >>> fig = Figure() - >>> fig.basemap(region=[0, 360, -90, 90], projection='W7i', frame=True) + >>> fig.basemap(region=[0, 360, -90, 90], projection="W7i", frame=True) >>> fig.savefig("my-figure.png") >>> # Make sure the figure file is generated and clean it up >>> import os - >>> os.path.exists('my-figure.png') + >>> os.path.exists("my-figure.png") True - >>> os.remove('my-figure.png') + >>> os.remove("my-figure.png") The plot region can be specified through ISO country codes (for example, ``'JP'`` for Japan): >>> fig = Figure() - >>> fig.basemap(region='JP', projection="M3i", frame=True) + >>> fig.basemap(region="JP", projection="M3i", frame=True) >>> # The fig.region attribute shows the WESN bounding box for the figure - >>> print(', '.join('{:.2f}'.format(i) for i in fig.region)) + >>> print(", ".join("{:.2f}".format(i) for i in fig.region)) 122.94, 145.82, 20.53, 45.52 """ diff --git a/pygmt/filtering.py b/pygmt/filtering.py index 37275fd7edd..adae7524576 100644 --- a/pygmt/filtering.py +++ b/pygmt/filtering.py @@ -17,7 +17,7 @@ @fmt_docstring -@use_alias(I="spacing", R="region") +@use_alias(I="spacing", R="region", V="verbose") @kwargs_to_strings(R="sequence") def blockmedian(table, outfile=None, **kwargs): """ @@ -50,6 +50,8 @@ def blockmedian(table, outfile=None, **kwargs): outfile : str Required if 'table' is a file. The file name for the output ASCII file. + {V} + Returns ------- output : pandas.DataFrame or None diff --git a/pygmt/gridding.py b/pygmt/gridding.py index 4a15a604191..bd47eb043f8 100644 --- a/pygmt/gridding.py +++ b/pygmt/gridding.py @@ -17,7 +17,7 @@ @fmt_docstring -@use_alias(I="spacing", R="region", G="outfile") +@use_alias(I="spacing", R="region", G="outfile", V="verbose") @kwargs_to_strings(R="sequence") def surface(x=None, y=None, z=None, data=None, **kwargs): """ @@ -58,6 +58,8 @@ def surface(x=None, y=None, z=None, data=None, **kwargs): Optional. The file name for the output netcdf file with extension .nc to store the grid in. + {V} + Returns ------- ret: xarray.DataArray or None diff --git a/pygmt/gridops.py b/pygmt/gridops.py index d6eaf08ef76..845fbee65ff 100644 --- a/pygmt/gridops.py +++ b/pygmt/gridops.py @@ -25,6 +25,7 @@ J="projection", N="extend", S="circ_subregion", + V="verbose", Z="z_subregion", ) @kwargs_to_strings(R="sequence") @@ -79,6 +80,8 @@ def grdcut(grid, **kwargs): considering the range of the core subset for further reduction of the area. + {V} + Returns ------- ret: xarray.DataArray or None @@ -117,75 +120,86 @@ def grdcut(grid, **kwargs): @fmt_docstring @use_alias( - G="outgrid", - F="filter", D="distance", - I="increment", + F="filter", + G="outgrid", + I="spacing", N="nans", R="region", T="toggle", V="verbose", - f="colinfo", ) @kwargs_to_strings(R="sequence") def grdfilter(grid, **kwargs): """ - filter a grid file in the time domain using one of the selected convolution + Filter a grid in the space (or time) domain. + + Filter a grid file in the time domain using one of the selected convolution or non-convolution isotropic or rectangular filters and compute distances - using Cartesian or Spherical geometries. The output grid file can optionally - be generated as a sub-region of the input (via *region*) and/or with new increment - (via *spacing*) or registration (via *toggle*). - In this way, one may have “extra space” in the input data so that the edges - will not be used and the output can be within one half-width of the input edges. - If the filter is low-pass, then the output may be less frequently sampled than the input. + using Cartesian or Spherical geometries. The output grid file can + optionally be generated as a sub-region of the input (via *region*) and/or + with new increment (via *spacing*) or registration (via *toggle*). In this + way, one may have "extra space" in the input data so that the edges will + not be used and the output can be within one half-width of the input edges. + If the filter is low-pass, then the output may be less frequently sampled + than the input. + + Full option list at :gmt-docs:`grdfilter.html` + + {aliases} Parameters ---------- grid : str or xarray.DataArray The file name of the input grid or the grid loaded as a DataArray. - outgrid : str or None + outgrid : str or None The name of the output netCDF file with extension .nc to store the grid in. filter : str + ``xwidth[/width2][modifiers]``. Name of filter type you which to apply, followed by the width b: Box Car; c: Cosine Arch; g: Gaussian; o: Operator; m: Median; p: Maximum Likelihood probability; h: histogram Example: F='m600' for a median filter with width of 600 - {D}: str - Distance flag, that tells how grid (x,y) rrlated to the filter width as follows: - flag = p: grid (px,py) with width an odd number of pixels; Cartesian distances. + distance : str + Distance *flag* tells how grid (x,y) relates to filter width as + follows: + + p: grid (px,py) with *width* an odd number of pixels; Cartesian + distances. - flag = 0: grid (x,y) same units as width, Cartesian distances. + 0: grid (x,y) same units as *width*, Cartesian distances. - flag = 1: grid (x,y) in degrees, width in kilometers, Cartesian distances. + 1: grid (x,y) in degrees, *width* in kilometers, Cartesian distances. - flag = 2: grid (x,y) in degrees, width in km, dx scaled by cos(middle y), Cartesian distances. + 2: grid (x,y) in degrees, *width* in km, dx scaled by cos(middle y), + Cartesian distances. - The above options are fastest because they allow weight matrix to be computed only once. - The next three options are slower because they recompute weights for each latitude. + The above options are fastest because they allow weight matrix to be + computed only once. The next three options are slower because they + recompute weights for each latitude. - flag = 3: grid (x,y) in degrees, width in km, dx scaled by cosine(y), Cartesian distance calculation. + 3: grid (x,y) in degrees, *width* in km, dx scaled by cosine(y), + Cartesian distance calculation. - flag = 4: grid (x,y) in degrees, width in km, Spherical distance calculation. + 4: grid (x,y) in degrees, *width* in km, Spherical distance + calculation. - flag = 5: grid (x,y) in Mercator -Jm1 img units, width in km, Spherical distance calculation. + 5: grid (x,y) in Mercator ``projection='m1'`` img units, *width* in km, + Spherical distance calculation. - {I}: str + spacing : str + ``xinc[+e|n][/yinc[+e|n]]``. x_inc [and optionally y_inc] is the grid spacing. - (http://docs.generic-mapping-tools.org/latest/grdfilter.html#i) - {N}: Str or Number + nans : str or float + ``i|p|r``. Determine how NaN-values in the input grid affects the filtered output. - Values are i|p|r (http://docs.generic-mapping-tools.org/latest/grdfilter.html#n) {R} - {T}: Bool - Toggle the node registration for the output grid so as to become the opposite of the input grid - (http://docs.generic-mapping-tools.org/latest/grdfilter.html#t) - {V}: Bool or Str - Select verbosity level, which will send progress reports to stderr. - (http://docs.generic-mapping-tools.org/latest/gmt.html#v-full) - {f}: Str - Specify the data types of input and/or output columns (time or geographical data). - (http://docs.generic-mapping-tools.org/latest/gmt.html#f-full) + toggle : bool + Toggle the node registration for the output grid so as to become the + opposite of the input grid. [Default gives the same registration as the + input grid]. + {V} Returns ------- @@ -194,14 +208,28 @@ def grdfilter(grid, **kwargs): - xarray.DataArray if *outgrid* is not set - None if *outgrid* is set (grid output will be stored in *outgrid*) - Usage - ------- - pygmt.grdfilter('input.nc',F='m1600',D='4', G='filtered_output.nc') - Applies a filter of 1600km (full width) in the input.nc and returns a a filtered field (saved as netcdf) + Examples + -------- + >>> import os + >>> import pygmt + + >>> # Apply a filter of 600km (full width) to the @earth_relief_30m file + >>> # and return a filtered field (saved as netcdf) + >>> pygmt.grdfilter( + ... grid="@earth_relief_30m", + ... filter="m600", + ... distance="4", + ... region=[150, 250, 10, 40], + ... spacing=0.5, + ... outgrid="filtered_pacific.nc", + ... ) + >>> os.remove("filtered_pacific.nc") # cleanup file + + >>> # Apply a gaussian smoothing filter of 600 km in the input data array, + >>> # and returns a filtered data array with the smoothed field. + >>> grid = pygmt.datasets.load_earth_relief() + >>> smooth_field = pygmt.grdfilter(grid=grid, filter="g600", distance="4") - smooth_field=pygmt.grdfiler(dataarray,F='g600',D='4') - Applies a gaussian smoothing filter of 600 km in the input data array, - and returns a filtered data array withthe smoothed field. """ kind = data_kind(grid) diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index 376952891b4..28e8fafd896 100644 --- a/pygmt/helpers/decorators.py +++ b/pygmt/helpers/decorators.py @@ -154,7 +154,7 @@ def fmt_docstring(module_func): -------- >>> @fmt_docstring - ... @use_alias(R='region', J='projection') + ... @use_alias(R="region", J="projection") ... def gmtinfo(**kwargs): ... ''' ... My nice module. @@ -230,19 +230,19 @@ def use_alias(**aliases): Examples -------- - >>> @use_alias(R='region', J='projection') + >>> @use_alias(R="region", J="projection") ... def my_module(**kwargs): - ... print('R =', kwargs['R'], 'J =', kwargs['J']) - >>> my_module(R='bla', J='meh') + ... print("R =", kwargs["R"], "J =", kwargs["J"]) + >>> my_module(R="bla", J="meh") R = bla J = meh - >>> my_module(region='bla', J='meh') + >>> my_module(region="bla", J="meh") R = bla J = meh - >>> my_module(R='bla', projection='meh') + >>> my_module(R="bla", projection="meh") R = bla J = meh - >>> my_module(region='bla', projection='meh') + >>> my_module(region="bla", projection="meh") R = bla J = meh >>> my_module( - ... region='bla', projection='meh', J="bla" + ... region="bla", projection="meh", J="bla" ... ) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... @@ -309,21 +309,25 @@ def kwargs_to_strings(convert_bools=True, **conversions): -------- >>> @kwargs_to_strings( - ... R='sequence', i='sequence_comma', files='sequence_space' + ... R="sequence", i="sequence_comma", files="sequence_space" ... ) ... def module(*args, **kwargs): ... "A module that prints the arguments it received" - ... print('{', end='') - ... print(', '.join( - ... "'{}': {}".format(k, repr(kwargs[k])) for k in sorted(kwargs)), - ... end='') - ... print('}') + ... print("{", end="") + ... print( + ... ", ".join( + ... "'{}': {}".format(k, repr(kwargs[k])) + ... for k in sorted(kwargs) + ... ), + ... end="", + ... ) + ... print("}") ... if args: - ... print("args:", ' '.join('{}'.format(x) for x in args)) + ... print("args:", " ".join("{}".format(x) for x in args)) >>> module(R=[1, 2, 3, 4]) {'R': '1/2/3/4'} >>> # It's already a string, do nothing - >>> module(R='5/6/7/8') + >>> module(R="5/6/7/8") {'R': '5/6/7/8'} >>> module(P=True) {'P': ''} diff --git a/pygmt/helpers/tempfile.py b/pygmt/helpers/tempfile.py index a17293eb460..7dd1b7c3710 100644 --- a/pygmt/helpers/tempfile.py +++ b/pygmt/helpers/tempfile.py @@ -45,7 +45,7 @@ class GMTTempFile: >>> with GMTTempFile() as tmpfile: ... # write data to temporary file ... x = y = z = np.arange(0, 3, 1) - ... np.savetxt(tmpfile.name, (x, y, z), fmt='%.1f') + ... np.savetxt(tmpfile.name, (x, y, z), fmt="%.1f") ... lines = tmpfile.read() ... print(lines) ... nx, ny, nz = tmpfile.loadtxt(unpack=True, dtype=float) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 5004e1b24cf..a72dcd6bb51 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -51,7 +51,7 @@ def data_kind(data, x=None, y=None, z=None): 'vectors' >>> data_kind(data=np.arange(10).reshape((5, 2)), x=None, y=None) 'matrix' - >>> data_kind(data='my-data-file.txt', x=None, y=None) + >>> data_kind(data="my-data-file.txt", x=None, y=None) 'file' >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' @@ -94,7 +94,7 @@ def dummy_context(arg): Examples -------- - >>> with dummy_context('some argument') as temp: + >>> with dummy_context("some argument") as temp: ... print(temp) some argument @@ -127,11 +127,22 @@ def build_arg_string(kwargs): Examples -------- - >>> print(build_arg_string(dict(R='1/2/3/4', J="X4i", P='', E=200))) + >>> print( + ... build_arg_string( + ... dict(R="1/2/3/4", J="X4i", P="", E=200, X=None, Y=None) + ... ) + ... ) -E200 -JX4i -P -R1/2/3/4 - >>> print(build_arg_string(dict(R='1/2/3/4', J="X4i", - ... B=['xaf', 'yaf', 'WSen'], - ... I=('1/1p,blue', '2/0.25p,blue')))) + >>> print( + ... build_arg_string( + ... dict( + ... R="1/2/3/4", + ... J="X4i", + ... B=["xaf", "yaf", "WSen"], + ... I=("1/1p,blue", "2/0.25p,blue"), + ... ) + ... ) + ... ) -Bxaf -Byaf -BWSen -I1/1p,blue -I2/0.25p,blue -JX4i -R1/2/3/4 """ @@ -140,6 +151,8 @@ def build_arg_string(kwargs): if is_nonstr_iter(kwargs[key]): for value in kwargs[key]: sorted_args.append("-{}{}".format(key, value)) + elif kwargs[key] is None: # arguments like -XNone are invalid + continue else: sorted_args.append("-{}{}".format(key, kwargs[key])) @@ -164,7 +177,7 @@ def is_nonstr_iter(value): Examples -------- - >>> is_nonstr_iter('abc') + >>> is_nonstr_iter("abc") False >>> is_nonstr_iter(10) False diff --git a/pygmt/mathops.py b/pygmt/mathops.py index 971edbc2645..a4a61c13d42 100644 --- a/pygmt/mathops.py +++ b/pygmt/mathops.py @@ -7,7 +7,15 @@ @fmt_docstring -@use_alias(C="cmap", T="series", G="truncate", H="output", I="reverse", Z="continuous") +@use_alias( + C="cmap", + T="series", + G="truncate", + H="output", + I="reverse", + V="verbose", + Z="continuous", +) @kwargs_to_strings(T="sequence", G="sequence") def makecpt(**kwargs): """ @@ -52,6 +60,8 @@ def makecpt(**kwargs): input CPT remains untouched, in the second case it is only scaled to match the range z_min/z_max. + {V} + """ with Session() as lib: if "H" not in kwargs.keys(): # if no output is set diff --git a/pygmt/modules.py b/pygmt/modules.py index 477bc99221b..c733bb58790 100644 --- a/pygmt/modules.py +++ b/pygmt/modules.py @@ -17,6 +17,7 @@ @fmt_docstring +@use_alias(V="verbose") def grdinfo(grid, **kwargs): """ Get information about a grid. @@ -30,6 +31,8 @@ def grdinfo(grid, **kwargs): grid : str or xarray.DataArray The file name of the input grid or the grid loaded as a DataArray. + {V} + Returns ------- info : str @@ -55,7 +58,7 @@ def grdinfo(grid, **kwargs): @fmt_docstring -@use_alias(C="per_column", I="spacing", T="nearest_multiple") +@use_alias(C="per_column", I="spacing", T="nearest_multiple", V="verbose") def info(table, **kwargs): """ Get information about data tables. @@ -94,6 +97,8 @@ def info(table, **kwargs): Report the min/max of the first (0'th) column to the nearest multiple of dz and output this in the form ``[zmin, zmax, dz]``. + {V} + Returns ------- output : np.ndarray or str @@ -137,7 +142,7 @@ def info(table, **kwargs): @fmt_docstring -@use_alias(G="download") +@use_alias(G="download", V="verbose") def which(fname, **kwargs): """ Find the full path to specified files. @@ -165,6 +170,7 @@ def which(fname, **kwargs): it. Use True or 'l' (default) to download to the current directory. Use 'c' to place in the user cache directory or 'u' user data directory instead. + {V} Returns ------- diff --git a/pygmt/sampling.py b/pygmt/sampling.py index 891b8676a60..1ae93db24f3 100644 --- a/pygmt/sampling.py +++ b/pygmt/sampling.py @@ -16,7 +16,7 @@ @fmt_docstring -@use_alias(n="interpolation") +@use_alias(n="interpolation", V="verbose") def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs): """ Sample grids at specified (x,y) locations. @@ -55,6 +55,8 @@ def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs): Required if 'points' is a file. The file name for the output ASCII file. + {V} + {n} Returns diff --git a/pygmt/tests/test_basemap.py b/pygmt/tests/test_basemap.py index 989c181d809..bc0d43059b1 100644 --- a/pygmt/tests/test_basemap.py +++ b/pygmt/tests/test_basemap.py @@ -4,6 +4,7 @@ import pytest from .. import Figure +from ..helpers.testing import check_figures_equal from ..exceptions import GMTInvalidInput @@ -54,15 +55,15 @@ def test_basemap_power_axis(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_basemap_polar(): "Create a polar basemap plot" - fig = Figure() - fig.basemap(R="0/360/0/1000", J="P6i", B="afg") - return fig + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.basemap(R="0/360/0/1000", J="P6i", B="afg") + fig_test.basemap(region=[0, 360, 0, 1000], projection="P6i", frame="afg") + + return fig_ref, fig_test @pytest.mark.mpl_image_compare diff --git a/pygmt/tests/test_clib.py b/pygmt/tests/test_clib.py index 0e70bd945eb..45dcc5da6db 100644 --- a/pygmt/tests/test_clib.py +++ b/pygmt/tests/test_clib.py @@ -161,13 +161,8 @@ def test_call_module_error_message(): try: lib.call_module("info", "bogus-data.bla") except GMTCLibError as error: - msg = "\n".join( - [ - "Module 'info' failed with status code 71:", - "gmtinfo [ERROR]: Cannot find file bogus-data.bla", - ] - ) - assert str(error) == msg + assert "Module 'info' failed with status code" in str(error) + assert "gmtinfo [ERROR]: Cannot find file bogus-data.bla" in str(error) def test_method_no_session(): diff --git a/pygmt/tests/test_coast.py b/pygmt/tests/test_coast.py index 7b44ca2619c..1f83f94d71e 100644 --- a/pygmt/tests/test_coast.py +++ b/pygmt/tests/test_coast.py @@ -4,6 +4,7 @@ import pytest from .. import Figure +from ..helpers.testing import check_figures_equal @pytest.mark.mpl_image_compare @@ -25,15 +26,16 @@ def test_coast(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_coast_iceland(): "Test passing in R as a list" - fig = Figure() - fig.coast(R=[-30, -10, 60, 65], J="m1c", B=True, G="p28+r100") - return fig + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.coast(R="-30/-10/60/65", J="m1c", B="", G="p28+r100") + fig_test.coast( + region=[-30, -10, 60, 65], projection="m1c", frame=True, land="p28+r100" + ) + return fig_ref, fig_test @pytest.mark.mpl_image_compare diff --git a/pygmt/tests/test_colorbar.py b/pygmt/tests/test_colorbar.py index 593126208cc..f03002eb078 100644 --- a/pygmt/tests/test_colorbar.py +++ b/pygmt/tests/test_colorbar.py @@ -4,6 +4,7 @@ import pytest from .. import Figure +from ..helpers.testing import check_figures_equal @pytest.mark.mpl_image_compare @@ -37,18 +38,19 @@ def test_colorbar_positioned_using_map_coordinates(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_colorbar_positioned_using_justification_code(): """ Create colorbar at Top Center inside the map frame with length 2cm. """ - fig = Figure() - fig.basemap(region=[2, 4, 6, 8], projection="t0/2c", frame=True) - fig.colorbar(cmap="rainbow", position="jTC+w2c") - return fig + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.basemap(R="2/4/6/8", J="t0/2c", B="") + fig_ref.colorbar(C="rainbow", D="jTC+w2c") + + fig_test.basemap(region=[2, 4, 6, 8], projection="t0/2c", frame=True) + fig_test.colorbar(cmap="rainbow", position="jTC+w2c") + return fig_ref, fig_test @pytest.mark.mpl_image_compare diff --git a/pygmt/tests/test_grdcontour.py b/pygmt/tests/test_grdcontour.py index 2b45f2622d6..fee5e50d9db 100644 --- a/pygmt/tests/test_grdcontour.py +++ b/pygmt/tests/test_grdcontour.py @@ -9,6 +9,7 @@ from .. import Figure from ..exceptions import GMTInvalidInput from ..datasets import load_earth_relief +from ..helpers.testing import check_figures_equal TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @@ -21,49 +22,49 @@ def fixture_grid(): return load_earth_relief(registration="gridline") -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_grdcontour(grid): """Plot a contour image using an xarray grid with fixed contour interval """ - fig = Figure() - fig.grdcontour(grid, interval="1000", projection="W0/6i") - return fig + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(interval="1000", projection="W0/6i") + fig_ref.grdcontour("@earth_relief_01d_g", **kwargs) + fig_test.grdcontour(grid, **kwargs) + return fig_ref, fig_test -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_grdcontour_labels(grid): """Plot a contour image using a xarray grid with contour labels and alternate colors """ - fig = Figure() - fig.grdcontour( - grid, + fig_ref, fig_test = Figure(), Figure() + kwargs = dict( interval="1000", annotation="5000", projection="W0/6i", pen=["a1p,red", "c0.5p,black"], label_placement="d3i", ) - return fig + fig_ref.grdcontour("@earth_relief_01d_g", **kwargs) + fig_test.grdcontour(grid, **kwargs) + return fig_ref, fig_test -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_grdcontour_slice(grid): "Plot an contour image using an xarray grid that has been sliced" + + fig_ref, fig_test = Figure(), Figure() + grid_ = grid.sel(lat=slice(-30, 30)) - fig = Figure() - fig.grdcontour(grid_, interval="1000", projection="M6i") - return fig + kwargs = dict(interval="1000", projection="M6i") + fig_ref.grdcontour( + grid="@earth_relief_01d_g", region=[-180, 180, -30, 30], **kwargs + ) + fig_test.grdcontour(grid=grid_, **kwargs) + return fig_ref, fig_test @pytest.mark.mpl_image_compare @@ -81,14 +82,23 @@ def test_grdcontour_file(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_grdcontour_interval_file_full_opts(): """ Plot based on external contour level file """ - fig = Figure() - comargs = { + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + comargs_ref = { + "grid": "@earth_relief_10m", + "R": "-161.5/-154/18.5/23", + "C": TEST_CONTOUR_FILE, + "S": 100, + "J": "M6i", + "Q": 10, + } + fig_ref.grdcontour(**comargs_ref, L="-25000/-1", W=["a1p,blue", "c0.5p,blue"]) + fig_ref.grdcontour(**comargs_ref, L="0", W=["a1p,black", "c0.5p,black"]) + + comargs_test = { "region": [-161.5, -154, 18.5, 23], "interval": TEST_CONTOUR_FILE, "grid": "@earth_relief_10m", @@ -96,11 +106,12 @@ def test_grdcontour_interval_file_full_opts(): "projection": "M6i", "cut": 10, } + fig_test.grdcontour( + **comargs_test, limit=(-25000, -1), pen=["a1p,blue", "c0.5p,blue"] + ) + fig_test.grdcontour(**comargs_test, limit=0, pen=["a1p,black", "c0.5p,black"]) - fig.grdcontour(**comargs, limit=(-25000, -1), pen=["a1p,blue", "c0.5p,blue"]) - - fig.grdcontour(**comargs, limit="0", pen=["a1p,black", "c0.5p,black"]) - return fig + return fig_ref, fig_test def test_grdcontour_fails(): diff --git a/pygmt/tests/test_grdimage.py b/pygmt/tests/test_grdimage.py index d86798178f3..c784760be8f 100644 --- a/pygmt/tests/test_grdimage.py +++ b/pygmt/tests/test_grdimage.py @@ -1,15 +1,20 @@ """ Test Figure.grdimage """ +import sys import numpy as np import pytest import xarray as xr +from packaging.version import Version -from .. import Figure +from .. import Figure, clib from ..datasets import load_earth_relief from ..exceptions import GMTInvalidInput from ..helpers.testing import check_figures_equal +with clib.Session() as _lib: + gmt_version = Version(_lib.info["version"]) + @pytest.fixture(scope="module", name="grid") def fixture_grid(): @@ -69,12 +74,24 @@ def test_grdimage_file(): return fig -@pytest.mark.xfail(reason="Upstream bug in GMT 6.1.1") +@pytest.mark.skip( + reason="Upstream bug in GMT 6.1.1", + condition=gmt_version <= Version("6.1.1") and sys.platform == "darwin", +) @check_figures_equal() -def test_grdimage_xarray_shading(grid, fig_ref, fig_test): +@pytest.mark.parametrize( + "shading", + [True, 0.5, "+a30+nt0.8", "@earth_relief_01d_g+d", "@earth_relief_01d_g+a60+nt0.8"], +) +def test_grdimage_shading_xarray(grid, shading): """ Test that shading works well for xarray. - See https://github.com/GenericMappingTools/pygmt/issues/364 + + The ``shading`` can be True, a constant intensity, some modifiers, or + a grid with modifiers. + + See https://github.com/GenericMappingTools/pygmt/issues/364 and + https://github.com/GenericMappingTools/pygmt/issues/618. """ fig_ref, fig_test = Figure(), Figure() kwargs = dict( @@ -82,7 +99,7 @@ def test_grdimage_xarray_shading(grid, fig_ref, fig_test): frame=True, projection="Cyl_stere/6i", cmap="geo", - shading=True, + shading=shading, ) fig_ref.grdimage("@earth_relief_01d_g", **kwargs) diff --git a/pygmt/tests/test_info.py b/pygmt/tests/test_info.py index 92e7616adc6..e4ce734f7e6 100644 --- a/pygmt/tests/test_info.py +++ b/pygmt/tests/test_info.py @@ -8,17 +8,13 @@ import pandas as pd import pytest import xarray as xr -from packaging.version import Version -from .. import clib, info +from .. import info from ..exceptions import GMTInvalidInput TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") -with clib.Session() as _lib: - gmt_version = Version(_lib.info["version"]) - def test_info(): "Make sure info works on file name inputs" @@ -43,7 +39,6 @@ def test_info_dataframe(): @pytest.mark.xfail( - condition=gmt_version <= Version("6.1.1"), reason="UNIX timestamps returned instead of ISO datetime, should work on GMT 6.2.0 " "after https://github.com/GenericMappingTools/gmt/issues/4241 is resolved", ) @@ -63,7 +58,6 @@ def test_info_pandas_dataframe_time_column(): @pytest.mark.xfail( - condition=gmt_version <= Version("6.1.1"), reason="UNIX timestamp returned instead of ISO datetime, should work on GMT 6.2.0 " "after https://github.com/GenericMappingTools/gmt/issues/4241 is resolved", ) diff --git a/pygmt/tests/test_legend.py b/pygmt/tests/test_legend.py index 1fa98d6733a..2044e7bb2a7 100644 --- a/pygmt/tests/test_legend.py +++ b/pygmt/tests/test_legend.py @@ -6,6 +6,7 @@ from .. import Figure from ..exceptions import GMTInvalidInput from ..helpers import GMTTempFile +from ..helpers.testing import check_figures_equal @pytest.mark.mpl_image_compare @@ -44,32 +45,42 @@ def test_legend_default_position(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_legend_entries(): """ Test different marker types/shapes. """ + fig_ref, fig_test = Figure(), Figure() - fig = Figure() - - fig.basemap(projection="x1i", region=[0, 7, 3, 7], frame=True) + # Use single-character arguments for the reference image + fig_ref = Figure() + fig_ref.basemap(J="x1i", R="0/7/3/7", B="") + fig_ref.plot( + data="@Table_5_11.txt", + S="c0.15i", + G="lightgreen", + W="faint", + l="Apples", + ) + fig_ref.plot(data="@Table_5_11.txt", W="1.5p,gray", l='"My lines"') + fig_ref.plot(data="@Table_5_11.txt", S="t0.15i", G="orange", l="Oranges") + fig_ref.legend(D="JTR+jTR") - fig.plot( + fig_test.basemap(projection="x1i", region=[0, 7, 3, 7], frame=True) + fig_test.plot( data="@Table_5_11.txt", style="c0.15i", color="lightgreen", pen="faint", - l="Apples", + label="Apples", ) - fig.plot(data="@Table_5_11.txt", pen="1.5p,gray", label='"My lines"') - fig.plot(data="@Table_5_11.txt", style="t0.15i", color="orange", label="Oranges") - - fig.legend(position="JTR+jTR") + fig_test.plot(data="@Table_5_11.txt", pen="1.5p,gray", label='"My lines"') + fig_test.plot( + data="@Table_5_11.txt", style="t0.15i", color="orange", label="Oranges" + ) + fig_test.legend(position="JTR+jTR") - return fig + return fig_ref, fig_test @pytest.mark.mpl_image_compare diff --git a/pygmt/tests/test_logo.py b/pygmt/tests/test_logo.py index d4185f115c2..6cfc7f4cd0e 100644 --- a/pygmt/tests/test_logo.py +++ b/pygmt/tests/test_logo.py @@ -1,32 +1,30 @@ """ Tests for fig.logo """ -import pytest - from .. import Figure -from ..exceptions import GMTInvalidInput +from ..helpers.testing import check_figures_equal -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_logo(): "Plot a GMT logo of a 2 inch width as a stand-alone plot" - fig = Figure() - fig.logo(D="x0/0+w2i") - return fig + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.logo(D="x0/0+w2i") + fig_test.logo(position="x0/0+w2i") + return fig_ref, fig_test -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_logo_on_a_map(): "Plot a GMT logo in the upper right corner of a map" - fig = Figure() - fig.coast(region=[-90, -70, 0, 20], projection="M6i", land="chocolate", frame=True) - fig.logo(D="jTR+o0.1i/0.1i+w3i", F=True) - return fig - + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.coast(R="-90/-70/0/20", J="M6i", G="chocolate", B="") + fig_ref.logo(D="jTR+o0.1i/0.1i+w3i", F="") -def test_logo_fails(): - "Make sure logo raises an exception when D is not given" - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.logo() - return fig + fig_test.coast( + region=[-90, -70, 0, 20], projection="M6i", land="chocolate", frame=True + ) + fig_test.logo(position="jTR+o0.1i/0.1i+w3i", box=True) + return fig_ref, fig_test diff --git a/pygmt/tests/test_makecpt.py b/pygmt/tests/test_makecpt.py index 105b6e75ed4..3c1716a1f8d 100644 --- a/pygmt/tests/test_makecpt.py +++ b/pygmt/tests/test_makecpt.py @@ -10,6 +10,7 @@ from ..datasets import load_earth_relief from ..exceptions import GMTInvalidInput from ..helpers import GMTTempFile +from ..helpers.testing import check_figures_equal TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") @@ -62,19 +63,21 @@ def test_makecpt_to_plot_grid(grid): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_makecpt_to_plot_grid_scaled_with_series(grid): """ Use static color palette table scaled to a min/max series to change color of grid """ - fig = Figure() + # Use single-character arguments for the reference image + fig_ref = Figure() + makecpt(C="oleron", T="-4500/4500") + fig_ref.grdimage(grid, J="W0/6i") + + fig_test = Figure() makecpt(cmap="oleron", series="-4500/4500") - fig.grdimage(grid, projection="W0/6i") - return fig + fig_test.grdimage(grid, projection="W0/6i") + return fig_ref, fig_test def test_makecpt_output_to_cpt_file(): diff --git a/pygmt/tests/test_plot.py b/pygmt/tests/test_plot.py index 561d86fde4a..196cf46037f 100644 --- a/pygmt/tests/test_plot.py +++ b/pygmt/tests/test_plot.py @@ -13,6 +13,8 @@ from .. import Figure from ..exceptions import GMTInvalidInput +from ..helpers import GMTTempFile +from ..helpers.testing import check_figures_equal TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @@ -126,11 +128,21 @@ def test_plot_projection(data): return fig -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_plot_colors(data, region): - "Plot the data using z as sizes" - fig = Figure() - fig.plot( + "Plot the data using z as colors" + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.plot( + data=POINTS_DATA, + R="/".join(map(str, region)), + J="X3i", + S="c0.5c", + C="cubhelix", + B="af", + ) + + fig_test.plot( x=data[:, 0], y=data[:, 1], color=data[:, 2], @@ -140,7 +152,7 @@ def test_plot_colors(data, region): cmap="cubhelix", frame="af", ) - return fig + return fig_ref, fig_test @pytest.mark.mpl_image_compare @@ -194,6 +206,104 @@ def test_plot_colors_sizes_proj(data, region): return fig +@check_figures_equal() +def test_plot_transparency(): + "Plot the data with a constant transparency" + x = np.arange(1, 10) + y = np.arange(1, 10) + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y], fmt="%d") + fig_ref.plot( + data=tmpfile.name, S="c0.2c", G="blue", t=80.0, R="0/10/0/10", J="X4i", B="" + ) + + fig_test.plot( + x=x, + y=y, + region=[0, 10, 0, 10], + projection="X4i", + frame=True, + style="c0.2c", + color="blue", + transparency=80.0, + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot_varying_transparency(): + "Plot the data using z as transparency" + x = np.arange(1, 10) + y = np.arange(1, 10) + z = np.arange(1, 10) * 10 + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y, z], fmt="%d") + fig_ref.plot( + data=tmpfile.name, + R="0/10/0/10", + J="X4i", + B="", + S="c0.2c", + G="blue", + t="", + ) + + fig_test.plot( + x=x, + y=y, + region=[0, 10, 0, 10], + projection="X4i", + frame=True, + style="c0.2c", + color="blue", + transparency=z, + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot_sizes_colors_transparencies(): + "Plot the data using z as transparency" + x = np.arange(1.0, 10.0) + y = np.arange(1.0, 10.0) + color = np.arange(1, 10) * 0.15 + size = np.arange(1, 10) * 0.2 + transparency = np.arange(1, 10) * 10 + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y, color, size, transparency]) + fig_ref.plot( + data=tmpfile.name, + R="0/10/0/10", + J="X4i", + B="", + S="cc", + C="gray", + t="", + ) + fig_test.plot( + x=x, + y=y, + region=[0, 10, 0, 10], + projection="X4i", + frame=True, + style="cc", + color=color, + sizes=size, + cmap="gray", + transparency=transparency, + ) + return fig_ref, fig_test + + @pytest.mark.mpl_image_compare def test_plot_matrix(data): "Plot the data passing in a matrix and specifying columns" diff --git a/requirements-dev.txt b/requirements-dev.txt index 76352e17224..f6a53ef0310 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,6 +7,7 @@ pytest-cov pytest-mpl coverage black +blackdoc pylint flake8 sphinx=2.2.1 diff --git a/versioneer.py b/versioneer.py index d3db64315bc..06bd328e436 100644 --- a/versioneer.py +++ b/versioneer.py @@ -165,7 +165,7 @@ ## Known Limitations Some situations are known to cause problems for Versioneer. This details the -most significant ones. More can be found on Github +most significant ones. More can be found on GitHub [issues page](https://github.com/warner/python-versioneer/issues). ### Subprojects