diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..cf87526d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Set default behaviour to automatically normalize line endings. +* text=auto + +# Force bash scripts to always use lf line endings so that if a repo is accessed +# in Unix via a file share from Windows, the scripts will work. +*.sh text eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 6c71e9db..236bcb9a 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -7,7 +7,7 @@ assignees: '' --- -Please search existing issues to avoid duplicates. + ## Details @@ -28,5 +28,5 @@ inspectdr | | | ### Versions Plots.jl version: -Backend version: +Backend version (`]st -m `): Output of `versioninfo()`: diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index 68dbe39c..d4d64580 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -2,23 +2,16 @@ name: CompatHelper on: schedule: - - cron: '00 * * * *' + - cron: '00 00 * * *' jobs: CompatHelper: - runs-on: ${{ matrix.os }} - strategy: - matrix: - julia-version: [1.2.0] - julia-arch: [x86] - os: [ubuntu-latest] + runs-on: ubuntu-latest steps: - - uses: julia-actions/setup-julia@latest - with: - version: ${{ matrix.julia-version }} - name: Pkg.add("CompatHelper") run: julia -e 'using Pkg; Pkg.add("CompatHelper")' - name: CompatHelper.main() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} # optional run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/SnoopCompile.yml b/.github/workflows/SnoopCompile.yml new file mode 100644 index 00000000..3825cf11 --- /dev/null +++ b/.github/workflows/SnoopCompile.yml @@ -0,0 +1,89 @@ +name: SnoopCompile + +on: + push: + branches: + # - 'master' # NOTE: to run the bot only on pushes to master + +defaults: + run: + shell: bash + +jobs: + SnoopCompile: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + env: + GKS_ENCODING: "utf8" + GKSwstype: "100" + PLOTS_TEST: "true" + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: # NOTE: the versions below should match those in your botconfig + - '1' + os: # NOTE: should match the os setting of your botconfig + - ubuntu-latest + arch: + - x64 + steps: + # Setup environment + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.version }} + + - name: Install dependencies + run: | + julia --project -e 'using Pkg; Pkg.instantiate();' + julia -e 'using Pkg; Pkg.add( PackageSpec(name = "CompileBot", version = "1") ); Pkg.develop(PackageSpec(; path=pwd())); using CompileBot; CompileBot.addtestdep();' + + + # TESTCMD + - name: Default TESTCMD + run: echo "TESTCMD=julia" >> $GITHUB_ENV + - name: Ubuntu TESTCMD + if: startsWith(matrix.os,'ubuntu') + run: echo "TESTCMD=xvfb-run --auto-servernum julia" >> $GITHUB_ENV + + # Generate precompile files + - name: Generating precompile files + run: $TESTCMD --project -e 'include("deps/SnoopCompile/snoop_bot.jl")' # NOTE: must match path + + # Run benchmarks + - name: Running Benchmark + run: $TESTCMD --project -e 'include("deps/SnoopCompile/snoop_bench.jl")' # NOTE: optional, if have benchmark file + + - name: Upload all + uses: actions/upload-artifact@v2.0.1 + with: + path: ./ + + Create_PR: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + needs: SnoopCompile + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Download all + uses: actions/download-artifact@v2 + + - name: CompileBot postprocess + run: julia -e 'using Pkg; Pkg.add( PackageSpec(name = "CompileBot", version = "1") ); using CompileBot; CompileBot.postprocess();' + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: Update precompile_*.jl file + title: "[AUTO] Update precompiles" + labels: SnoopCompile + branch: "Test_SnoopCompile_AutoPR_${{ github.ref }}" + + + Skip: + if: "contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ubuntu-latest + steps: + - name: Skip CI 🚫 + run: echo skip ci diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index d77d3a0c..778c06fe 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -1,9 +1,12 @@ name: TagBot on: - schedule: - - cron: 0 * * * * + issue_comment: + types: + - created + workflow_dispatch: jobs: TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' runs-on: ubuntu-latest steps: - uses: JuliaRegistries/TagBot@v1 diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..ccc7bb31 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,34 @@ +name: Run benchmarks + +on: + pull_request: + +jobs: + Benchmark: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + env: + GKS_ENCODING: "utf8" + GKSwstype: "100" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@latest + with: + version: 1 + + ## Setup + - name: Ubuntu TESTCMD + run: echo "TESTCMD=xvfb-run --auto-servernum julia" >> $GITHUB_ENV + - name: Install Plots dependencies + uses: julia-actions/julia-buildpkg@latest + - name: Install Benchmarking dependencies + run: julia -e 'using Pkg; pkg"add PkgBenchmark BenchmarkCI@0.1"' + + - name: Run benchmarks + run: $TESTCMD -e 'using BenchmarkCI; BenchmarkCI.judge()' + - name: Print judgement + run: julia -e 'using BenchmarkCI; BenchmarkCI.displayjudgement()' + - name: Post results + run: julia -e 'using BenchmarkCI; BenchmarkCI.postjudge()' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..45a723a9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,89 @@ +name: ci + +on: + push: + pull_request: + +defaults: + run: + shell: bash + +jobs: + CI: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + env: + GKS_ENCODING: "utf8" + GKSwstype: "100" + + name: Julia ${{ matrix.version }} - ${{ matrix.os }} + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.version == 'nightly' }} + strategy: + fail-fast: false + matrix: + version: + - '1' + - 'nightly' + os: + - ubuntu-latest + - windows-latest + - macos-latest + arch: + - x64 + # - x86 + + steps: + + # Setup environment + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.version }} + - name: Cache artifacts + uses: actions/cache@v1 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + + ## maybe required if we ever want to run graphical tests for plotly + # OS Dependencies + # - name: Ubuntu OS dependencies + # if: startsWith(matrix.os,'ubuntu') + # run: | + # ./test/install_wkhtmltoimage.sh + + # TESTCMD + - name: Default TESTCMD + run: echo "TESTCMD=julia" >> $GITHUB_ENV + - name: Ubuntu TESTCMD + if: startsWith(matrix.os,'ubuntu') + run: echo "TESTCMD=xvfb-run --auto-servernum julia" >> $GITHUB_ENV + + # Julia Dependencies + - name: Install Julia dependencies + uses: julia-actions/julia-buildpkg@latest + + # Run tests + - name: Run Graphical test + run: | + $TESTCMD --project -e 'using Pkg; Pkg.test(coverage=true);' + $TESTCMD -e 'using Pkg; Pkg.activate(tempdir()); Pkg.develop(path=abspath(".")); Pkg.add("StatsPlots"); Pkg.test("StatsPlots");' + $TESTCMD -e 'using Pkg; Pkg.activate(tempdir()); Pkg.develop(path=abspath(".")); Pkg.add("GraphRecipes"); Pkg.test("GraphRecipes");' + + - name: Codecov + uses: julia-actions/julia-uploadcodecov@latest + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + Skip: + if: "contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ubuntu-latest + steps: + - name: Skip CI 🚫 + run: echo skip ci diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..e1dbcf1a --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,50 @@ +name: docs + +on: + push: + branches: + - master + tags: '*' + +jobs: + Build_docs: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + with: + repository: JuliaPlots/PlotDocs.jl + - uses: julia-actions/setup-julia@v1 + - name: Cache artifacts + uses: actions/cache@v1 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - name: Install dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y qt5-default \ + ttf-mscorefonts-installer \ + poppler-utils \ + pdf2svg \ + texlive-latex-base \ + texlive-binaries \ + texlive-pictures \ + texlive-latex-extra \ + texlive-luatex \ + ghostscript-x \ + libgconf2-4 + sudo fc-cache -vr + + - name: build documentation + env: + PYTHON: "" + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} + run: | + xvfb-run julia --color=yes --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.add(PackageSpec(name="Plots", rev=split(ENV["GITHUB_REF"], "/", limit=3)[3])); Pkg.instantiate()' + xvfb-run julia --color=yes --project=docs/ -e 'withenv("GITHUB_REPOSITORY" => "JuliaPlots/PlotDocs.jl") do; include("docs/make.jl"); end' diff --git a/.gitignore b/.gitignore index 6e5cdbe6..48ae0045 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,12 @@ .DS_Store examples/.ipynb_checkpoints/* examples/meetup/.ipynb_checkpoints/* -deps/plotly-latest.min.js +deps/plotly-* deps/build.log deps/deps.jl Manifest.toml dev/ test/tmpplotsave.hdf5 +/.benchmarkci +/benchmark/*.json +.vscode/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ccd58dfb..00000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -# Documentation: http://docs.travis-ci.com/user/languages/julia/ -language: julia -os: - - linux - # - osx -julia: - - 1.0 - - 1 - - nightly - -matrix: - allow_failures: - - julia: nightly - -addons: - apt: - packages: - - at-spi2-core - - libgtk-3-dev - - xauth - - xvfb - -env: - - GKS_ENCODING="utf8" - -cache: - directories: - - $HOME/.julia/artifacts - -sudo: required -before_install: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pwd ; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./test/install_wkhtmltoimage.sh ; fi - -notifications: - email: true - -after_success: - - julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' - -script: - - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - if [[ `uname` = "Linux" ]]; then TESTCMD="xvfb-run julia"; else TESTCMD="julia"; fi - - $TESTCMD -e 'using Pkg; Pkg.build(); Pkg.test(coverage=true)' diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 00000000..1870811d --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,697 @@ +{ + "title": "Plots.jl", + "license": "MIT", + "creators": [ + { + "affiliation": "Elemental Cognition", + "name": "Tom Breloff" + } + ], + "contributors":[ + { + "affiliation": "TU Wien", + "name": "Daniel Schwabeneder", + "orcid": "0000-0002-0412-0777", + "type": "ProjectLeader" + }, + { + "affiliation": "GLOBE Institute", + "name": "Michael Krabbe Borregaard", + "type": "ProjectLeader" + }, + { + "affiliation": "Leibniz Universit\u00e4t Hannover", + "name": "Simon Christ", + "orcid": "0000-0002-5866-1472", + "type": "ProjectLeader" + }, + { + "affiliation": "Forschungszentrum J\u00fclich", + "name": "Josef Heinen", + "orcid": "0000-0001-6509-1925", + "type": "ProjectMember" + }, + { + "name": "Yuval", + "type": "Other" + }, + { + "name": "Andrew Palugniok", + "type": "ProjectMember" + }, + { + "affiliation": "@beacon-biosignals", + "name": "Simon Danisch", + "type": "Other" + }, + { + "affiliation": "Veos Digital (https://veos.digital/)", + "name": "Pietro Vertechi", + "type": "ProjectMember" + }, + { + "affiliation": "Korea Advanced Inst. of Science and Technology (KAIST)", + "name": "Zhanibek Omarov", + "type": "ProjectMember", + "orcid": "0000-0002-8783-8791" + }, + { + "name": "Thatcher Chamberlin", + "type": "Other" + }, + { + "name": "@ma-laforge", + "type": "ProjectMember" + }, + { + "affiliation": "Massachusetts Institute of Technology", + "name": "Christopher Rackauckas", + "orcid": "0000-0001-5850-0663", + "type": "Other" + }, + { + "affiliation": "Max Planck Institute for Physics", + "name": "Oliver Schulz", + "type": "Other" + }, + { + "affiliation": "@JuliaComputing", + "name": "Sebastian Pfitzner", + "type": "Other" + }, + { + "name": "Takafumi Arakaki", + "type": "Other" + }, + { + "affiliation": "University of Manitoba", + "name": "Amin Yahyaabadi", + "type": "Other" + }, + { + "name": "Jack Devine", + "type": "Other" + }, + { + "name": "Sebastian Pech", + "type": "Other" + }, + { + "affiliation": "@JuliaComputing", + "name": "Patrick Kofod Mogensen", + "type": "Other", + "orcid": "0000-0002-4910-1932" + }, + { + "name": "Samuel S. Watson", + "type": "Other" + }, + { + "affiliation": "UC Davis", + "name": "Naoki Saito", + "orcid": "0000-0001-5234-4719", + "type": "Other" + }, + { + "affiliation": "University of Southern California (USC)", + "name": "Benoit Pasquier", + "orcid": "0000-0002-3838-5976", + "type": "Other" + }, + { + "affiliation": "NTNU Trondheim", + "name": "Ronny Bergmann", + "orcid": "0000-0001-8342-7218", + "type": "Other" + }, + { + "name": "Andy Nowacki", + "affiliation": "University of Leeds", + "orcid": "0000-0001-7669-7383", + "type": "Other" + }, + { + "name": "Ian Butterworth", + "type": "Other" + }, + { + "affiliation": "Lund University", + "name": "David Gustavsson", + "type": "Other" + }, + { + "name": "Anshul Singhvi", + "type": "Other" + }, + { + "name": "david-macmahon", + "type": "Other" + }, + { + "name": "Fredrik Ekre", + "type": "Other" + }, + { + "name": "Maaz Bin Tahir Saeed", + "type": "Other" + }, + { + "name": "Kristoffer Carlsson", + "type": "Other" + }, + { + "name": "Will Kearney", + "type": "Other" + }, + { + "name": "Niklas Korsbo", + "type": "Other" + }, + { + "name": "Miles Lucas", + "type": "Other" + }, + { + "name": "@Godisemo", + "type": "Other" + }, + { + "name": "Florian Oswald", + "type": "Other" + }, + { + "name": "Diego Javier Zea", + "type": "Other" + }, + { + "name": "@WillRam", + "type": "Other" + }, + { + "name": "Fedor Bezrukov", + "type": "Other" + }, + { + "name": "Spencer Lyon", + "type": "Other" + }, + { + "name": "Darwin Darakananda", + "type": "Other" + }, + { + "name": "Lukas Hauertmann", + "type": "Other" + }, + { + "name": "Huckleberry Febbo", + "type": "Other" + }, + { + "name": "@H-M-H", + "type": "Other" + }, + { + "name": "Josh Day", + "type": "Other" + }, + { + "name": "@wfgra", + "type": "Other" + }, + { + "name": "Sheehan Olver", + "type": "Other" + }, + { + "name": "Jerry Ling", + "type": "Other" + }, + { + "name": "Jks Liu", + "type": "Other" + }, + { + "name": "Seth Axen", + "type": "Other" + }, + { + "name": "@o01eg", + "type": "Other" + }, + { + "name": "Sebastian Micluța-Câmpeanu", + "type": "Other" + }, + { + "name": "Tim Holy", + "type": "Other" + }, + { + "name": "Tony Kelman", + "type": "Other" + }, + { + "name": "Antoine Levitt", + "type": "Other" + }, + { + "name": "Iblis Lin", + "type": "Other" + }, + { + "name": "Harry Scholes", + "type": "Other" + }, + { + "name": "@djsegal", + "type": "Other" + }, + { + "name": "Goran Nakerst", + "type": "Other" + }, + { + "name": "Felix Hagemann", + "type": "Other" + }, + { + "name": "Matthieu Gomez", + "type": "Other" + }, + { + "name": "@biggsbiggsby", + "type": "Other" + }, + { + "name": "Jonathan Anderson", + "type": "Other" + }, + { + "name": "Michael Kraus", + "type": "Other" + }, + { + "name": "Carlo Lucibello", + "type": "Other" + }, + { + "name": "Robin Deits", + "type": "Other" + }, + { + "name": "Misha Mkhasenko", + "type": "Other" + }, + { + "name": "Benoît Legat", + "type": "Other" + }, + { + "name": "Steven G. Johnson", + "type": "Other" + }, + { + "name": "John Verzani", + "type": "Other" + }, + { + "name": "Mattias Fält", + "type": "Other" + }, + { + "name": "Rashika Karki", + "type": "Other" + }, + { + "name": "Morten Piibeleht", + "type": "Other" + }, + { + "name": "Filippo Vicentini", + "type": "Other" + }, + { + "name": "David Anthoff", + "type": "Other" + }, + { + "name": "Leon Wabeke", + "type": "Other" + }, + { + "name": "Yusuke Kominami", + "type": "Other" + }, + { + "name": "Oscar Dowson", + "type": "Other" + }, + { + "name": "Max G", + "type": "Other" + }, + { + "name": "Fabian Greimel", + "type": "Other" + }, + { + "name": "Jérémy", + "type": "Other" + }, + { + "name": "Pearl Li", + "type": "Other" + }, + { + "name": "David P. Sanders", + "type": "Other" + }, + { + "name": "Asbjørn Nilsen Riseth", + "type": "Other" + }, + { + "name": "Jan Weidner", + "type": "Other" + }, + { + "name": "@jakkor2", + "type": "Other" + }, + { + "name": "Pablo Zubieta", + "type": "Other" + }, + { + "name": "Hamza Yusuf Çakır", + "type": "Other" + }, + { + "name": "John Rinehart", + "type": "Other" + }, + { + "name": "Martin Biel", + "type": "Other" + }, + { + "name": "Moritz Schauer", + "type": "Other" + }, + { + "name": "Moesè Giodano", + "type": "Other" + }, + { + "name": "@olegshtch", + "type": "Other" + }, + { + "name": "Leon Shen", + "type": "Other" + }, + { + "name": "Jeff Fessler", + "type": "Other" + }, + { + "name": "@hustf", + "type": "Other" + }, + { + "name": "Asim H Dar", + "type": "Other" + }, + { + "name": "@8uurg", + "type": "Other" + }, + { + "name": "Abel Siqueira", + "type": "Other" + }, + { + "name": "Adrian Dawid", + "type": "Other" + }, + { + "name": "Alberto Lusiani", + "type": "Other" + }, + { + "name": "Balázs Mezei", + "type": "Other" + }, + { + "name": "Ben Ide", + "type": "Other" + }, + { + "name": "Benjamin Lungwitz", + "type": "Other" + }, + { + "affiliation": "University of Graz", + "name": "Bernd Riederer", + "type": "Other", + "orcid": "0000-0001-8390-0087" + }, + { + "name": "Christina Lee", + "type": "Other" + }, + { + "name": "Christof Stocker", + "type": "Other" + }, + { + "name": "Christoph Finkensiep", + "type": "Other" + }, + { + "name": "@Cornelius-G", + "type": "Other" + }, + { + "name": "Daniel Høegh", + "type": "Other" + }, + { + "name": "Denny Biasiolli", + "type": "Other" + }, + { + "name": "Dieter Castel", + "type": "Other" + }, + { + "name": "Elliot Saba", + "type": "Other" + }, + { + "name": "Fengyang Wang", + "type": "Other" + }, + { + "name": "Fons van der Plas", + "type": "Other" + }, + { + "name": "Fredrik Bagge Carlson", + "type": "Other" + }, + { + "name": "Graham Smith", + "type": "Other" + }, + { + "name": "Hayato Ikoma", + "type": "Other" + }, + { + "name": "Hessam Mehr", + "type": "Other" + }, + { + "name": "@InfiniteChai", + "type": "Other" + }, + { + "name": "Jack Dunn", + "type": "Other" + }, + { + "name": "Jeff Bezanson", + "type": "Other" + }, + { + "name": "Jeff Eldredge", + "type": "Other" + }, + { + "name": "Jinay Jain", + "type": "Other" + }, + { + "name": "Johan Blåbäck", + "type": "Other" + }, + { + "name": "@jmert", + "type": "Other" + }, + { + "name": "Lakshya Khatri", + "type": "Other" + }, + { + "name": "Lia Siegelmann", + "type": "Other" + }, + { + "name": "@marekkukan-tw", + "type": "Other" + }, + { + "affiliation": "ETH Zurich", + "name": "Mauro Werder", + "type": "Other", + "orcid": "0000-0003-0137-9377" + }, + { + "name": "Maxim Grechkin", + "type": "Other" + }, + { + "name": "Michael Cawte", + "type": "Other" + }, + { + "name": "@milesfrain", + "type": "Other" + }, + { + "name": "Nicholas Bauer", + "type": "Other" + }, + { + "name": "Nicolau Leal Werneck", + "type": "Other" + }, + { + "name": "@nilshg", + "type": "Other" + }, + { + "name": "Oliver Evans", + "type": "Other" + }, + { + "name": "Peter Gagarinov", + "type": "Other" + }, + { + "name": "Páll Haraldsson", + "type": "Other" + }, + { + "name": "Rik Huijzer", + "type": "Other" + }, + { + "name": "Romain Franconville", + "type": "Other" + }, + { + "name": "Ronan Pigott", + "type": "Other" + }, + { + "name": "Roshan Shariff", + "type": "Other" + }, + { + "name": "Scott Thomas", + "type": "Other" + }, + { + "name": "Sebastian Rollén", + "type": "Other" + }, + { + "name": "Seth Bromberger", + "type": "Other" + }, + { + "name": "Siva Swaminathan", + "type": "Other" + }, + { + "name": "Tim DuBois", + "type": "Other" + }, + { + "name": "Travis DePrato", + "type": "Other" + }, + { + "name": "Will Thompson", + "type": "Other" + }, + { + "name": "Yakir Luc Gagnon", + "type": "Other" + }, + { + "name": "Benjamin Chislett", + "type": "Other" + }, + { + "name": "@hhaensel", + "type": "Other" + }, + { + "name": "@improbable22", + "type": "Other" + }, + { + "name": "Johannes Fleck", + "type": "Other" + }, + { + "name": "Peter Czaban", + "type": "Other" + }, + { + "name": "@innerlee", + "type": "Other" + }, + { + "name": "Mats Cronqvist", + "type": "Other" + }, + { + "name": "Shi Pengcheng", + "type": "Other" + }, + { + "name": "@wg030", + "type": "Other" + }, + { + "affiliation": "University of Cambridge", + "name": "Will Tebbutt", + "type": "Other" + }, + { + "name": "@t-bltg", + "type": "Other" + } + { + "name": "Fred Callaway", + "type": "Other" + } + ], + "upload_type": "software" +} diff --git a/Project.toml b/Project.toml index 56b2d845..8a90b5fd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Plots" uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" author = ["Tom Breloff (@tbreloff)"] -version = "1.2.2" +version = "1.16.8" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" @@ -10,12 +10,12 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" FFMPEG = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" -GeometryTypes = "4d00f742-c7ba-57c2-abde-4428a4b178cb" +GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Measures = "442fdcdd-2543-5da2-b0f3-8c86c306513e" NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PlotThemes = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a" PlotUtils = "995b91a9-d308-5afd-9ec6-746e21dbc043" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" @@ -25,6 +25,7 @@ RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" RecipesPipeline = "01d81517-befc-4cb6-b9ec-a95719d0359c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" +Scratch = "6c6a2e73-6563-6170-7368-637461726353" Showoff = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" @@ -33,27 +34,28 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] Contour = "0.5" -FFMPEG = "0.2, 0.3" +FFMPEG = "0.2, 0.3, 0.4" FixedPointNumbers = "0.6, 0.7, 0.8" -GR = "0.46, 0.47, 0.48, 0.49" -GeometryTypes = "0.7, 0.8" -JSON = "0.21" +GR = "0.53, 0.54, 0.55, 0.57" +GeometryBasics = "0.2, 0.3.1" +JSON = "0.21, 1" +Latexify = "0.14, 0.15" Measures = "0.3" NaNMath = "0.3" -PGFPlotsX = "1.2.0" PlotThemes = "2" PlotUtils = "1" RecipesBase = "1" -RecipesPipeline = "0.1.3" -Reexport = "0.2" -Requires = "0.5, 1" -Showoff = "0.3.1" +RecipesPipeline = "0.3" +Reexport = "0.2, 1.0" +Requires = "1" +Scratch = "1" +Showoff = "0.3.1, 1.0" StatsBase = "0.32, 0.33" -julia = "1" +julia = "1.5" [extras] +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" -GeometryTypes = "4d00f742-c7ba-57c2-abde-4428a4b178cb" Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44" HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" @@ -61,13 +63,15 @@ Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" PGFPlotsX = "8314cec4-20b6-5062-9cdb-752b83310925" +PlotlyJS = "f0f68f2c-4968-5e81-91da-67840de0976a" RDatasets = "ce6b1742-4840-55fa-b093-852dadbb1d8b" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990" UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92" [targets] -test = ["FileIO", "GeometryTypes", "Gtk", "ImageMagick", "Images", "LibGit2", "OffsetArrays", "PGFPlotsX", "HDF5", "Random", "RDatasets", "StaticArrays", "StatsPlots", "Test", "UnicodePlots", "VisualRegressionTests"] +test = ["Distributions", "FileIO", "Gtk", "ImageMagick", "Images", "LibGit2", "OffsetArrays", "PGFPlotsX", "PlotlyJS", "HDF5", "RDatasets", "StableRNGs", "StaticArrays", "StatsPlots", "Test", "TestImages", "UnicodePlots", "VisualRegressionTests"] diff --git a/README.md b/README.md index 1e6cef98..a097683e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@ # Plots -[travis-img]: https://img.shields.io/travis/JuliaPlots/Plots.jl?logo=travis -[travis-url]: https://travis-ci.org/JuliaPlots/Plots.jl - -[appveyor-img]: https://ci.appveyor.com/api/projects/status/github/juliaplots/plots.jl?branch=master&svg=true -[appveyor-url]: https://ci.appveyor.com/project/mkborregaard/plots-jl +[gh-ci-img]: https://github.com/JuliaPlots/Plots.jl/workflows/ci/badge.svg?branch=master +[gh-ci-url]: https://github.com/JuliaPlots/Plots.jl/actions?query=workflow%3Aci [pkgeval-img]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/P/Plots.svg [pkgeval-url]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html @@ -15,13 +12,16 @@ [docs-img]: https://img.shields.io/badge/docs-stable-blue.svg [docs-url]: http://docs.juliaplots.org/latest/ -[![][travis-img]][travis-url] -[![][appveyor-img]][appveyor-url] +[![][gh-ci-img]][gh-ci-url] [![][pkgeval-img]][pkgeval-url] [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://julialang.zulipchat.com/#narrow/stream/236493-plots) [![][docs-img]][docs-url] [![Codecov](https://codecov.io/gh/JuliaPlots/Plots.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaPlots/Plots.jl) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4725317.svg)](https://doi.org/10.5281/zenodo.4725317) +This is the DOI for all Versions, please follow the link to get the DOI for a specific version. + + #### Created by Tom Breloff (@tbreloff) #### Maintained by the [JuliaPlots members](https://github.com/orgs/JuliaPlots/people) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 081e56df..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,46 +0,0 @@ -environment: - matrix: - - julia_version: 1.0 - - julia_version: 1 - - julia_version: nightly - -platform: - - x86 # 32-bit - - x64 # 64-bit - -# # Uncomment the following lines to allow failures on nightly julia -# # (tests will run but not make your overall status red) -matrix: - allow_failures: - - julia_version: nightly - -branches: - only: - - master - - /release-.*/ - -cache: - - '%USERPROFILE%\.julia\artifacts' - -notifications: - - provider: Email - on_build_success: false - on_build_failure: false - on_build_status_changed: false - -install: - - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) - -build_script: - - echo "%JL_TEST_SCRIPT%" - - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" - -test_script: - - echo "%JL_TEST_SCRIPT%" - - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" - -# # Uncomment to support code coverage upload. Should only be enabled for packages -# # which would have coverage gaps without running on Windows -# on_success: -# - echo "%JL_CODECOV_SCRIPT%" -# - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" diff --git a/benchmark/Project.toml b/benchmark/Project.toml new file mode 100644 index 00000000..d0c35ad5 --- /dev/null +++ b/benchmark/Project.toml @@ -0,0 +1,5 @@ +[deps] +BenchmarkCI = "20533458-34a3-403d-a444-e18f38190b5b" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +PkgBenchmark = "32113eaa-f34f-5b0d-bd6c-c81e245fc73d" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl new file mode 100644 index 00000000..ae1d09de --- /dev/null +++ b/benchmark/benchmarks.jl @@ -0,0 +1,10 @@ +using BenchmarkTools +using Plots + +const SUITE = BenchmarkGroup() +julia_cmd = split(get(ENV, "TESTCMD", Base.JLOptions().julia_bin)) + +SUITE["load_plot_display"] = @benchmarkable run(`$julia_cmd --startup-file=no --project -e 'using Plots; display(plot(1:0.1:10, sin.(1:0.1:10)))'`) +SUITE["load"] = @benchmarkable run(`$julia_cmd --startup-file=no --project -e 'using Plots'`) +SUITE["plot"] = @benchmarkable p = plot(1:0.1:10, sin.(1:0.1:10)) samples=1 evals=1 +SUITE["display"] = @benchmarkable display(p) setup=(p = plot(1:0.1:10, sin.(1:0.1:10))) samples=1 evals=1 diff --git a/deps/SnoopCompile/precompile/precompile_Plots.jl b/deps/SnoopCompile/precompile/precompile_Plots.jl new file mode 100644 index 00000000..1dcbccd1 --- /dev/null +++ b/deps/SnoopCompile/precompile/precompile_Plots.jl @@ -0,0 +1,431 @@ +# Use +# @warnpcfail precompile(args...) +# if you want to be warned when a precompile directive fails +macro warnpcfail(ex::Expr) + modl = __module__ + file = __source__.file === nothing ? "?" : String(__source__.file) + line = __source__.line + quote + $(esc(ex)) || @warn """precompile directive + $($(Expr(:quote, ex))) + failed. Please report an issue in $($modl) (after checking for duplicates) or remove this directive.""" _file=$file _line=$line + end +end + + +const __bodyfunction__ = Dict{Method,Any}() + +# Find keyword "body functions" (the function that contains the body +# as written by the developer, called after all missing keyword-arguments +# have been assigned values), in a manner that doesn't depend on +# gensymmed names. +# `mnokw` is the method that gets called when you invoke it without +# supplying any keywords. +function __lookup_kwbody__(mnokw::Method) + function getsym(arg) + isa(arg, Symbol) && return arg + @assert isa(arg, GlobalRef) + return arg.name + end + + f = get(__bodyfunction__, mnokw, nothing) + if f === nothing + fmod = mnokw.module + # The lowered code for `mnokw` should look like + # %1 = mkw(kwvalues..., #self#, args...) + # return %1 + # where `mkw` is the name of the "active" keyword body-function. + ast = Base.uncompressed_ast(mnokw) + if isa(ast, Core.CodeInfo) && length(ast.code) >= 2 + callexpr = ast.code[end-1] + if isa(callexpr, Expr) && callexpr.head == :call + fsym = callexpr.args[1] + if isa(fsym, Symbol) + f = getfield(fmod, fsym) + elseif isa(fsym, GlobalRef) + if fsym.mod === Core && fsym.name === :_apply + f = getfield(mnokw.module, getsym(callexpr.args[2])) + elseif fsym.mod === Core && fsym.name === :_apply_iterate + f = getfield(mnokw.module, getsym(callexpr.args[3])) + else + f = getfield(fsym.mod, fsym.name) + end + else + f = missing + end + else + f = missing + end + else + f = missing + end + __bodyfunction__[mnokw] = f + end + return f +end + +function _precompile_() + ccall(:jl_generating_output, Cint, ()) == 1 || return nothing + Base.precompile(Tuple{Core.kwftype(typeof(Type)),NamedTuple{(:label, :blank), Tuple{Symbol, Bool}},Type{EmptyLayout}}) + Base.precompile(Tuple{Core.kwftype(typeof(Type)),NamedTuple{(:label, :width, :height), Tuple{Symbol, Symbol, Length{:pct, Float64}}},Type{EmptyLayout}}) + Base.precompile(Tuple{Core.kwftype(typeof(Type)),NamedTuple{(:parent,), Tuple{GridLayout}},Type{Subplot},GRBackend}) + Base.precompile(Tuple{Core.kwftype(typeof(Type)),NamedTuple{(:parent,), Tuple{GridLayout}},Type{Subplot},PlotlyBackend}) + Base.precompile(Tuple{Core.kwftype(typeof(Type)),NamedTuple{(:parent,), Tuple{Subplot{GRBackend}}},Type{Subplot},GRBackend}) + Base.precompile(Tuple{Core.kwftype(typeof(Type)),NamedTuple{(:parent,), Tuple{Subplot{PlotlyBackend}}},Type{Subplot},PlotlyBackend}) + Base.precompile(Tuple{Core.kwftype(typeof(_make_hist)),NamedTuple{(:normed, :weights), Tuple{Bool, Nothing}},typeof(_make_hist),Tuple{Vector{Float64}, Vector{Float64}},Int64}) + Base.precompile(Tuple{Core.kwftype(typeof(_make_hist)),NamedTuple{(:normed, :weights), Tuple{Bool, Nothing}},typeof(_make_hist),Tuple{Vector{Float64}, Vector{Float64}},Tuple{Int64, Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(_make_hist)),NamedTuple{(:normed, :weights), Tuple{Bool, Nothing}},typeof(_make_hist),Tuple{Vector{Float64}},Symbol}) + Base.precompile(Tuple{Core.kwftype(typeof(_make_hist)),NamedTuple{(:normed, :weights), Tuple{Bool, Vector{Int64}}},typeof(_make_hist),Tuple{Vector{Float64}},Symbol}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:flip,), Tuple{Bool}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:formatter,), Tuple{Symbol}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:formatter,), Tuple{typeof(datetimeformatter)}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:grid, :flip, :minorgrid, :guide), Tuple{Bool, Bool, Bool, String}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:grid, :lims), Tuple{Bool, Tuple{Float64, Float64}}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:grid, :lims, :flip), Tuple{Bool, Tuple{Float64, Float64}, Bool}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:grid, :minorgrid, :guide), Tuple{Bool, Bool, String}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:grid, :minorgrid, :mirror, :guide), Tuple{Bool, Bool, Bool, String}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:grid,), Tuple{Bool}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:gridlinewidth, :grid, :gridalpha, :gridstyle, :foreground_color_grid), Tuple{Int64, Bool, Float64, Symbol, RGBA{Float64}}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:guide,), Tuple{String}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:guide_position, :guidefontvalign, :mirror, :guide), Tuple{Symbol, Symbol, Bool, String}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:guidefonthalign, :guide_position, :mirror, :guide), Tuple{Symbol, Symbol, Bool, String}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:lims, :flip, :ticks, :guide), Tuple{Tuple{Int64, Int64}, Bool, StepRange{Int64, Int64}, String}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:lims,), Tuple{Tuple{Float64, Float64}}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:lims,), Tuple{Tuple{Int64, Float64}}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:lims,), Tuple{Tuple{Int64, Int64}}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:rotation,), Tuple{Int64}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:scale, :guide), Tuple{Symbol, String}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:ticks,), Tuple{Nothing}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(attr!)),NamedTuple{(:ticks,), Tuple{UnitRange{Int64}}},typeof(attr!),Axis}) + Base.precompile(Tuple{Core.kwftype(typeof(default)),NamedTuple{(:shape,), Tuple{Symbol}},typeof(default)}) + Base.precompile(Tuple{Core.kwftype(typeof(default)),NamedTuple{(:titlefont, :legendfontsize, :guidefont, :tickfont, :guide, :framestyle, :yminorgrid), Tuple{Tuple{Int64, String}, Int64, Tuple{Int64, Symbol}, Tuple{Int64, Symbol}, String, Symbol, Bool}},typeof(default)}) + Base.precompile(Tuple{Core.kwftype(typeof(font)),NamedTuple{(:family, :pointsize, :halign, :valign, :rotation, :color), Tuple{String, Int64, Symbol, Symbol, Float64, RGBA{Float64}}},typeof(font)}) + Base.precompile(Tuple{Core.kwftype(typeof(font)),NamedTuple{(:family, :pointsize, :valign, :halign, :rotation, :color), Tuple{String, Int64, Symbol, Symbol, Float64, RGBA{Float64}}},typeof(font)}) + Base.precompile(Tuple{Core.kwftype(typeof(gr_polyline)),NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}},typeof(gr_polyline),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(gr_polyline)),NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}},typeof(gr_polyline),StepRange{Int64, Int64},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(gr_polyline)),NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}},typeof(gr_polyline),UnitRange{Int64},UnitRange{Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(gr_polyline)),NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}},typeof(gr_polyline),UnitRange{Int64},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(gr_polyline)),NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}},typeof(gr_polyline),Vector{Int64},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(gr_set_font)),NamedTuple{(:halign, :valign, :rotation, :color), Tuple{Symbol, Symbol, Int64, RGBA{Float64}}},typeof(gr_set_font),Font,Subplot{GRBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(heatmap)),Any,typeof(heatmap),Any,Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(hline!)),Any,typeof(hline!),Any}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:alpha, :seriestype), Tuple{Float64, Symbol}},typeof(plot!),Plot{GRBackend},Vector{GeometryBasics.Point2{Float64}}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:alpha, :seriestype), Tuple{Float64, Symbol}},typeof(plot!),Plot{PlotlyBackend},Vector{GeometryBasics.Point2{Float64}}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:alpha, :seriestype), Tuple{Float64, Symbol}},typeof(plot!),Vector{GeometryBasics.Point2{Float64}}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:annotation,), Tuple{Vector{Tuple{Int64, Float64, PlotText}}}},typeof(plot!),Plot{GRBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:annotation,), Tuple{Vector{Tuple{Int64, Float64, PlotText}}}},typeof(plot!),Plot{PlotlyBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:annotation,), Tuple{Vector{Tuple{Int64, Float64, PlotText}}}},typeof(plot!)}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:layout, :label, :fillrange, :fillalpha), Tuple{Tuple{Int64, Int64}, String, Int64, Float64}},typeof(plot!),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:layout, :label, :fillrange, :fillalpha), Tuple{Tuple{Int64, Int64}, String, Int64, Float64}},typeof(plot!),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:layout, :margin), Tuple{GridLayout, AbsoluteLength}},typeof(plot!),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:layout, :margin), Tuple{GridLayout, AbsoluteLength}},typeof(plot!),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:layout, :xlims), Tuple{GridLayout, Tuple{Int64, Float64}}},typeof(plot!),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:layout,), Tuple{Tuple{Int64, Int64}}},typeof(plot!),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:layout,), Tuple{Tuple{Int64, Int64}}},typeof(plot!),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:legend,), Tuple{Bool}},typeof(plot!),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:legend,), Tuple{Bool}},typeof(plot!),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:legend,), Tuple{Symbol}},typeof(plot!),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:legend,), Tuple{Symbol}},typeof(plot!),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:line, :seriestype), Tuple{Tuple{Int64, Symbol, Float64, Matrix{Symbol}}, Symbol}},typeof(plot!),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:line, :seriestype), Tuple{Tuple{Int64, Symbol, Float64, Matrix{Symbol}}, Symbol}},typeof(plot!),Plot{GRBackend},Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:line, :seriestype), Tuple{Tuple{Int64, Symbol, Float64, Matrix{Symbol}}, Symbol}},typeof(plot!),Plot{PlotlyBackend},Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:lw, :color), Tuple{Int64, Symbol}},typeof(plot!),Function,Float64,Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:lw, :color), Tuple{Int64, Symbol}},typeof(plot!),Plot{GRBackend},Function,Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:marker, :series_annotations, :seriestype), Tuple{Tuple{Int64, Float64, Symbol}, Vector{Any}, Symbol}},typeof(plot!),Plot{GRBackend},StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:marker, :series_annotations, :seriestype), Tuple{Tuple{Int64, Float64, Symbol}, Vector{Any}, Symbol}},typeof(plot!),Plot{PlotlyBackend},StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:marker, :series_annotations, :seriestype), Tuple{Tuple{Int64, Float64, Symbol}, Vector{Any}, Symbol}},typeof(plot!),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:markersize, :c, :seriestype), Tuple{Int64, Symbol, Symbol}},typeof(plot!),Plot{GRBackend},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:markersize, :c, :seriestype), Tuple{Int64, Symbol, Symbol}},typeof(plot!),Plot{PlotlyBackend},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:markersize, :c, :seriestype), Tuple{Int64, Symbol, Symbol}},typeof(plot!),Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:seriestype, :inset), Tuple{Symbol, Tuple{Int64, BoundingBox{Tuple{Length{:w, Float64}, Length{:h, Float64}}, Tuple{Length{:w, Float64}, Length{:h, Float64}}}}}},typeof(plot!),Plot{GRBackend},Vector{Int64},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:seriestype, :inset), Tuple{Symbol, Tuple{Int64, BoundingBox{Tuple{Length{:w, Float64}, Length{:h, Float64}}, Tuple{Length{:w, Float64}, Length{:h, Float64}}}}}},typeof(plot!),Plot{PlotlyBackend},Vector{Int64},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:seriestype, :inset), Tuple{Symbol, Tuple{Int64, BoundingBox{Tuple{Length{:w, Float64}, Length{:h, Float64}}, Tuple{Length{:w, Float64}, Length{:h, Float64}}}}}},typeof(plot!),Vector{Int64},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:seriestype,), Tuple{Symbol}},typeof(plot!),Plot{GRBackend},Vector{Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:seriestype,), Tuple{Symbol}},typeof(plot!),Plot{PlotlyBackend},Vector{Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:seriestype,), Tuple{Symbol}},typeof(plot!),Vector{Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:title,), Tuple{String}},typeof(plot!),Plot{GRBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:title,), Tuple{String}},typeof(plot!),Plot{PlotlyBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:title,), Tuple{String}},typeof(plot!)}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:w,), Tuple{Int64}},typeof(plot!),Plot{GRBackend},Vector{Float64},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:xgrid,), Tuple{Tuple{Symbol, Symbol, Int64, Symbol, Float64}}},typeof(plot!),Plot{GRBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:xgrid,), Tuple{Tuple{Symbol, Symbol, Int64, Symbol, Float64}}},typeof(plot!),Plot{PlotlyBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:yaxis,), Tuple{Tuple{String, Symbol}}},typeof(plot!),Plot{GRBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:yaxis,), Tuple{Tuple{String, Symbol}}},typeof(plot!),Plot{PlotlyBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:yaxis,), Tuple{Tuple{String, Symbol}}},typeof(plot!)}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:zcolor, :m, :ms, :lab, :seriestype), Tuple{Vector{Float64}, Tuple{Symbol, Float64, Stroke}, Vector{Float64}, String, Symbol}},typeof(plot!),Plot{GRBackend},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:zcolor, :m, :ms, :lab, :seriestype), Tuple{Vector{Float64}, Tuple{Symbol, Float64, Stroke}, Vector{Float64}, String, Symbol}},typeof(plot!),Plot{PlotlyBackend},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot!)),NamedTuple{(:zcolor, :m, :ms, :lab, :seriestype), Tuple{Vector{Float64}, Tuple{Symbol, Float64, Stroke}, Vector{Float64}, String, Symbol}},typeof(plot!),Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:annotations, :leg), Tuple{Tuple{Int64, Float64, PlotText}, Bool}},typeof(plot),Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:arrow,), Tuple{Int64}},typeof(plot),Vector{Float64},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:aspect_ratio, :seriestype), Tuple{Int64, Symbol}},typeof(plot),Vector{String},Vector{String},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:bins, :weights, :seriestype), Tuple{Symbol, Vector{Int64}, Symbol}},typeof(plot),Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:color, :line, :marker), Tuple{Matrix{Symbol}, Tuple{Symbol, Int64}, Tuple{Matrix{Symbol}, Int64, Float64, Stroke}}},typeof(plot),Vector{Vector{T} where T}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:connections, :seriestype), Tuple{Tuple{Vector{Int64}, Vector{Int64}, Vector{Int64}}, Symbol}},typeof(plot),Vector{Int64},Vector{Int64},Vararg{Vector{Int64}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:fill, :seriestype), Tuple{Bool, Symbol}},typeof(plot),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:framestyle, :title, :color, :layout, :label, :markerstrokewidth, :ticks, :seriestype), Tuple{Matrix{Symbol}, Matrix{String}, Base.ReshapedArray{Int64, 2, UnitRange{Int64}, Tuple{}}, Int64, String, Int64, UnitRange{Int64}, Symbol}},typeof(plot),Vector{Vector{Float64}},Vector{Vector{Float64}}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:grid, :title), Tuple{Tuple{Symbol, Symbol, Symbol, Int64, Float64}, String}},typeof(plot),Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:lab, :w, :palette, :fill, :α), Tuple{String, Int64, PlotUtils.ContinuousColorGradient, Int64, Float64}},typeof(plot),StepRange{Int64, Int64},Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:label, :title, :xlabel, :linewidth, :legend), Tuple{Matrix{String}, String, String, Int64, Symbol}},typeof(plot),Vector{Function},Float64,Vararg{Float64, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:label,), Tuple{Matrix{String}}},typeof(plot),Vector{AbstractVector{Float64}}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:labels,), Tuple{Matrix{String}}},typeof(plot),PortfolioComposition}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :group, :linetype, :linecolor), Tuple{GridLayout, Vector{String}, Matrix{Symbol}, Symbol}},typeof(plot),Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :label, :fillrange, :fillalpha), Tuple{Tuple{Int64, Int64}, String, Int64, Float64}},typeof(plot),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :label, :fillrange, :fillalpha), Tuple{Tuple{Int64, Int64}, String, Int64, Float64}},typeof(plot),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :link), Tuple{Int64, Symbol}},typeof(plot),Plot{GRBackend},Plot{GRBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :link), Tuple{Int64, Symbol}},typeof(plot),Plot{PlotlyBackend},Plot{PlotlyBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :margin), Tuple{GridLayout, AbsoluteLength}},typeof(plot),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :margin), Tuple{GridLayout, AbsoluteLength}},typeof(plot),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :palette, :bg_inside), Tuple{Int64, Matrix{PlotUtils.ContinuousColorGradient}, Matrix{Symbol}}},typeof(plot),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :t, :leg, :ticks, :border), Tuple{GridLayout, Matrix{Symbol}, Bool, Nothing, Symbol}},typeof(plot),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :title, :titlelocation, :left_margin, :bottom_margin, :xrotation), Tuple{GridLayout, Matrix{String}, Symbol, Matrix{AbsoluteLength}, AbsoluteLength, Int64}},typeof(plot),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :xguide, :yguide, :xguidefonthalign, :yguidefontvalign, :xguideposition, :yguideposition, :ymirror, :xmirror, :legend, :seriestype), Tuple{Int64, String, String, Matrix{Symbol}, Matrix{Symbol}, Symbol, Matrix{Symbol}, Matrix{Bool}, Matrix{Bool}, Bool, Matrix{Symbol}}},typeof(plot),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout, :xlims), Tuple{GridLayout, Tuple{Int64, Float64}}},typeof(plot),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout,), Tuple{Tuple{Int64, Int64}}},typeof(plot),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:layout,), Tuple{Tuple{Int64, Int64}}},typeof(plot),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:legend,), Tuple{Bool}},typeof(plot),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:legend,), Tuple{Bool}},typeof(plot),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:legend,), Tuple{Symbol}},typeof(plot),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:legend,), Tuple{Symbol}},typeof(plot),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:legend,), Tuple{Symbol}},typeof(plot),Vector{Tuple{Int64, Real}}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:line, :lab, :ms), Tuple{Tuple{Matrix{Symbol}, Int64}, Matrix{String}, Int64}},typeof(plot),Vector{Vector{T} where T},Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:line, :label, :legendtitle), Tuple{Tuple{Int64, Matrix{Symbol}}, Matrix{String}, String}},typeof(plot),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:line, :leg, :fill), Tuple{Int64, Bool, Tuple{Int64, Symbol}}},typeof(plot),Function,Function,Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:line, :marker, :bg, :fg, :xlim, :ylim, :leg), Tuple{Tuple{Int64, Symbol, Symbol}, Tuple{Shape{Float64, Float64}, Int64, RGBA{Float64}}, Symbol, Symbol, Tuple{Int64, Int64}, Tuple{Int64, Int64}, Bool}},typeof(plot),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:line_z, :linewidth, :legend), Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Int64, Bool}},typeof(plot),Vector{Float64},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:m, :markersize, :lab, :bg, :xlim, :ylim, :seriestype), Tuple{Matrix{Symbol}, Int64, Matrix{String}, Symbol, Tuple{Int64, Int64}, Tuple{Int64, Int64}, Symbol}},typeof(plot),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:marker,), Tuple{Bool}},typeof(plot),Vector{Union{Missing, Int64}}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:marker_z, :color, :legend, :seriestype), Tuple{typeof(+), Symbol, Bool, Symbol}},typeof(plot),Vector{Float64},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:markershape, :markersize, :marker_z, :line_z, :linewidth), Tuple{Matrix{Symbol}, Matrix{Int64}, Matrix{Int64}, Matrix{Int64}, Matrix{Int64}}},typeof(plot),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:markershape, :seriestype, :label), Tuple{Symbol, Symbol, String}},typeof(plot),UnitRange{Int64},Vector{Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:nbins, :seriestype), Tuple{Int64, Symbol}},typeof(plot),Vector{Float64},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:nbins, :show_empty_bins, :normed, :aspect_ratio, :seriestype), Tuple{Tuple{Int64, Int64}, Bool, Bool, Int64, Symbol}},typeof(plot),Vector{ComplexF64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:proj, :m), Tuple{Symbol, Int64}},typeof(plot),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:projection, :seriestype), Tuple{Symbol, Symbol}},typeof(plot),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},UnitRange{Int64},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:quiver, :seriestype), Tuple{Tuple{Vector{Float64}, Vector{Float64}, Vector{Float64}}, Symbol}},typeof(plot),Vector{Float64},Vector{Float64},Vararg{Vector{Float64}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:reg, :fill), Tuple{Bool, Tuple{Int64, Symbol}}},typeof(plot),Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:ribbon,), Tuple{Int64}},typeof(plot),UnitRange{Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:ribbon,), Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}}},typeof(plot),UnitRange{Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:ribbon,), Tuple{Tuple{LinRange{Float64}, LinRange{Float64}}}},typeof(plot),UnitRange{Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:ribbon,), Tuple{typeof(sqrt)}},typeof(plot),UnitRange{Int64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:seriestype, :markershape, :markersize, :color), Tuple{Matrix{Symbol}, Vector{Symbol}, Int64, Vector{Symbol}}},typeof(plot),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:seriestype,), Tuple{Symbol}},typeof(plot),Vector{DateTime},UnitRange{Int64},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:seriestype,), Tuple{Symbol}},typeof(plot),Vector{OHLC}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:st, :xlabel, :ylabel, :zlabel), Tuple{Symbol, String, String, String}},typeof(plot),Vector{Float64},Vector{Float64},Vararg{Vector{Float64}, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:title, :l, :seriestype), Tuple{String, Float64, Symbol}},typeof(plot),Vector{String},Vector{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:title, :xflip, :yflip, :zflip, :zlabel, :dpi, :grid, :ylabel, :minorgrid, :xlabel, :seriestype), Tuple{String, Bool, Bool, Bool, String, Int64, Bool, String, Bool, String, Symbol}},typeof(plot),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:title, :xmirror, :ymirror, :zmirror, :zlabel, :dpi, :grid, :ylabel, :minorgrid, :xlabel, :seriestype), Tuple{String, Bool, Bool, Bool, String, Int64, Bool, String, Bool, String, Symbol}},typeof(plot),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:title, :zlabel, :dpi, :grid, :ylabel, :minorgrid, :xlabel, :seriestype), Tuple{String, String, Int64, Bool, String, Bool, String, Symbol}},typeof(plot),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:title,), Tuple{Matrix{String}}},typeof(plot),Plot{GRBackend},Plot{GRBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:title,), Tuple{Matrix{String}}},typeof(plot),Plot{PlotlyBackend},Plot{PlotlyBackend}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:w,), Tuple{Int64}},typeof(plot),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:xaxis, :background_color, :leg), Tuple{Tuple{String, Tuple{Int64, Int64}, StepRange{Int64, Int64}, Symbol}, RGB{Float64}, Bool}},typeof(plot),Matrix{Float64}}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:yflip, :aspect_ratio), Tuple{Bool, Symbol}},typeof(plot),Vector{Float64},Vector{Int64},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(plot)),NamedTuple{(:zcolor, :m, :leg, :cbar, :w), Tuple{StepRange{Int64, Int64}, Tuple{Int64, Float64, Symbol, Stroke}, Bool, Bool, Int64}},typeof(plot),Vector{Float64},Vector{Float64},Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(portfoliocomposition)),Any,typeof(portfoliocomposition),Any,Vararg{Any, N} where N}) + Base.precompile(Tuple{Core.kwftype(typeof(scatter!)),Any,typeof(scatter!),Any}) + Base.precompile(Tuple{Core.kwftype(typeof(test_examples)),NamedTuple{(:skip, :disp), Tuple{Vector{Int64}, Bool}},typeof(test_examples),Symbol}) + Base.precompile(Tuple{Core.kwftype(typeof(test_examples)),NamedTuple{(:skip,), Tuple{Vector{Int64}}},typeof(test_examples),Symbol}) + Base.precompile(Tuple{Type{GridLayout},Int64,Vararg{Int64, N} where N}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},AbstractVector{OHLC}}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},PortfolioComposition}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:barbins}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:barhist}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:bar}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:bins2d}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:histogram2d}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:histogram}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:hline}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:lens}},AbstractPlot}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:pie}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:quiver}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:steppost}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:steppre}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:sticks}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:vline}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Type{Val{:xerror}},Any,Any,Any}) + Base.precompile(Tuple{typeof(RecipesBase.apply_recipe),AbstractDict{Symbol, Any},Vector{ComplexF64}}) + Base.precompile(Tuple{typeof(RecipesPipeline.add_series!),Plot{GRBackend},DefaultsDict}) + Base.precompile(Tuple{typeof(RecipesPipeline.add_series!),Plot{PlotlyBackend},DefaultsDict}) + Base.precompile(Tuple{typeof(RecipesPipeline.plot_setup!),Plot{GRBackend},Dict{Symbol, Any},Vector{Dict{Symbol, Any}}}) + Base.precompile(Tuple{typeof(RecipesPipeline.plot_setup!),Plot{PlotlyBackend},Dict{Symbol, Any},Vector{Dict{Symbol, Any}}}) + Base.precompile(Tuple{typeof(RecipesPipeline.preprocess_attributes!),Plot{GRBackend},DefaultsDict}) + Base.precompile(Tuple{typeof(RecipesPipeline.process_userrecipe!),Plot{GRBackend},Vector{Dict{Symbol, Any}},Dict{Symbol, Any}}) + Base.precompile(Tuple{typeof(RecipesPipeline.process_userrecipe!),Plot{PlotlyBackend},Vector{Dict{Symbol, Any}},Dict{Symbol, Any}}) + Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plot{GRBackend},DefaultsDict,Symbol,Any}) + Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plot{GRBackend},Dict{Symbol, Any},Symbol,Any}) + Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plot{PlotlyBackend},DefaultsDict,Symbol,Any}) + Base.precompile(Tuple{typeof(RecipesPipeline.warn_on_recipe_aliases!),Plot{PlotlyBackend},Dict{Symbol, Any},Symbol,Any}) + Base.precompile(Tuple{typeof(_bin_centers),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}}) + Base.precompile(Tuple{typeof(_cbar_unique),Vector{Int64},String}) + Base.precompile(Tuple{typeof(_cbar_unique),Vector{Nothing},String}) + Base.precompile(Tuple{typeof(_cbar_unique),Vector{PlotUtils.ContinuousColorGradient},String}) + Base.precompile(Tuple{typeof(_cbar_unique),Vector{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}},String}) + Base.precompile(Tuple{typeof(_cbar_unique),Vector{Symbol},String}) + Base.precompile(Tuple{typeof(_cycle),Base.OneTo{Int64},Vector{Int64}}) + Base.precompile(Tuple{typeof(_cycle),StepRange{Int64, Int64},Vector{Int64}}) + Base.precompile(Tuple{typeof(_cycle),Vector{Float64},StepRange{Int64, Int64}}) + Base.precompile(Tuple{typeof(_cycle),Vector{Float64},UnitRange{Int64}}) + Base.precompile(Tuple{typeof(_cycle),Vector{Float64},Vector{Int64}}) + Base.precompile(Tuple{typeof(_do_plot_show),Plot{GRBackend},Bool}) + Base.precompile(Tuple{typeof(_do_plot_show),Plot{PlotlyBackend},Bool}) + Base.precompile(Tuple{typeof(_heatmap_edges),Vector{Float64},Bool,Bool}) + Base.precompile(Tuple{typeof(_plot!),Plot,Any,Any}) + Base.precompile(Tuple{typeof(_preprocess_barlike),DefaultsDict,Base.OneTo{Int64},Vector{Float64}}) + Base.precompile(Tuple{typeof(_preprocess_binlike),DefaultsDict,StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vector{Float64}}) + Base.precompile(Tuple{typeof(_update_min_padding!),GridLayout}) + Base.precompile(Tuple{typeof(_update_subplot_args),Plot{GRBackend},Subplot{GRBackend},Dict{Symbol, Any},Int64,Bool}) + Base.precompile(Tuple{typeof(_update_subplot_args),Plot{PlotlyBackend},Subplot{PlotlyBackend},Dict{Symbol, Any},Int64,Bool}) + Base.precompile(Tuple{typeof(_update_subplot_periphery),Subplot{GRBackend},Vector{Any}}) + Base.precompile(Tuple{typeof(_update_subplot_periphery),Subplot{PlotlyBackend},Vector{Any}}) + Base.precompile(Tuple{typeof(axis_limits),Subplot{GRBackend},Symbol,Bool,Bool}) + Base.precompile(Tuple{typeof(axis_limits),Subplot{PlotlyBackend},Symbol,Bool,Bool}) + Base.precompile(Tuple{typeof(backend),PlotlyBackend}) + Base.precompile(Tuple{typeof(bbox),AbsoluteLength,AbsoluteLength,AbsoluteLength,AbsoluteLength}) + Base.precompile(Tuple{typeof(bbox),Float64,Float64,Float64,Float64}) + Base.precompile(Tuple{typeof(build_layout),GridLayout,Int64}) + Base.precompile(Tuple{typeof(contour),Any,Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(convert_to_polar),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vector{Float64},Tuple{Int64, Float64}}) + Base.precompile(Tuple{typeof(create_grid),Expr}) + Base.precompile(Tuple{typeof(discrete_value!),Axis,Vector{String}}) + Base.precompile(Tuple{typeof(error_coords),Vector{Float64},Vector{Float64},Vector{Float64},Vararg{Vector{Float64}, N} where N}) + Base.precompile(Tuple{typeof(error_coords),Vector{Float64},Vector{Float64},Vector{Float64}}) + Base.precompile(Tuple{typeof(error_style!),DefaultsDict}) + Base.precompile(Tuple{typeof(error_zipit),Tuple{Vector{Float64}, Vector{Float64}, Vector{Float64}}}) + Base.precompile(Tuple{typeof(get_minor_ticks),Subplot{GRBackend},Axis,Tuple{Vector{Float64}, Vector{String}}}) + Base.precompile(Tuple{typeof(get_minor_ticks),Subplot{GRBackend},Axis,Tuple{Vector{Int64}, Vector{String}}}) + Base.precompile(Tuple{typeof(get_series_color),Vector{Symbol},Subplot{GRBackend},Int64,Symbol}) + Base.precompile(Tuple{typeof(get_ticks),StepRange{Int64, Int64},Vector{Float64},Vector{Any},Tuple{Int64, Int64},Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(get_ticks),Symbol,Vector{Float64},Vector{Any},Tuple{Float64, Float64},Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(get_ticks),Symbol,Vector{Float64},Vector{Any},Tuple{Int64, Float64},Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(get_ticks),Symbol,Vector{Float64},Vector{Any},Tuple{Int64, Int64},Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(get_ticks),UnitRange{Int64},Vector{Float64},Vector{Any},Tuple{Float64, Float64},Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(get_xy),Vector{OHLC}}) + Base.precompile(Tuple{typeof(gr_add_legend),Subplot{GRBackend},NamedTuple{(:w, :h, :dy, :leftw, :textw, :rightw, :xoffset, :yoffset, :width_factor), NTuple{9, Float64}},Vector{Float64}}) + Base.precompile(Tuple{typeof(gr_add_legend),Subplot{GRBackend},NamedTuple{(:w, :h, :dy, :leftw, :textw, :rightw, :xoffset, :yoffset, :width_factor), Tuple{Int64, Float64, Float64, Float64, Int64, Float64, Float64, Float64, Float64}},Vector{Float64}}) + Base.precompile(Tuple{typeof(gr_display),Subplot{GRBackend},AbsoluteLength,AbsoluteLength,Vector{Float64}}) + Base.precompile(Tuple{typeof(gr_draw_colorbar),GRColorbar,Subplot{GRBackend},Tuple{Float64, Float64},Vector{Float64}}) + Base.precompile(Tuple{typeof(gr_draw_contour),Series,StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Matrix{Float64},Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_heatmap),Series,Vector{Float64},Vector{Float64},Matrix{Float64},Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_marker),Series,Float64,Float64,Tuple{Float64, Float64},Int64,Float64,Float64,Symbol}) + Base.precompile(Tuple{typeof(gr_draw_marker),Series,Float64,Float64,Tuple{Float64, Float64},Int64,Int64,Int64,Shape{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_marker),Series,Int64,Float64,Tuple{Float64, Float64},Int64,Float64,Int64,Symbol}) + Base.precompile(Tuple{typeof(gr_draw_marker),Series,Int64,Int64,Tuple{Float64, Float64},Int64,Int64,Int64,Symbol}) + Base.precompile(Tuple{typeof(gr_draw_markers),Series,Base.OneTo{Int64},Vector{Float64},Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_segments),Series,Base.OneTo{Int64},UnitRange{Int64},Tuple{Vector{Float64}, Vector{Float64}},Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_segments),Series,Base.OneTo{Int64},Vector{Float64},Int64,Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_segments),Series,StepRange{Int64, Int64},Vector{Float64},Int64,Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_segments),Series,Vector{Float64},Vector{Float64},Int64,Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_shapes),Series,Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_surface),Series,StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Matrix{Float64},Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_surface),Series,Vector{Float64},Vector{Float64},Matrix{Float64},Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_draw_surface),Series,Vector{Float64},Vector{Float64},Vector{Float64},Tuple{Float64, Float64}}) + Base.precompile(Tuple{typeof(gr_fill_viewport),Vector{Float64},RGBA{Float64}}) + Base.precompile(Tuple{typeof(gr_get_ticks_size),Tuple{Vector{Float64}, Vector{String}},Int64}) + Base.precompile(Tuple{typeof(gr_get_ticks_size),Tuple{Vector{Int64}, Vector{String}},Int64}) + Base.precompile(Tuple{typeof(gr_label_ticks),Subplot{GRBackend},Symbol,Tuple{Vector{Float64}, Vector{String}}}) + Base.precompile(Tuple{typeof(gr_label_ticks_3d),Subplot{GRBackend},Symbol,Tuple{Vector{Float64}, Vector{String}}}) + Base.precompile(Tuple{typeof(gr_polaraxes),Int64,Float64,Subplot{GRBackend}}) + Base.precompile(Tuple{typeof(gr_polyline),Vector{Float64},Vector{Float64},Function}) + Base.precompile(Tuple{typeof(gr_set_gradient),PlotUtils.ContinuousColorGradient}) + Base.precompile(Tuple{typeof(gr_update_viewport_legend!),Vector{Float64},Subplot{GRBackend},NamedTuple{(:w, :h, :dy, :leftw, :textw, :rightw, :xoffset, :yoffset, :width_factor), NTuple{9, Float64}}}) + Base.precompile(Tuple{typeof(gr_update_viewport_legend!),Vector{Float64},Subplot{GRBackend},NamedTuple{(:w, :h, :dy, :leftw, :textw, :rightw, :xoffset, :yoffset, :width_factor), Tuple{Int64, Float64, Float64, Float64, Int64, Float64, Float64, Float64, Float64}}}) + Base.precompile(Tuple{typeof(gr_viewport_from_bbox),Subplot{GRBackend},BoundingBox{Tuple{AbsoluteLength, AbsoluteLength}, Tuple{AbsoluteLength, AbsoluteLength}},AbsoluteLength,AbsoluteLength,Vector{Float64}}) + Base.precompile(Tuple{typeof(heatmap_edges),Base.OneTo{Int64},Symbol}) + Base.precompile(Tuple{typeof(heatmap_edges),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Symbol,StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Symbol,Tuple{Int64, Int64},Bool}) + Base.precompile(Tuple{typeof(heatmap_edges),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Symbol}) + Base.precompile(Tuple{typeof(heatmap_edges),UnitRange{Int64},Symbol}) + Base.precompile(Tuple{typeof(heatmap_edges),Vector{Float64},Symbol,UnitRange{Int64},Symbol,Tuple{Int64, Int64},Bool}) + Base.precompile(Tuple{typeof(heatmap_edges),Vector{Float64},Symbol,Vector{Float64},Symbol,Tuple{Int64, Int64},Bool}) + Base.precompile(Tuple{typeof(heatmap_edges),Vector{Float64},Symbol}) + Base.precompile(Tuple{typeof(ignorenan_minimum),Vector{Int64}}) + Base.precompile(Tuple{typeof(layout_args),Int64}) + Base.precompile(Tuple{typeof(make_fillrange_side),UnitRange{Int64},LinRange{Float64}}) + Base.precompile(Tuple{typeof(optimal_ticks_and_labels),Nothing,Tuple{Float64, Float64},Symbol,Function}) + Base.precompile(Tuple{typeof(optimal_ticks_and_labels),Nothing,Tuple{Float64, Float64},Symbol,Symbol}) + Base.precompile(Tuple{typeof(optimal_ticks_and_labels),Nothing,Tuple{Int64, Float64},Symbol,Symbol}) + Base.precompile(Tuple{typeof(optimal_ticks_and_labels),Nothing,Tuple{Int64, Int64},Symbol,Symbol}) + Base.precompile(Tuple{typeof(optimal_ticks_and_labels),StepRange{Int64, Int64},Tuple{Int64, Int64},Symbol,Symbol}) + Base.precompile(Tuple{typeof(optimal_ticks_and_labels),UnitRange{Int64},Tuple{Float64, Float64},Symbol,Symbol}) + Base.precompile(Tuple{typeof(partialcircle),Int64,Float64,Int64}) + Base.precompile(Tuple{typeof(plot),Any,Any,Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(plot),Any}) + Base.precompile(Tuple{typeof(plot),Plot{GRBackend},Plot{GRBackend},Vararg{Plot{GRBackend}, N} where N}) + Base.precompile(Tuple{typeof(plot),Plot{GRBackend},Plot{GRBackend}}) + Base.precompile(Tuple{typeof(plot),Plot{GRBackend}}) + Base.precompile(Tuple{typeof(plot),Plot{PlotlyBackend},Plot{PlotlyBackend},Vararg{Plot{PlotlyBackend}, N} where N}) + Base.precompile(Tuple{typeof(plot),Plot{PlotlyBackend},Plot{PlotlyBackend}}) + Base.precompile(Tuple{typeof(processGridArg!),DefaultsDict,Bool,Symbol}) + Base.precompile(Tuple{typeof(processGridArg!),Dict{Symbol, Any},Symbol,Symbol}) + Base.precompile(Tuple{typeof(processLineArg),Dict{Symbol, Any},Matrix{Symbol}}) + Base.precompile(Tuple{typeof(processLineArg),Dict{Symbol, Any},Symbol}) + Base.precompile(Tuple{typeof(processMarkerArg),Dict{Symbol, Any},Matrix{Symbol}}) + Base.precompile(Tuple{typeof(processMarkerArg),Dict{Symbol, Any},RGBA{Float64}}) + Base.precompile(Tuple{typeof(processMarkerArg),Dict{Symbol, Any},Shape{Float64, Float64}}) + Base.precompile(Tuple{typeof(processMarkerArg),Dict{Symbol, Any},Stroke}) + Base.precompile(Tuple{typeof(processMarkerArg),Dict{Symbol, Any},Symbol}) + Base.precompile(Tuple{typeof(process_annotation),Subplot{GRBackend},Int64,Float64,PlotText}) + Base.precompile(Tuple{typeof(process_annotation),Subplot{PlotlyBackend},Int64,Float64,PlotText}) + Base.precompile(Tuple{typeof(process_axis_arg!),Dict{Symbol, Any},StepRange{Int64, Int64},Symbol}) + Base.precompile(Tuple{typeof(process_axis_arg!),Dict{Symbol, Any},Symbol,Symbol}) + Base.precompile(Tuple{typeof(push!),Plot{GRBackend},Float64,Vector{Float64}}) + Base.precompile(Tuple{typeof(scalefontsizes),Float64}) + Base.precompile(Tuple{typeof(scalefontsizes)}) + Base.precompile(Tuple{typeof(series_annotations),Vector{Any}}) + Base.precompile(Tuple{typeof(slice_arg),Base.ReshapedArray{Int64, 2, UnitRange{Int64}, Tuple{}},Int64}) + Base.precompile(Tuple{typeof(slice_arg),Matrix{Bool},Int64}) + Base.precompile(Tuple{typeof(slice_arg),Matrix{Int64},Int64}) + Base.precompile(Tuple{typeof(slice_arg),Matrix{PlotUtils.ContinuousColorGradient},Int64}) + Base.precompile(Tuple{typeof(slice_arg),Matrix{RGBA{Float64}},Int64}) + Base.precompile(Tuple{typeof(slice_arg),Matrix{String},Int64}) + Base.precompile(Tuple{typeof(slice_arg),Matrix{Symbol},Int64}) + Base.precompile(Tuple{typeof(spy),Any}) + Base.precompile(Tuple{typeof(straightline_data),Tuple{Float64, Float64},Tuple{Float64, Float64},Vector{Float64},Vector{Float64},Int64}) + Base.precompile(Tuple{typeof(stroke),Int64,Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(text),String,Int64,Symbol,Vararg{Symbol, N} where N}) + Base.precompile(Tuple{typeof(text),String,Symbol,Int64,Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(text),String,Symbol}) + Base.precompile(Tuple{typeof(title!),AbstractString}) + Base.precompile(Tuple{typeof(unzip),Vector{GeometryBasics.Point2{Float64}}}) + Base.precompile(Tuple{typeof(vline!),Any}) + Base.precompile(Tuple{typeof(xgrid!),Plot{GRBackend},Symbol,Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(xgrid!),Plot{PlotlyBackend},Symbol,Vararg{Any, N} where N}) + Base.precompile(Tuple{typeof(xlims),Subplot{PlotlyBackend}}) + Base.precompile(Tuple{typeof(yaxis!),Any,Any}) + isdefined(Plots, Symbol("#add_major_or_minor_segments#125")) && Base.precompile(Tuple{getfield(Plots, Symbol("#add_major_or_minor_segments#125")),Vector{Float64},Bool,Segments{Tuple{Float64, Float64}},Float64,Bool}) + isdefined(Plots, Symbol("#add_major_or_minor_segments#126")) && Base.precompile(Tuple{getfield(Plots, Symbol("#add_major_or_minor_segments#126")),Vector{Float64},Bool,Segments{Tuple{Float64, Float64, Float64}},Float64,Bool}) + let fbody = try __lookup_kwbody__(which(plot!, (Any,))) catch missing end + if !ismissing(fbody) + precompile(fbody, (Any,typeof(plot!),Any,)) + end + end + let fbody = try __lookup_kwbody__(which(plot!, (Any,Vararg{Any, N} where N,))) catch missing end + if !ismissing(fbody) + precompile(fbody, (Any,typeof(plot!),Any,Vararg{Any, N} where N,)) + end + end + let fbody = try __lookup_kwbody__(which(plot, (Any,))) catch missing end + if !ismissing(fbody) + precompile(fbody, (Any,typeof(plot),Any,)) + end + end + let fbody = try __lookup_kwbody__(which(plot, (Any,Vararg{Any, N} where N,))) catch missing end + if !ismissing(fbody) + precompile(fbody, (Any,typeof(plot),Any,Vararg{Any, N} where N,)) + end + end + let fbody = try __lookup_kwbody__(which(text, (String,Int64,Vararg{Any, N} where N,))) catch missing end + if !ismissing(fbody) + precompile(fbody, (Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}},typeof(text),String,Int64,Vararg{Any, N} where N,)) + end + end + let fbody = try __lookup_kwbody__(which(text, (String,Symbol,Vararg{Any, N} where N,))) catch missing end + if !ismissing(fbody) + precompile(fbody, (Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}},typeof(text),String,Symbol,Vararg{Any, N} where N,)) + end + end + let fbody = try __lookup_kwbody__(which(title!, (AbstractString,))) catch missing end + if !ismissing(fbody) + precompile(fbody, (Any,typeof(title!),AbstractString,)) + end + end +end diff --git a/deps/SnoopCompile/precompile_script.jl b/deps/SnoopCompile/precompile_script.jl new file mode 100644 index 00000000..369ac499 --- /dev/null +++ b/deps/SnoopCompile/precompile_script.jl @@ -0,0 +1,4 @@ +using Plots + +Plots.test_examples(:gr, skip = Plots._backend_skips[:gr]) +Plots.test_examples(:plotly, skip = Plots._backend_skips[:plotly], disp = false) diff --git a/deps/SnoopCompile/snoop_bench.jl b/deps/SnoopCompile/snoop_bench.jl new file mode 100644 index 00000000..4bb5838e --- /dev/null +++ b/deps/SnoopCompile/snoop_bench.jl @@ -0,0 +1,8 @@ +using CompileBot + +snoop_bench( + BotConfig( + "Plots", + ), + joinpath(@__DIR__, "precompile_script.jl"), +) diff --git a/deps/SnoopCompile/snoop_bot.jl b/deps/SnoopCompile/snoop_bot.jl new file mode 100644 index 00000000..6a77f2be --- /dev/null +++ b/deps/SnoopCompile/snoop_bot.jl @@ -0,0 +1,8 @@ +using CompileBot + +snoop_bot( + BotConfig( + "Plots", + ), + joinpath(@__DIR__, "precompile_script.jl"), +) diff --git a/deps/build.jl b/deps/build.jl deleted file mode 100644 index a8cf81ff..00000000 --- a/deps/build.jl +++ /dev/null @@ -1,18 +0,0 @@ - -#TODO: download https://cdn.plot.ly/plotly-latest.min.js to deps/ if it doesn't exist -file_path = "" -if get(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL", "false") == "true" - global file_path - local_fn = joinpath(dirname(@__FILE__), "plotly-latest.min.js") - if !isfile(local_fn) - @info("Cannot find deps/plotly-latest.min.js... downloading latest version.") - download("https://cdn.plot.ly/plotly-latest.min.js", local_fn) - isfile(local_fn) && (file_path = local_fn) - else - file_path = local_fn - end -end - -open("deps.jl", "w") do io - println(io, "const plotly_local_file_path = $(repr(file_path))") -end diff --git a/deps/generateprecompiles.jl b/deps/generateprecompiles.jl deleted file mode 100644 index e522982b..00000000 --- a/deps/generateprecompiles.jl +++ /dev/null @@ -1,55 +0,0 @@ -# To figure out what should be precompiled, run this script, then move -# precompile_Plots.jl in precompiles_path (see below) to src/precompile.jl - -# This script works by using SnoopCompile to log compilations that take place -# while running the examples on the GR backend. So SnoopCompile must be -# installed, and StatsPlots, RDatasets, and FileIO are also required for -# certain examples. - -# If precompilation fails with an UndefVarError for a module, probably what is -# happening is that the module appears in the precompile statements, but is -# only visible to one of Plots' dependencies, and not Plots itself. Adding the -# module to the blacklist below will remove these precompile statements. - -# Anonymous functions may appear in precompile statements as functions with -# hashes in their name. Those of the form "#something##kw" have to do with -# compiling functions with keyword arguments, and are named reproducibly, so -# can be kept. Others generally will not work. Currently, SnoopCompile includes -# some anonymous functions that not reproducible, but SnoopCompile PR #30 -# (which looks about to be merged) will ensure that anonymous functions are -# actually defined before attempting to precompile them. Alternatively, we can -# keep only the keyword argument related anonymous functions by changing the -# regex that SnoopCompile uses to detect anonymous functions to -# r"#{1,2}[^\"#]+#{1,2}\d+" (see anonrex in SnoopCompile.jl). To exclude all -# precompile statements involving anonymous functions, "#" can also be added to -# the blacklist below. - -using SnoopCompile - -project_flag = string("--project=", joinpath(homedir(), ".julia", "dev", "Plots")) -log_path = joinpath(tempdir(), "compiles.log") -precompiles_path = joinpath(tempdir(), "precompile") - -# run examples with GR backend, logging what needs to be compiled -SnoopCompile.@snoopc project_flag log_path begin - using Plots - Plots.test_examples(:gr) - Plots.test_examples(:plotly, skip = Plots._backend_skips[:plotly]) -end - -# precompile calls containing the following strings are dropped -blacklist = [ - # functions defined in examples - "PlotExampleModule", - # the following are not visible to Plots, only its dependencies - "CategoricalArrays", - "FixedPointNumbers", - "OffsetArrays", - "SparseArrays", - "StaticArrays", - r"#{1,2}[^\"#]+#{1,2}\d+", -] - -data = SnoopCompile.read(log_path) -pc = SnoopCompile.parcel(reverse!(data[2]), blacklist=blacklist) -SnoopCompile.write(precompiles_path, pc) diff --git a/src/Plots.jl b/src/Plots.jl index 7c8b0d1e..e7a71878 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -8,9 +8,9 @@ const _current_plots_version = VersionNumber(split(first(filter(line -> occursin using Reexport -import GeometryTypes +import GeometryBasics using Dates, Printf, Statistics, Base64, LinearAlgebra, Random -import SparseArrays: findnz +using SparseArrays using FFMPEG @@ -25,17 +25,6 @@ import JSON using Requires -if isfile(joinpath(@__DIR__, "..", "deps", "deps.jl")) - include(joinpath(@__DIR__, "..", "deps", "deps.jl")) -else - # This is a bit dirty, but I don't really see why anyone should be forced - # to build Plots, while it will just include exactly the below line - # as long as `ENV["PLOTS_HOST_DEPENDENCY_LOCAL"] = "true"` is not set. - # If the above env is set + `plotly_local_file_path == ""``, - # it will warn in the __init__ function to run build - const plotly_local_file_path = "" -end - export grid, bbox, @@ -124,7 +113,11 @@ export center, BezierCurve, - plotattr + plotattr, + scalefontsize, + scalefontsizes, + resetfontsizes + # --------------------------------------------------------- @@ -161,6 +154,12 @@ const BBox = Measures.Absolute2DBox # allow pixels and percentages const px = AbsoluteLength(0.254) const pct = Length{:pct, Float64}(1.0) + +Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value) +Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value) +Base.:/(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value) +Base.:/(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value) + export BBox, BoundingBox, mm, cm, inch, px, pct, pt, w, h end @@ -188,11 +187,16 @@ import RecipesPipeline: SliceIt, datetimeformatter, timeformatter +# Use fixed version of Plotly instead of the latest one for stable dependency +# Ref: https://github.com/JuliaPlots/Plots.jl/pull/2779 +const _plotly_min_js_filename = "plotly-1.57.1.min.js" + include("types.jl") include("utils.jl") -include("components.jl") +include("colorbars.jl") include("axes.jl") include("args.jl") +include("components.jl") include("themes.jl") include("plot.jl") include("pipeline.jl") @@ -208,6 +212,7 @@ include("output.jl") include("ijulia.jl") include("fileio.jl") include("init.jl") +include("legend.jl") include("backends/plotly.jl") include("backends/gr.jl") @@ -225,8 +230,8 @@ let PlotOrSubplot = Union{Plot, Subplot} global xlims!(plt::PlotOrSubplot, xmin::Real, xmax::Real; kw...) = plot!(plt; xlims = (xmin,xmax), kw...) global ylims!(plt::PlotOrSubplot, ymin::Real, ymax::Real; kw...) = plot!(plt; ylims = (ymin,ymax), kw...) global zlims!(plt::PlotOrSubplot, zmin::Real, zmax::Real; kw...) = plot!(plt; zlims = (zmin,zmax), kw...) - global xticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) where {T<:Real} = plot!(plt; xticks = ticks, kw...) - global yticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) where {T<:Real} = plot!(plt; yticks = ticks, kw...) + global xticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) = plot!(plt; xticks = ticks, kw...) + global yticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) = plot!(plt; yticks = ticks, kw...) global xticks!(plt::PlotOrSubplot, ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(plt; xticks = (ticks,labels), kw...) global yticks!(plt::PlotOrSubplot, @@ -246,7 +251,6 @@ end const CURRENT_BACKEND = CurrentBackend(:none) -include("precompile.jl") -_precompile_() +include("precompile_includer.jl") end # module diff --git a/src/animation.jl b/src/animation.jl index 610bc558..cb09e527 100644 --- a/src/animation.jl +++ b/src/animation.jl @@ -90,15 +90,15 @@ function buildanimation(anim::Animation, fn::AbstractString, if variable_palette # generate a colorpalette for each frame for highest quality, but larger filesize palette="palettegen=stats_mode=single[pal],[0:v][pal]paletteuse=new=1" - ffmpeg_exe(`-v $verbose_level -framerate $framerate -loop $loop -i $(animdir)/%06d.png -lavfi "$palette" -y $fn`) + ffmpeg_exe(`-v $verbose_level -framerate $framerate -i $(animdir)/%06d.png -lavfi "$palette" -loop $loop -y $fn`) else # generate a colorpalette first so ffmpeg does not have to guess it ffmpeg_exe(`-v $verbose_level -i $(animdir)/%06d.png -vf "palettegen=stats_mode=diff" -y "$(animdir)/palette.bmp"`) # then apply the palette to get better results - ffmpeg_exe(`-v $verbose_level -framerate $framerate -loop $loop -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -y $fn`) + ffmpeg_exe(`-v $verbose_level -framerate $framerate -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -loop $loop -y $fn`) end else - ffmpeg_exe(`-v $verbose_level -framerate $framerate -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`) + ffmpeg_exe(`-v $verbose_level -framerate $framerate -i $(animdir)/%06d.png -vf format=yuv420p -loop $loop -y $fn`) end show_msg && @info("Saved animation to ", fn) @@ -137,8 +137,8 @@ end # ----------------------------------------------- function _animate(forloop::Expr, args...; callgif = false) - if forloop.head != :for - error("@animate macro expects a for-block. got: $(forloop.head)") + if forloop.head ∉ (:for, :while) + error("@animate macro expects a for- or while-block. got: $(forloop.head)") end # add the call to frame to the end of each iteration diff --git a/src/arg_desc.jl b/src/arg_desc.jl index bbeb0075..9ea4f463 100644 --- a/src/arg_desc.jl +++ b/src/arg_desc.jl @@ -2,163 +2,185 @@ const _arg_desc = KW( # series args -:label => "String type. The label for a series, which appears in a legend. If empty, no legend entry is added.", -:seriescolor => "Color Type. The base color for this series. `:auto` (the default) will select a color from the subplot's `color_palette`, based on the order it was added to the subplot", -:seriesalpha => "Number in [0,1]. The alpha/opacity override for the series. `nothing` (the default) means it will take the alpha value of the color.", -:seriestype => "Symbol. This is the identifier of the type of visualization for this series. Choose from $(_allTypes) or any series recipes which are defined.", -:linestyle => "Symbol. Style of the line (for path and bar stroke). Choose from $(_allStyles)", -:linewidth => "Number. Width of the line (in pixels)", -:linecolor => "Color Type. Color of the line (for path and bar stroke). `:match` will take the value from `:seriescolor`, (though histogram/bar types use `:black` as a default).", -:linealpha => "Number in [0,1]. The alpha/opacity override for the line. `nothing` (the default) means it will take the alpha value of linecolor.", -:fillrange => "Number or AbstractVector. Fills area between fillrange and y for line-types, sets the base for bar/stick types, and similar for other types.", -:fillcolor => "Color Type. Color of the filled area of path or bar types. `:match` will take the value from `:seriescolor`.", -:fillalpha => "Number in [0,1]. The alpha/opacity override for the fill area. `nothing` (the default) means it will take the alpha value of fillcolor.", -:markershape => "Symbol, Shape, or AbstractVector. Choose from $(_allMarkers).", -:markercolor => "Color Type. Color of the interior of the marker or shape. `:match` will take the value from `:seriescolor`.", -:markeralpha => "Number in [0,1]. The alpha/opacity override for the marker interior. `nothing` (the default) means it will take the alpha value of markercolor.", -:markersize => "Number or AbstractVector. Size (radius pixels) of the markers.", -:markerstrokestyle => "Symbol. Style of the marker stroke (border). Choose from $(_allStyles)", -:markerstrokewidth => "Number. Width of the marker stroke (border. in pixels)", -:markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:foreground_color_subplot`.", -:markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.", -:bins => "Integer, NTuple{2,Integer}, AbstractVector or Symbol. Default is :auto (the Freedman-Diaconis rule). For histogram-types, defines the approximate number of bins to aim for, or the auto-binning algorithm to use (:sturges, :sqrt, :rice, :scott or :fd). For fine-grained control pass a Vector of break values, e.g. `range(minimum(x), stop = maximum(x), length = 25)`", -:smooth => "Bool. Add a regression line?", -:group => "AbstractVector. Data is split into a separate series, one for each unique value in `group`.", -:x => "Various. Input data. First Dimension", -:y => "Various. Input data. Second Dimension", -:z => "Various. Input data. Third Dimension. May be wrapped by a `Surface` for surface and heatmap types.", -:marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.", -:line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).", -:fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.", -:levels => "Integer, NTuple{2,Integer}, or AbstractVector. Levels or number of levels (or x-levels/y-levels) for a contour type.", -:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).", -:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)", -:bar_width => "nothing or Number. Width of bars in data coordinates. When nothing, chooses based on x (or y when `orientation = :h`).", -:bar_edges => "Bool. Align bars to edges (true), or centers (the default)?", -:xerror => "AbstractVector or 2-Tuple of Vectors. x (horizontal) error relative to x-value. If 2-tuple of vectors, the first vector corresponds to the left error (and the second to the right)", -:yerror => "AbstractVector or 2-Tuple of Vectors. y (vertical) error relative to y-value. If 2-tuple of vectors, the first vector corresponds to the bottom error (and the second to the top)", -:ribbon => "Number or AbstractVector. Creates a fillrange around the data points.", -:quiver => "AbstractVector or 2-Tuple of vectors. The directional vectors U,V which specify velocity/gradient vectors for a quiver plot.", -:arrow => "nothing (no arrows), Bool (if true, default arrows), Arrow object, or arg(s) that could be style or head length/widths. Defines arrowheads that should be displayed at the end of path line segments (just before a NaN and the last non-NaN point). Used in quiverplot, streamplot, or similar.", -:normalize => "Bool or Symbol. Histogram normalization mode. Possible values are: false/:none (no normalization, default), true/:pdf (normalize to a discrete Probability Density Function, where the total area of the bins is 1), :probability (bin heights sum to 1) and :density (the area of each bin, rather than the height, is equal to the counts - useful for uneven bin sizes).", -:weights => "AbstractVector. Used in histogram types for weighted counts.", -:show_empty_bins => "Bool. Whether empty bins in a 2D histogram are colored as 0 (true), or transparent (the default).", -:contours => "Bool. Add contours to the side-grids of 3D plots? Used in surface/wireframe.", -:contour_labels => "Bool. Show labels at the contour lines?", -:match_dimensions => "Bool. For heatmap types... should the first dimension of a matrix (rows) correspond to the first dimension of the plot (x-axis)? The default is false, which matches the behavior of Matplotlib, Plotly, and others. Note: when passing a function for z, the function should still map `(x,y) -> z`.", -:subplot => "Integer (subplot index) or Subplot object. The subplot that this series belongs to.", -:series_annotations => "AbstractVector of String or PlotText. These are annotations which are mapped to data points/positions.", -:primary => "Bool. Does this count as a 'real series'? For example, you could have a path (primary), and a scatter (secondary) as 2 separate series, maybe with different data (see sticks recipe for an example). The secondary series will get the same color, etc as the primary.", -:hover => "nothing or vector of strings. Text to display when hovering over each data point.", -:colorbar_entry => "Bool. Include this series in the color bar? Set to `false` to exclude.", +:label => "String type. The label for a series, which appears in a legend. If empty, no legend entry is added.", +:seriescolor => "Color Type. The base color for this series. `:auto` (the default) will select a color from the subplot's `color_palette`, based on the order it was added to the subplot", +:seriesalpha => "Number in [0,1]. The alpha/opacity override for the series. `nothing` (the default) means it will take the alpha value of the color.", +:seriestype => "Symbol. This is the identifier of the type of visualization for this series. Choose from $(_allTypes) or any series recipes which are defined.", +:linestyle => "Symbol. Style of the line (for path and bar stroke). Choose from $(_allStyles)", +:linewidth => "Number. Width of the line (in pixels)", +:linecolor => "Color Type. Color of the line (for path and bar stroke). `:match` will take the value from `:seriescolor`, (though histogram/bar types use `:black` as a default).", +:linealpha => "Number in [0,1]. The alpha/opacity override for the line. `nothing` (the default) means it will take the alpha value of linecolor.", +:fillrange => "Number or AbstractVector. Fills area between fillrange and y for line-types, sets the base for bar/stick types, and similar for other types.", +:fillcolor => "Color Type. Color of the filled area of path or bar types. `:match` will take the value from `:seriescolor`.", +:fillalpha => "Number in [0,1]. The alpha/opacity override for the fill area. `nothing` (the default) means it will take the alpha value of fillcolor.", +:markershape => "Symbol, Shape, or AbstractVector. Choose from $(_allMarkers).", +:markercolor => "Color Type. Color of the interior of the marker or shape. `:match` will take the value from `:seriescolor`.", +:markeralpha => "Number in [0,1]. The alpha/opacity override for the marker interior. `nothing` (the default) means it will take the alpha value of markercolor.", +:markersize => "Number or AbstractVector. Size (radius pixels) of the markers.", +:markerstrokestyle => "Symbol. Style of the marker stroke (border). Choose from $(_allStyles)", +:markerstrokewidth => "Number. Width of the marker stroke (border. in pixels)", +:markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:foreground_color_subplot`.", +:markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.", +:bins => "Integer, NTuple{2,Integer}, AbstractVector or Symbol. Default is :auto (the Freedman-Diaconis rule). For histogram-types, defines the approximate number of bins to aim for, or the auto-binning algorithm to use (:sturges, :sqrt, :rice, :scott or :fd). For fine-grained control pass a Vector of break values, e.g. `range(minimum(x), stop = maximum(x), length = 25)`", +:smooth => "Bool. Add a regression line?", +:group => "AbstractVector. Data is split into a separate series, one for each unique value in `group`.", +:x => "Various. Input data. First Dimension", +:y => "Various. Input data. Second Dimension", +:z => "Various. Input data. Third Dimension. May be wrapped by a `Surface` for surface and heatmap types.", +:marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.", +:line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).", +:fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.", +:levels => "Integer, NTuple{2,Integer}, or AbstractVector. Levels or number of levels (or x-levels/y-levels) for a contour type.", +:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).", +:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)", +:bar_width => "nothing or Number. Width of bars in data coordinates. When nothing, chooses based on x (or y when `orientation = :h`).", +:bar_edges => "Bool. Align bars to edges (true), or centers (the default)?", +:xerror => "AbstractVector or 2-Tuple of Vectors. x (horizontal) error relative to x-value. If 2-tuple of vectors, the first vector corresponds to the left error (and the second to the right)", +:yerror => "AbstractVector or 2-Tuple of Vectors. y (vertical) error relative to y-value. If 2-tuple of vectors, the first vector corresponds to the bottom error (and the second to the top)", +:ribbon => "Number or AbstractVector. Creates a fillrange around the data points.", +:quiver => "AbstractVector or 2-Tuple of vectors. The directional vectors U,V which specify velocity/gradient vectors for a quiver plot.", +:arrow => "nothing (no arrows), Bool (if true, default arrows), Arrow object, or arg(s) that could be style or head length/widths. Defines arrowheads that should be displayed at the end of path line segments (just before a NaN and the last non-NaN point). Used in quiverplot, streamplot, or similar.", +:normalize => "Bool or Symbol. Histogram normalization mode. Possible values are: false/:none (no normalization, default), true/:pdf (normalize to a discrete Probability Density Function, where the total area of the bins is 1), :probability (bin heights sum to 1) and :density (the area of each bin, rather than the height, is equal to the counts - useful for uneven bin sizes).", +:weights => "AbstractVector. Used in histogram types for weighted counts.", +:show_empty_bins => "Bool. Whether empty bins in a 2D histogram are colored as 0 (true), or transparent (the default).", +:contours => "Bool. Add contours to the side-grids of 3D plots? Used in surface/wireframe.", +:contour_labels => "Bool. Show labels at the contour lines?", +:match_dimensions => "Bool. For heatmap types... should the first dimension of a matrix (rows) correspond to the first dimension of the plot (x-axis)? The default is false, which matches the behavior of Matplotlib, Plotly, and others. Note: when passing a function for z, the function should still map `(x,y) -> z`.", +:subplot => "Integer (subplot index) or Subplot object. The subplot that this series belongs to.", +:series_annotations => "AbstractVector of String or PlotText. These are annotations which are mapped to data points/positions.", +:primary => "Bool. Does this count as a 'real series'? For example, you could have a path (primary), and a scatter (secondary) as 2 separate series, maybe with different data (see sticks recipe for an example). The secondary series will get the same color, etc as the primary.", +:hover => "nothing or vector of strings. Text to display when hovering over each data point.", +:colorbar_entry => "Bool. Include this series in the color bar? Set to `false` to exclude.", # plot args -:plot_title => "String. Title for the whole plot (not the subplots) (Note: Not currently implemented)", -:background_color => "Color Type. Base color for all backgrounds.", -:background_color_outside => "Color Type or `:match` (matches `:background_color`). Color outside the plot area(s)", -:foreground_color => "Color Type. Base color for all foregrounds.", -:size => "NTuple{2,Int}. (width_px, height_px) of the whole Plot", -:pos => "NTuple{2,Int}. (left_px, top_px) position of the GUI window (note: currently unimplemented)", -:window_title => "String. Title of the window.", -:show => "Bool. Should this command open/refresh a GUI/display? This allows displaying in scripts or functions without explicitly calling `display`", -:layout => "Integer (number of subplots), NTuple{2,Integer} (grid dimensions), AbstractLayout (for example `grid(2,2)`), or the return from the `@layout` macro. This builds the layout of subplots.", -:link => "Symbol. How/whether to link axis limits between subplots. Values: `:none`, `:x` (x axes are linked by columns), `:y` (y axes are linked by rows), `:both` (x and y are linked), `:all` (every subplot is linked together regardless of layout position).", -:overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).", -:html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.", -:tex_output_standalone => "Bool. When writing tex output, should the source include a preamble for a standalone document class.", -:inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots", -:dpi => "Number. Dots Per Inch of output figures", -:thickness_scaling => "Number. Scale for the thickness of all line elements like lines, borders, axes, grid lines, ... defaults to 1.", -:display_type => "Symbol (`:auto`, `:gui`, or `:inline`). When supported, `display` will either open a GUI window or plot inline.", -:extra_kwargs => "Either one of (`:plot`, `:subplot`, `:series`) to specify for which element extra keyword args are collected or a KW (Dict{Symbol,Any}) to pass a map of extra keyword args which may be specific to a backend. Default: `:series`.\n Example: `pgfplotsx(); scatter(1:5, extra_kwargs=Dict(:subplot=>Dict(\"axis line shift\" => \"10pt\"))`", -:fontfamily => "String or Symbol. Default font family for title, legend entries, tick labels and guides", -:warn_on_unsupported => "Bool. Warn on unsupported attributes, series types and marker shapes", +:plot_title => "String. Title for the whole plot (not the subplots) (Note: Not currently implemented)", +:background_color => "Color Type. Base color for all backgrounds.", +:background_color_outside => "Color Type or `:match` (matches `:background_color`). Color outside the plot area(s)", +:foreground_color => "Color Type. Base color for all foregrounds.", +:size => "NTuple{2,Int}. (width_px, height_px) of the whole Plot", +:pos => "NTuple{2,Int}. (left_px, top_px) position of the GUI window (note: currently unimplemented)", +:window_title => "String. Title of the standalone gui-window.", +:show => "Bool. Should this command open/refresh a GUI/display? This allows displaying in scripts or functions without explicitly calling `display`", +:layout => "Integer (number of subplots), NTuple{2,Integer} (grid dimensions), AbstractLayout (for example `grid(2,2)`), or the return from the `@layout` macro. This builds the layout of subplots.", +:link => "Symbol. How/whether to link axis limits between subplots. Values: `:none`, `:x` (x axes are linked by columns), `:y` (y axes are linked by rows), `:both` (x and y are linked), `:all` (every subplot is linked together regardless of layout position).", +:overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).", +:html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.", +:tex_output_standalone => "Bool. When writing tex output, should the source include a preamble for a standalone document class.", +:inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots", +:dpi => "Number. Dots Per Inch of output figures", +:thickness_scaling => "Number. Scale for the thickness of all line elements like lines, borders, axes, grid lines, ... defaults to 1.", +:display_type => "Symbol (`:auto`, `:gui`, or `:inline`). When supported, `display` will either open a GUI window or plot inline.", +:extra_kwargs => "Either one of (`:plot`, `:subplot`, `:series`) to specify for which element extra keyword args are collected or a KW (Dict{Symbol,Any}) to pass a map of extra keyword args which may be specific to a backend. Default: `:series`.\n Example: `pgfplotsx(); scatter(1:5, extra_kwargs=Dict(:subplot=>Dict(\"axis line shift\" => \"10pt\"))`", +:fontfamily => "String or Symbol. Default font family for title, legend entries, tick labels and guides", +:warn_on_unsupported => "Bool. Warn on unsupported attributes, series types and marker shapes", # subplot args -:title => "String. Subplot title.", -:titlelocation => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`", -:titlefontfamily => "String or Symbol. Font family of subplot title.", -:titlefontsize => "Integer. Font pointsize of subplot title.", -:titlefonthalign => "Symbol. Font horizontal alignment of subplot title: :hcenter, :left, :right or :center", -:titlefontvalign => "Symbol. Font vertical alignment of subplot title: :vcenter, :top, :bottom or :center", -:titlefontrotation => "Real. Font rotation of subplot title", -:titlefontcolor => "Color Type. Font color of subplot title", -:background_color_subplot => "Color Type or `:match` (matches `:background_color`). Base background color of the subplot.", -:background_color_legend => "Color Type or `:match` (matches `:background_color_subplot`). Background color of the legend.", -:background_color_inside => "Color Type or `:match` (matches `:background_color_subplot`). Background color inside the plot area (under the grid).", -:foreground_color_subplot => "Color Type or `:match` (matches `:foreground_color`). Base foreground color of the subplot.", -:foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.", -:foreground_color_title => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of subplot title.", -:color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.", -:legend => "Bool (show the legend?) or (x,y) tuple or Symbol (legend position). Bottom left corner of legend is placed at (x,y). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` , `:inline` (note: only some may be supported in each backend)", -:legendfontfamily => "String or Symbol. Font family of legend entries.", -:legendfontsize => "Integer. Font pointsize of legend entries.", -:legendfonthalign => "Symbol. Font horizontal alignment of legend entries: :hcenter, :left, :right or :center", -:legendfontvalign => "Symbol. Font vertical alignment of legend entries: :vcenter, :top, :bottom or :center", -:legendfontrotation => "Real. Font rotation of legend entries", -:legendfontcolor => "Color Type. Font color of legend entries", -:legendtitlefontfamily => "String or Symbol. Font family of the legend title.", -:legendtitlefontsize => "Integer. Font pointsize the legend title.", -:legendtitlefonthalign => "Symbol. Font horizontal alignment of the legend title: :hcenter, :left, :right or :center", -:legendtitlefontvalign => "Symbol. Font vertical alignment of the legend title: :vcenter, :top, :bottom or :center", -:legendtitlefontrotation => "Real. Font rotation of the legend title", -:legendtitlefontcolor => "Color Type. Font color of the legend title", -:colorbar => "Bool (show the colorbar?) or Symbol (colorbar position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:legend` (matches legend value) (note: only some may be supported in each backend)", -:clims => "`:auto`, NTuple{2,Number}, or a function that takes series data in and returns NTuple{2,Number}. Fixes the limits of the colorbar.", -:legendfont => "Font. Font of legend items.", -:legendtitlefont => "Font. Font of the legend title.", -:annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String or PlotText (created with `text(args...)`) Add one-off text annotations at the x,y coordinates.", -:projection => "Symbol or String. '3d' or 'polar'", -:aspect_ratio => "Symbol (:equal) or Number. Plot area is resized so that 1 y-unit is the same size as `aspect_ratio` x-units.", -:margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.", -:left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.", -:top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.", -:right_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the right of the subplot.", -:bottom_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the bottom of the subplot.", -:subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.", -:colorbar_title => "String. Title of colorbar.", -:framestyle => "Symbol. Style of the axes frame. Choose from $(_allFramestyles)", -:camera => "NTuple{2, Real}. Sets the view angle (azimuthal, elevation) for 3D plots", +:title => "String. Subplot title.", +:titlelocation => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`", +:titlefontfamily => "String or Symbol. Font family of subplot title.", +:titlefontsize => "Integer. Font pointsize of subplot title.", +:titlefonthalign => "Symbol. Font horizontal alignment of subplot title: :hcenter, :left, :right or :center", +:titlefontvalign => "Symbol. Font vertical alignment of subplot title: :vcenter, :top, :bottom or :center", +:titlefontrotation => "Real. Font rotation of subplot title", +:titlefontcolor => "Color Type. Font color of subplot title", +:background_color_subplot => "Color Type or `:match` (matches `:background_color`). Base background color of the subplot.", +:background_color_legend => "Color Type or `:match` (matches `:background_color_subplot`). Background color of the legend.", +:background_color_inside => "Color Type or `:match` (matches `:background_color_subplot`). Background color inside the plot area (under the grid).", +:foreground_color_subplot => "Color Type or `:match` (matches `:foreground_color`). Base foreground color of the subplot.", +:foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.", +:foreground_color_title => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of subplot title.", +:color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.", +:legend => "Bool (show the legend?) or (x,y) tuple or Symbol (legend position) or angle or (angle,inout) tuple. Bottom left corner of legend is placed at (x,y). Symbol values: `:none`; `:best`; `:inline`; `:inside`; `:legend`; any valid combination of `:(outer ?)(top/bottom ?)(right/left ?)`, i.e.: `:top`, `:topright`, `:outerleft`, `:outerbottomright` ... (note: only some may be supported in each backend). Legend is positioned at (angle degrees) (so (90,:outer) is roughly equivalent to :outertop), close to the inside of the axes or the outside if inout=:outer.", +:legendfontfamily => "String or Symbol. Font family of legend entries.", +:legendfontsize => "Integer. Font pointsize of legend entries.", +:legendfonthalign => "Symbol. Font horizontal alignment of legend entries: :hcenter, :left, :right or :center", +:legendfontvalign => "Symbol. Font vertical alignment of legend entries: :vcenter, :top, :bottom or :center", +:legendfontrotation => "Real. Font rotation of legend entries", +:legendfontcolor => "Color Type. Font color of legend entries", +:legendtitle => "String. Legend title.", +:legendtitlefontfamily => "String or Symbol. Font family of the legend title.", +:legendtitlefontsize => "Integer. Font pointsize the legend title.", +:legendtitlefonthalign => "Symbol. Font horizontal alignment of the legend title: :hcenter, :left, :right or :center", +:legendtitlefontvalign => "Symbol. Font vertical alignment of the legend title: :vcenter, :top, :bottom or :center", +:legendtitlefontrotation => "Real. Font rotation of the legend title", +:legendtitlefontcolor => "Color Type. Font color of the legend title", +:colorbar => "Bool (show the colorbar?) or Symbol (colorbar position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:legend` (matches legend value) (note: only some may be supported in each backend)", +:clims => "`:auto`, NTuple{2,Number}, or a function that takes series data in and returns NTuple{2,Number}. Fixes the limits of the colorbar.", +:colorbar_fontfamily => "String or Symbol. Font family of colobar entries.", +:colorbar_ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`", +:colorbar_tickfontfamily => "String or Symbol. Font family of colorbar tick labels.", +:colorbar_tickfontsize => "Integer. Font pointsize of colorbar tick entries.", +:colorbar_tickfontcolor => "Color Type. Font color of colorbar tick entries", +:colorbar_scale => "Symbol. Scale of the colorbar axis: `:none`, `:ln`, `:log2`, `:log10`", +:colorbar_formatter => "Function, :scientific, :plain or :auto. A method which converts a number to a string for tick labeling.", +:legendfont => "Font. Font of legend items.", +:legendtitlefont => "Font. Font of the legend title.", +:annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String or PlotText (created with `text(args...)`) Add one-off text annotations at the x,y coordinates.", +:annotationfontfamily => "String or Symbol. Font family of annotations.", +:annotationfontsize => "Integer. Font pointsize of annotations.", +:annotationhalign => "Symbol. horizontal alignment of annotations, :hcenter, :left, :right or :center.", +:annotationvalign => "Symbol. Vertical alignment of annotations, :vcenter, :top, :bottom or :center.", +:annotationrotation => "Float. Rotation of annotations in degrees.", +:annotationcolor => "Colorant or :match. Color of annotations.", +:projection => "Symbol or String. '3d' or 'polar'", +:aspect_ratio => "Symbol (:equal or :none) or Number. Plot area is resized so that 1 y-unit is the same size as `aspect_ratio` x-units. With `:none`, images inherit aspect ratio of the plot area.", +:margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.", +:left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.", +:top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.", +:right_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the right of the subplot.", +:bottom_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the bottom of the subplot.", +:subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.", +:colorbar_title => "String. Title of colorbar.", +:framestyle => "Symbol. Style of the axes frame. Choose from $(_allFramestyles)", +:camera => "NTuple{2, Real}. Sets the view angle (azimuthal, elevation) for 3D plots", # axis args -:guide => "String. Axis guide (label).", -:guide_position => "Symbol. Position of axis guides: :top, :bottom, :left or :right", -:lims => "NTuple{2,Number} or Symbol. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example). `:round` widens the limit to the nearest round number ie. [0.1,3.6]=>[0.0,4.0]", -:ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`", -:scale => "Symbol. Scale of the axis: `:none`, `:ln`, `:log2`, `:log10`", -:rotation => "Number. Degrees rotation of tick labels.", -:flip => "Bool. Should we flip (reverse) the axis?", -:formatter => "Function, :scientific, :plain or :auto. A method which converts a number to a string for tick labeling.", -:tickfontfamily => "String or Symbol. Font family of tick labels.", -:tickfontsize => "Integer. Font pointsize of tick labels.", -:tickfonthalign => "Symbol. Font horizontal alignment of tick labels: :hcenter, :left, :right or :center", -:tickfontvalign => "Symbol. Font vertical alignment of tick labels: :vcenter, :top, :bottom or :center", -:tickfontrotation => "Real. Font rotation of tick labels", -:tickfontcolor => "Color Type. Font color of tick labels", -:guidefontfamily => "String or Symbol. Font family of axes guides.", -:guidefontsize => "Integer. Font pointsize of axes guides.", -:guidefonthalign => "Symbol. Font horizontal alignment of axes guides: :hcenter, :left, :right or :center", -:guidefontvalign => "Symbol. Font vertical alignment of axes guides: :vcenter, :top, :bottom or :center", -:guidefontrotation => "Real. Font rotation of axes guides", -:guidefontcolor => "Color Type. Font color of axes guides", -:foreground_color_axis => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis ticks.", -:foreground_color_border => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of plot area border (spines).", -:foreground_color_text => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of tick labels.", -:foreground_color_guide => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis guides (axis labels).", -:mirror => "Bool. Switch the side of the tick labels (right or top).", -:grid => "Bool, Symbol, String or `nothing`. Show the grid lines? `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:none`, `:off`", -:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.", -:gridalpha => "Number in [0,1]. The alpha/opacity override for the grid lines.", -:gridstyle => "Symbol. Style of the grid lines. Choose from $(_allStyles)", -:gridlinewidth => "Number. Width of the grid lines (in pixels)", +:guide => "String. Axis guide (label).", +:guide_position => "Symbol. Position of axis guides: :top, :bottom, :left or :right", +:lims => """ + NTuple{2,Number} or Symbol. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example). + `:round` widens the limit to the nearest round number ie. [0.1,3.6]=>[0.0,4.0] + `:symmetric` sets the limits to be symmetric around zero. + Set widen=true to widen the specified limits (as occurs when lims are not specified). + """, +:ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`", +:scale => "Symbol. Scale of the axis: `:none`, `:ln`, `:log2`, `:log10`", +:rotation => "Number. Degrees rotation of tick labels.", +:flip => "Bool. Should we flip (reverse) the axis?", +:formatter => "Function, :scientific, :plain or :auto. A method which converts a number to a string for tick labeling.", +:tickfontfamily => "String or Symbol. Font family of tick labels.", +:tickfontsize => "Integer. Font pointsize of tick labels.", +:tickfonthalign => "Symbol. Font horizontal alignment of tick labels: :hcenter, :left, :right or :center", +:tickfontvalign => "Symbol. Font vertical alignment of tick labels: :vcenter, :top, :bottom or :center", +:tickfontrotation => "Real. Font rotation of tick labels", +:tickfontcolor => "Color Type. Font color of tick labels", +:guidefontfamily => "String or Symbol. Font family of axes guides.", +:guidefontsize => "Integer. Font pointsize of axes guides.", +:guidefonthalign => "Symbol. Font horizontal alignment of axes guides: :hcenter, :left, :right or :center", +:guidefontvalign => "Symbol. Font vertical alignment of axes guides: :vcenter, :top, :bottom or :center", +:guidefontrotation => "Real. Font rotation of axes guides", +:guidefontcolor => "Color Type. Font color of axes guides", +:foreground_color_axis => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis ticks.", +:foreground_color_border => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of plot area border (spines).", +:foreground_color_text => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of tick labels.", +:foreground_color_guide => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis guides (axis labels).", +:mirror => "Bool. Switch the side of the tick labels (right or top).", +:grid => "Bool, Symbol, String or `nothing`. Show the grid lines? `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:none`, `:off`", +:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.", +:gridalpha => "Number in [0,1]. The alpha/opacity override for the grid lines.", +:gridstyle => "Symbol. Style of the grid lines. Choose from $(_allStyles)", +:gridlinewidth => "Number. Width of the grid lines (in pixels)", :foreground_color_minor_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of minor grid lines.", -:minorgrid => "Bool. Adds minor grid lines and ticks to the plot. Set minorticks to change number of gridlines", -:minorticks => "Integer. Intervals to divide the gap between major ticks into", -:minorgridalpha => "Number in [0,1]. The alpha/opacity override for the minorgrid lines.", -:minorgridstyle => "Symbol. Style of the minor grid lines. Choose from $(_allStyles)", -:minorgridlinewidth => "Number. Width of the minor grid lines (in pixels)", -:tick_direction => "Symbol. Direction of the ticks. `:in` or `:out`", -:showaxis => "Bool, Symbol or String. Show the axis. `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:off`", -:widen => "Bool. Widen the axis limits by a small factor to avoid cut-off markers and lines at the borders. Defaults to `true`.", -:draw_arrow => "Bool. Draw arrow at the end of the axis.", +:minorgrid => "Bool. Adds minor grid lines and ticks to the plot. Set minorticks to change number of gridlines", +:minorticks => "Integer. Intervals to divide the gap between major ticks into", +:minorgridalpha => "Number in [0,1]. The alpha/opacity override for the minorgrid lines.", +:minorgridstyle => "Symbol. Style of the minor grid lines. Choose from $(_allStyles)", +:minorgridlinewidth => "Number. Width of the minor grid lines (in pixels)", +:tick_direction => "Symbol. Direction of the ticks. `:in`, `:out` or `:none`", +:showaxis => "Bool, Symbol or String. Show the axis. `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:off`", +:widen => """ + Bool or :auto. Widen the axis limits by a small factor to avoid cut-off markers and lines at the borders. + Defaults to `:auto`, which widens unless limits were manually set. + """, +:draw_arrow => "Bool. Draw arrow at the end of the axis.", ) diff --git a/src/args.jl b/src/args.jl index 3b779f07..965f3fc2 100644 --- a/src/args.jl +++ b/src/args.jl @@ -1,26 +1,46 @@ +function makeplural(s::Symbol) + str = string(s) + if last(str) != 's' + return Symbol(string(s,"s")) + end + return s +end +function make_non_underscore(s::Symbol) + str = string(s) + str = replace(str, "_" => "") + return Symbol(str) +end const _keyAliases = Dict{Symbol,Symbol}() function add_aliases(sym::Symbol, aliases::Symbol...) for alias in aliases - if haskey(_keyAliases, alias) - error("Already an alias $alias => $(_keyAliases[alias])... can't also alias $sym") + if haskey(_keyAliases, alias) || alias === sym + return nothing end _keyAliases[alias] = sym end + return nothing end function add_non_underscore_aliases!(aliases::Dict{Symbol,Symbol}) for (k,v) in aliases s = string(k) if '_' in s - aliases[Symbol(replace(s, "_" => ""))] = v + aliases[make_non_underscore(k)] = v end end end - +function add_non_underscore_aliases!(aliases::Dict{Symbol,Symbol}, args::Vector{Symbol}) + for arg in args + s = string(arg) + if '_' in s + aliases[make_non_underscore(arg)] = arg + end + end +end # ------------------------------------------------------------ const _allAxes = [:auto, :left, :right] @@ -31,10 +51,10 @@ const _axesAliases = Dict{Symbol,Symbol}( ) const _3dTypes = [ - :path3d, :scatter3d, :surface, :wireframe, :contour3d, :volume + :path3d, :scatter3d, :surface, :wireframe, :contour3d, :volume, :mesh3d ] const _allTypes = vcat([ - :none, :line, :path, :steppre, :steppost, :sticks, :scatter, + :none, :line, :path, :steppre, :stepmid, :steppost, :sticks, :scatter, :heatmap, :hexbin, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :histogram3d, :density, :bar, :hline, :vline, @@ -81,7 +101,7 @@ const _typeAliases = Dict{Symbol,Symbol}( add_non_underscore_aliases!(_typeAliases) const _histogram_like = [:histogram, :barhist, :barbins] -const _line_like = [:line, :path, :steppre, :steppost] +const _line_like = [:line, :path, :steppre, :stepmid, :steppost] const _surface_like = [:contour, :contourf, :contour3d, :heatmap, :surface, :wireframe, :image] like_histogram(seriestype::Symbol) = seriestype in _histogram_like @@ -104,56 +124,81 @@ const _styleAliases = Dict{Symbol,Symbol}( :ddd => :dashdotdot, ) +const _shape_keys = Symbol[ + :circle, + :rect, + :star5, + :diamond, + :hexagon, + :cross, + :xcross, + :utriangle, + :dtriangle, + :rtriangle, + :ltriangle, + :pentagon, + :heptagon, + :octagon, + :star4, + :star6, + :star7, + :star8, + :vline, + :hline, + :+, + :x, +] + const _allMarkers = vcat(:none, :auto, _shape_keys) #sort(collect(keys(_shapes)))) const _markerAliases = Dict{Symbol,Symbol}( - :n => :none, - :no => :none, - :a => :auto, - :ellipse => :circle, - :c => :circle, - :circ => :circle, - :square => :rect, - :sq => :rect, - :r => :rect, - :d => :diamond, - :^ => :utriangle, - :ut => :utriangle, - :utri => :utriangle, - :uptri => :utriangle, - :uptriangle => :utriangle, - :v => :dtriangle, - :V => :dtriangle, - :dt => :dtriangle, - :dtri => :dtriangle, - :downtri => :dtriangle, - :downtriangle => :dtriangle, - :> => :rtriangle, - :rt => :rtriangle, - :rtri => :rtriangle, + :n => :none, + :no => :none, + :a => :auto, + :ellipse => :circle, + :c => :circle, + :circ => :circle, + :square => :rect, + :sq => :rect, + :r => :rect, + :d => :diamond, + :^ => :utriangle, + :ut => :utriangle, + :utri => :utriangle, + :uptri => :utriangle, + :uptriangle => :utriangle, + :v => :dtriangle, + :V => :dtriangle, + :dt => :dtriangle, + :dtri => :dtriangle, + :downtri => :dtriangle, + :downtriangle => :dtriangle, + :> => :rtriangle, + :rt => :rtriangle, + :rtri => :rtriangle, :righttri => :rtriangle, :righttriangle => :rtriangle, - :< => :ltriangle, - :lt => :ltriangle, - :ltri => :ltriangle, + :< => :ltriangle, + :lt => :ltriangle, + :ltri => :ltriangle, :lighttri => :ltriangle, :lighttriangle => :ltriangle, - # :+ => :cross, - :plus => :cross, - # :x => :xcross, - :X => :xcross, - :star => :star5, - :s => :star5, - :star1 => :star5, - :s2 => :star8, - :star2 => :star8, - :p => :pentagon, - :pent => :pentagon, - :h => :hexagon, - :hex => :hexagon, - :hep => :heptagon, - :o => :octagon, - :oct => :octagon, - :spike => :vline, + # :+ => :cross, + :plus => :cross, + # :x => :xcross, + :X => :xcross, + :star => :star5, + :s => :star5, + :star1 => :star5, + :s2 => :star8, + :star2 => :star8, + :p => :pentagon, + :pent => :pentagon, + :h => :hexagon, + :hex => :hexagon, + :hep => :heptagon, + :o => :octagon, + :oct => :octagon, + :spike => :vline, ) const _positionAliases = Dict{Symbol,Symbol}( @@ -228,65 +273,71 @@ const _bar_width = 0.8 # ----------------------------------------------------------------------------- const _series_defaults = KW( - :label => "AUTO", - :colorbar_entry => true, - :seriescolor => :auto, - :seriesalpha => nothing, - :seriestype => :path, - :linestyle => :solid, - :linewidth => :auto, - :linecolor => :auto, - :linealpha => nothing, - :fillrange => nothing, # ribbons, areas, etc - :fillcolor => :match, - :fillalpha => nothing, - :markershape => :none, - :markercolor => :match, - :markeralpha => nothing, - :markersize => 4, - :markerstrokestyle => :solid, - :markerstrokewidth => 1, - :markerstrokecolor => :match, - :markerstrokealpha => nothing, - :bins => :auto, # number of bins for hists - :smooth => false, # regression line? - :group => nothing, # groupby vector - :x => nothing, - :y => nothing, - :z => nothing, # depth for contour, surface, etc - :marker_z => nothing, # value for color scale - :line_z => nothing, - :fill_z => nothing, - :levels => 15, - :orientation => :vertical, - :bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side) - :bar_width => nothing, - :bar_edges => false, - :xerror => nothing, - :yerror => nothing, - :zerror => nothing, - :ribbon => nothing, - :quiver => nothing, - :arrow => nothing, # allows for adding arrows to line/path... call `arrow(args...)` - :normalize => false, # do we want a normalized histogram? - :weights => nothing, # optional weights for histograms (1D and 2D) - :show_empty_bins => false, # should empty bins in 2D histogram be colored as zero (otherwise they are transparent) - :contours => false, # add contours to 3d surface and wireframe plots - :contour_labels => false, - :match_dimensions => false, # do rows match x (true) or y (false) for heatmap/image/spy? see issue 196 - # this ONLY effects whether or not the z-matrix is transposed for a heatmap display! - :subplot => :auto, # which subplot(s) does this series belong to? + :label => :auto, + :colorbar_entry => true, + :seriescolor => :auto, + :seriesalpha => nothing, + :seriestype => :path, + :linestyle => :solid, + :linewidth => :auto, + :linecolor => :auto, + :linealpha => nothing, + :fillrange => nothing, # ribbons, areas, etc + :fillcolor => :match, + :fillalpha => nothing, + :markershape => :none, + :markercolor => :match, + :markeralpha => nothing, + :markersize => 4, + :markerstrokestyle => :solid, + :markerstrokewidth => 1, + :markerstrokecolor => :match, + :markerstrokealpha => nothing, + :bins => :auto, # number of bins for hists + :smooth => false, # regression line? + :group => nothing, # groupby vector + :x => nothing, + :y => nothing, + :z => nothing, # depth for contour, surface, etc + :marker_z => nothing, # value for color scale + :line_z => nothing, + :fill_z => nothing, + :levels => 15, + :orientation => :vertical, + :bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side) + :bar_width => nothing, + :bar_edges => false, + :xerror => nothing, + :yerror => nothing, + :zerror => nothing, + :ribbon => nothing, + :quiver => nothing, + :arrow => nothing, # allows for adding arrows to line/path... call `arrow(args...)` + :normalize => false, # do we want a normalized histogram? + :weights => nothing, # optional weights for histograms (1D and 2D) + :show_empty_bins => false, # should empty bins in 2D histogram be colored as zero (otherwise they are transparent) + :contours => false, # add contours to 3d surface and wireframe plots + :contour_labels => false, + :subplot => :auto, # which subplot(s) does this series belong to? :series_annotations => nothing, # a list of annotations which apply to the coordinates of this series :primary => true, # when true, this "counts" as a series for color selection, etc. the main use is to allow # one logical series to be broken up (path and markers, for example) :hover => nothing, # text to display when hovering over the data points :stride => (1,1), # array stride for wireframe/surface, the first element is the row stride and the second is the column stride. + :connections => nothing, # tuple of arrays to specifiy connectivity of a 3d mesh :extra_kwargs => Dict() ) const _plot_defaults = KW( :plot_title => "", + :plot_titlefontsize => 16, + :plot_title_location => :center, # also :left or :right + :plot_titlefontfamily => :match, + :plot_titlefonthalign => :hcenter, + :plot_titlefontvalign => :vcenter, + :plot_titlefontrotation => 0.0, + :plot_titlefontcolor => :match, :background_color => colorant"white", # default for all backgrounds, :background_color_outside => :match, # background outside grid, :foreground_color => :auto, # default for all foregrounds, and title color, @@ -312,96 +363,121 @@ const _plot_defaults = KW( const _subplot_defaults = KW( - :title => "", - :titlelocation => :center, # also :left or :right - :fontfamily_subplot => :match, - :titlefontfamily => :match, - :titlefontsize => 14, - :titlefonthalign => :hcenter, - :titlefontvalign => :vcenter, - :titlefontrotation => 0.0, - :titlefontcolor => :match, - :background_color_subplot => :match, # default for other bg colors... match takes plot default - :background_color_legend => :match, # background of legend - :background_color_inside => :match, # background inside grid - :foreground_color_subplot => :match, # default for other fg colors... match takes plot default - :foreground_color_legend => :match, # foreground of legend - :foreground_color_title => :match, # title color - :color_palette => :auto, - :legend => :best, - :legendtitle => nothing, - :colorbar => :legend, - :clims => :auto, - :legendfontfamily => :match, - :legendfontsize => 8, - :legendfonthalign => :hcenter, - :legendfontvalign => :vcenter, - :legendfontrotation => 0.0, - :legendfontcolor => :match, - :legendtitlefontfamily => :match, - :legendtitlefontsize => 11, - :legendtitlefonthalign => :hcenter, - :legendtitlefontvalign => :vcenter, - :legendtitlefontrotation => 0.0, - :legendtitlefontcolor => :match, - :annotations => [], # annotation tuples... list of (x,y,annotation) - :projection => :none, # can also be :polar or :3d - :aspect_ratio => :auto, # choose from :none or :equal - :margin => 1mm, - :left_margin => :match, - :top_margin => :match, - :right_margin => :match, - :bottom_margin => :match, - :subplot_index => -1, - :colorbar_title => "", - :framestyle => :axes, - :camera => (30,30), - :extra_kwargs => Dict() + :title => "", + :titlelocation => :center, # also :left or :right + :fontfamily_subplot => :match, + :titlefontfamily => :match, + :titlefontsize => 14, + :titlefonthalign => :hcenter, + :titlefontvalign => :vcenter, + :titlefontrotation => 0.0, + :titlefontcolor => :match, + :background_color_subplot => :match, # default for other bg colors... match takes plot default + :background_color_legend => :match, # background of legend + :background_color_inside => :match, # background inside grid + :foreground_color_subplot => :match, # default for other fg colors... match takes plot default + :foreground_color_legend => :match, # foreground of legend + :foreground_color_title => :match, # title color + :color_palette => :auto, + :legend => :best, + :legendtitle => nothing, + :colorbar => :legend, + :clims => :auto, + :colorbar_fontfamily => :match, + :colorbar_ticks => :auto, + :colorbar_tickfontfamily => :match, + :colorbar_tickfontsize => 8, + :colorbar_tickfonthalign => :hcenter, + :colorbar_tickfontvalign => :vcenter, + :colorbar_tickfontrotation => 0.0, + :colorbar_tickfontcolor => :match, + :colorbar_scale => :identity, + :colorbar_formatter => :auto, + :colorbar_discrete_values => [], + :colorbar_continuous_values => zeros(0), + :legendfontfamily => :match, + :legendfontsize => 8, + :legendfonthalign => :hcenter, + :legendfontvalign => :vcenter, + :legendfontrotation => 0.0, + :legendfontcolor => :match, + :legendtitlefontfamily => :match, + :legendtitlefontsize => 11, + :legendtitlefonthalign => :hcenter, + :legendtitlefontvalign => :vcenter, + :legendtitlefontrotation => 0.0, + :legendtitlefontcolor => :match, + :annotations => [], # annotation tuples... list of (x,y,annotation) + :annotationfontfamily => :match, + :annotationfontsize => 14, + :annotationhalign => :hcenter, + :annotationvalign => :vcenter, + :annotationrotation => 0.0, + :annotationcolor => :match, + :projection => :none, # can also be :polar or :3d + :aspect_ratio => :auto, # choose from :none or :equal + :margin => 1mm, + :left_margin => :match, + :top_margin => :match, + :right_margin => :match, + :bottom_margin => :match, + :subplot_index => -1, + :colorbar_title => "", + :colorbar_titlefontsize => 10, + :colorbar_title_location => :center, # also :left or :right + :colorbar_titlefontfamily => :match, + :colorbar_titlefonthalign => :hcenter, + :colorbar_titlefontvalign => :vcenter, + :colorbar_titlefontrotation => 0.0, + :colorbar_titlefontcolor => :match, + :framestyle => :axes, + :camera => (30,30), + :extra_kwargs => Dict() ) const _axis_defaults = KW( - :guide => "", - :guide_position => :auto, - :lims => :auto, - :ticks => :auto, - :scale => :identity, - :rotation => 0, - :flip => false, - :link => [], - :tickfontfamily => :match, - :tickfontsize => 8, - :tickfonthalign => :hcenter, - :tickfontvalign => :vcenter, - :tickfontrotation => 0.0, - :tickfontcolor => :match, - :guidefontfamily => :match, - :guidefontsize => 11, - :guidefonthalign => :hcenter, - :guidefontvalign => :vcenter, - :guidefontrotation => 0.0, - :guidefontcolor => :match, - :foreground_color_axis => :match, # axis border/tick colors, - :foreground_color_border => :match, # plot area border/spines, - :foreground_color_text => :match, # tick text color, - :foreground_color_guide => :match, # guide text color, - :discrete_values => [], - :formatter => :auto, - :mirror => false, - :grid => true, - :foreground_color_grid => :match, # grid color - :gridalpha => 0.1, - :gridstyle => :solid, - :gridlinewidth => 0.5, + :guide => "", + :guide_position => :auto, + :lims => :auto, + :ticks => :auto, + :scale => :identity, + :rotation => 0, + :flip => false, + :link => [], + :tickfontfamily => :match, + :tickfontsize => 8, + :tickfonthalign => :hcenter, + :tickfontvalign => :vcenter, + :tickfontrotation => 0.0, + :tickfontcolor => :match, + :guidefontfamily => :match, + :guidefontsize => 11, + :guidefonthalign => :hcenter, + :guidefontvalign => :vcenter, + :guidefontrotation => 0.0, + :guidefontcolor => :match, + :foreground_color_axis => :match, # axis border/tick colors, + :foreground_color_border => :match, # plot area border/spines, + :foreground_color_text => :match, # tick text color, + :foreground_color_guide => :match, # guide text color, + :discrete_values => [], + :formatter => :auto, + :mirror => false, + :grid => true, + :foreground_color_grid => :match, # grid color + :gridalpha => 0.1, + :gridstyle => :solid, + :gridlinewidth => 0.5, :foreground_color_minor_grid => :match, # grid color - :minorgridalpha => 0.05, - :minorgridstyle => :solid, - :minorgridlinewidth => 0.5, - :tick_direction => :in, - :minorticks => false, - :minorgrid => false, - :showaxis => true, - :widen => true, - :draw_arrow => false, + :minorgridalpha => 0.05, + :minorgridstyle => :solid, + :minorgridlinewidth => 0.5, + :tick_direction => :in, + :minorticks => false, + :minorgrid => false, + :showaxis => true, + :widen => :auto, + :draw_arrow => false, ) const _suppress_warnings = Set{Symbol}([ @@ -449,6 +525,7 @@ const _initial_axis_defaults = deepcopy(_axis_defaults) const _initial_fontsizes = Dict(:titlefontsize => _subplot_defaults[:titlefontsize], :legendfontsize => _subplot_defaults[:legendfontsize], :legendtitlefontsize => _subplot_defaults[:legendtitlefontsize], + :annotationfontsize => _subplot_defaults[:annotationfontsize], :tickfontsize => _axis_defaults[:tickfontsize], :guidefontsize => _axis_defaults[:guidefontsize]) @@ -461,7 +538,7 @@ const _subplot_args = sort(union(collect(keys(_subplot_defaults)))) const _plot_args = sort(union(collect(keys(_plot_defaults)))) const _magic_axis_args = [:axis, :tickfont, :guidefont, :grid, :minorgrid] -const _magic_subplot_args = [:titlefont, :legendfont, :legendtitlefont, ] +const _magic_subplot_args = [:titlefont, :legendfont, :legendtitlefont, :plot_titlefont, :colorbar_titlefont] const _magic_series_args = [:line, :marker, :fill] const _all_axis_args = sort(union([_axis_args; _magic_axis_args])) @@ -470,7 +547,7 @@ const _all_series_args = sort(union([_series_args; _magic_series_args])) const _all_plot_args = _plot_args const _all_args = - sort([_all_axis_args; _all_subplot_args; _all_series_args; _all_plot_args]) + sort(union([_all_axis_args; _all_subplot_args; _all_series_args; _all_plot_args])) is_subplot_attr(k) = k in _all_subplot_args is_series_attr(k) = k in _all_series_args @@ -481,9 +558,6 @@ RecipesBase.is_key_supported(k::Symbol) = is_attr_supported(k) is_default_attribute(k) = k in _internal_args || k in _all_args || is_axis_attr_noletter(k) # ----------------------------------------------------------------------------- - -makeplural(s::Symbol) = Symbol(string(s,"s")) - autopick_ignore_none_auto(arr::AVec, idx::Integer) = _cycle(setdiff(arr, [:none, :auto]), idx) autopick_ignore_none_auto(notarr, idx::Integer) = notarr @@ -502,6 +576,13 @@ end # ----------------------------------------------------------------------------- +# margin +add_aliases(:left_margin , :leftmargin ) +add_aliases(:top_margin , :topmargin) +add_aliases(:bottom_margin , :bottommargin) +add_aliases(:right_margin ,:rightmargin) + + # colors add_aliases(:seriescolor, :c, :color, :colour) add_aliases(:linecolor, :lc, :lcolor, :lcolour, :linecolour) @@ -583,6 +664,7 @@ add_aliases(:fill_z, :fillz, :fz, :surfacecolor, :surfacecolour, :sc, :surfcolor add_aliases(:legend, :leg, :key) add_aliases(:legendtitle, :legend_title, :labeltitle, :label_title, :leg_title, :key_title) add_aliases(:colorbar, :cb, :cbar, :colorkey) +add_aliases(:colorbar_title, :colorbartitle, :cb_title, :cbtitle, :cbartitle, :cbar_title, :colorkeytitle, :colorkey_title) add_aliases(:clims, :clim, :cbarlims, :cbar_lims, :climits, :color_limits) add_aliases(:smooth, :regression, :reg) add_aliases(:levels, :nlevels, :nlev, :levs) @@ -598,7 +680,6 @@ add_aliases(:quiver, :velocity, :quiver2d, :gradient, :vectorfield) add_aliases(:normalize, :norm, :normed, :normalized) add_aliases(:show_empty_bins, :showemptybins, :showempty, :show_empty) add_aliases(:aspect_ratio, :aspectratio, :axis_ratio, :axisratio, :ratio) -add_aliases(:match_dimensions, :transpose, :transpose_z) add_aliases(:subplot, :sp, :subplt, :splt) add_aliases(:projection, :proj) add_aliases(:titlelocation, :title_location, :title_loc, :titleloc, :title_position, :title_pos, :titlepos, :titleposition, :title_align, :title_alignment) @@ -618,11 +699,11 @@ add_aliases(:contour_labels, :contourlabels, :clabels, :clabs) add_aliases(:warn_on_unsupported, :warn) # add all pluralized forms to the _keyAliases dict -for arg in keys(_series_defaults) - _keyAliases[makeplural(arg)] = arg +for arg in _all_args + add_aliases(arg, makeplural(arg)) end - - +# add all non_underscored forms to the _keyAliases +add_non_underscore_aliases!(_keyAliases) # ----------------------------------------------------------------------------- @@ -944,8 +1025,9 @@ function RecipesPipeline.preprocess_attributes!(plotattributes::AKW) replaceAliases!(plotattributes, _keyAliases) # handle axis args common to all axis - args = RecipesPipeline.pop_kw!(plotattributes, :axis, ()) - for arg in wraptuple(args) + args = wraptuple(RecipesPipeline.pop_kw!(plotattributes, :axis, ())) + showarg = wraptuple(RecipesPipeline.pop_kw!(plotattributes, :showaxis, ())) + for arg in wraptuple((args..., showarg...)) for letter in (:x, :y, :z) process_axis_arg!(plotattributes, arg, letter) end @@ -961,9 +1043,9 @@ function RecipesPipeline.preprocess_attributes!(plotattributes::AKW) end end - # vline accesses the y argument but actually maps it to the x axis. + # vline and others accesses the y argument but actually maps it to the x axis. # Hence, we have to swap formatters - if get(plotattributes, :seriestype, :path) == :vline + if treats_y_as_x(get(plotattributes, :seriestype, :path)) xformatter = get(plotattributes, :xformatter, :auto) yformatter = get(plotattributes, :yformatter, :auto) plotattributes[:xformatter] = yformatter @@ -1032,7 +1114,7 @@ function RecipesPipeline.preprocess_attributes!(plotattributes::AKW) end # fonts - for fontname in (:titlefont, :legendfont, :legendtitlefont) + for fontname in (:titlefont, :legendfont, :legendtitlefont, :plot_titlefont, :colorbar_titlefont) args = RecipesPipeline.pop_kw!(plotattributes, fontname, ()) for arg in wraptuple(args) processFontArg!(plotattributes, fontname, arg) @@ -1057,7 +1139,7 @@ function RecipesPipeline.preprocess_attributes!(plotattributes::AKW) RecipesPipeline.reset_kw!(plotattributes, :marker) if haskey(plotattributes, :markershape) plotattributes[:markershape] = _replace_markershape(plotattributes[:markershape]) - if plotattributes[:markershape] == :none && plotattributes[:seriestype] in (:scatter, :scatterbins, :scatterhist, :scatter3d) #the default should be :auto, not :none, so that :none can be set explicitly and would be respected + if plotattributes[:markershape] == :none && get(plotattributes, :seriestype, :path) in (:scatter, :scatterbins, :scatterhist, :scatter3d) #the default should be :auto, not :none, so that :none can be set explicitly and would be respected plotattributes[:markershape] = :circle end elseif anymarker @@ -1113,7 +1195,6 @@ function RecipesPipeline.preprocess_attributes!(plotattributes::AKW) if st in (:boxplot, :violin, :density) && !isdefined(Main, :StatsPlots) @warn("seriestype $st has been moved to StatsPlots. To use: \`Pkg.add(\"StatsPlots\"); using StatsPlots\`") end - return end @@ -1199,6 +1280,8 @@ end convertLegendValue(val::Bool) = val ? :best : :none convertLegendValue(val::Nothing) = :none convertLegendValue(v::Tuple{S,T}) where {S<:Real, T<:Real} = v +convertLegendValue(v::Tuple{<:Real,Symbol}) = v +convertLegendValue(v::Real) = v convertLegendValue(v::AbstractArray) = map(convertLegendValue, v) # ----------------------------------------------------------------------------- @@ -1264,18 +1347,27 @@ const _match_map = KW( :background_color_inside => :background_color_subplot, :foreground_color_legend => :foreground_color_subplot, :foreground_color_title => :foreground_color_subplot, - :left_margin => :margin, - :top_margin => :margin, - :right_margin => :margin, - :bottom_margin => :margin, + :left_margin => :margin, + :top_margin => :margin, + :right_margin => :margin, + :bottom_margin => :margin, :titlefontfamily => :fontfamily_subplot, - :legendfontfamily => :fontfamily_subplot, - :legendtitlefontfamily => :fontfamily_subplot, :titlefontcolor => :foreground_color_subplot, + :legendfontfamily => :fontfamily_subplot, :legendfontcolor => :foreground_color_subplot, + :legendtitlefontfamily => :fontfamily_subplot, :legendtitlefontcolor => :foreground_color_subplot, + :colorbar_fontfamily => :fontfamily_subplot, + :colorbar_titlefontfamily => :fontfamily_subplot, + :colorbar_titlefontcolor => :foreground_color_subplot, + :colorbar_tickfontfamily => :fontfamily_subplot, + :colorbar_tickfontcolor => :foreground_color_subplot, + :plot_titlefontfamily => :fontfamily, + :plot_titlefontcolor => :foreground_color, :tickfontcolor => :foreground_color_text, :guidefontcolor => :foreground_color_guide, + :annotationfontfamily => :fontfamily_subplot, + :annotationcolor => :foreground_color_subplot, ) # these can match values from the parent container (axis --> subplot --> plot) @@ -1499,9 +1591,18 @@ function _update_subplot_args(plt::Plot, sp::Subplot, plotattributes_in, subplot _update_subplot_periphery(sp, anns) _update_subplot_colors(sp) + lims_warned = false for letter in (:x, :y, :z) _update_axis(plt, sp, plotattributes_in, letter, subplot_index) + lk = Symbol(letter, :lims) + + # warn against using `Range` in x,y,z lims + if !lims_warned && haskey(plotattributes_in, lk) && plotattributes_in[lk] isa AbstractRange + @warn("lims should be a Tuple, not $(typeof(plotattributes_in[lk])).") + lims_warned = true + end end + _update_subplot_colorbars(sp) end # ----------------------------------------------------------------------------- @@ -1553,6 +1654,20 @@ function _slice_series_args!(plotattributes::AKW, plt::Plot, sp::Subplot, comman return plotattributes end +label_to_string(label::Bool, series_plotindex) = label ? label_to_string(:auto, series_plotindex) : "" +label_to_string(label::Nothing, series_plotindex) = "" +label_to_string(label::Missing, series_plotindex) = "" +function label_to_string(label::Symbol, series_plotindex) + if label==:auto + return string("y", series_plotindex) + elseif label==:none + return "" + else + throw(ArgumentError("unsupported symbol $(label) passed to `label`")) + end +end +label_to_string(label, series_plotindex) = string(label) # Fallback to string promotion + function _update_series_attributes!(plotattributes::AKW, plt::Plot, sp::Subplot) pkg = plt.backend globalIndex = plotattributes[:series_plotindex] @@ -1621,10 +1736,7 @@ function _update_series_attributes!(plotattributes::AKW, plt::Plot, sp::Subplot) end # set label - label = plotattributes[:label] - label = (label == "AUTO" ? "y$globalIndex" : label) - label = label in (:none, nothing, false) ? "" : label - plotattributes[:label] = label + plotattributes[:label] = label_to_string.(plotattributes[:label], globalIndex) _replace_linewidth(plotattributes) plotattributes @@ -1645,3 +1757,80 @@ function _series_index(plotattributes, sp) end return idx end + +#-------------------------------------------------- +## inspired by Base.@kwdef +macro add_attributes( level, expr ) + expr = macroexpand(__module__, expr) # to expand @static + expr isa Expr && expr.head === :struct || error("Invalid usage of @add_attributes") + T = expr.args[2] + if T isa Expr && T.head === :<: + T = T.args[1] + end + + key_args = Any[] + value_args = Any[] + + _splitdef!(expr.args[3], value_args, key_args) + + insert_block = Expr(:block) + for (key, value) in zip(key_args, value_args) + # e.g. _series_defualts[key] = value + exp_key = Symbol(lowercase(string(T)), "_", key) + pl_key = makeplural(exp_key) + push!(insert_block.args, Expr( + :(=), Expr(:ref, Symbol("_", level, "_defaults"), QuoteNode(exp_key)), value + )) + push!(insert_block.args, :( + add_aliases($(QuoteNode(exp_key)), $(QuoteNode(pl_key))) + )) + push!(insert_block.args, :( + add_aliases($(QuoteNode(exp_key)), $(QuoteNode(make_non_underscore(exp_key)))) + )) + push!(insert_block.args, :( + add_aliases($(QuoteNode(exp_key)), $(QuoteNode(make_non_underscore(pl_key)))) + )) + end + return quote + $expr + $insert_block + end |> esc +end + +function _splitdef!(blk, value_args, key_args) + for i in eachindex(blk.args) + ei = blk.args[i] + if ei isa Symbol + # var + continue + elseif ei isa Expr + if ei.head === :(=) + lhs = ei.args[1] + if lhs isa Symbol + # var = defexpr + var = lhs + elseif lhs isa Expr && lhs.head === :(::) && lhs.args[1] isa Symbol + # var::T = defexpr + var = lhs.args[1] + else + # something else, e.g. inline inner constructor + # F(...) = ... + continue + end + defexpr = ei.args[2] # defexpr + push!(value_args, defexpr) + push!(key_args, var) + blk.args[i] = lhs + elseif ei.head === :(::) && ei.args[1] isa Symbol + # var::Typ + var = ei.args[1] + push!(value_args, var) + push!(key_args, var) + elseif ei.head === :block + # can arise with use of @static inside type decl + _kwdef!(ei, value_args, key_args) + end + end + end + blk +end diff --git a/src/axes.jl b/src/axes.jl index 0979186a..91be5ba0 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -35,7 +35,6 @@ end function process_axis_arg!(plotattributes::AKW, arg, letter = "") T = typeof(arg) arg = get(_scaleAliases, arg, arg) - if typeof(arg) <: Font plotattributes[Symbol(letter,:tickfont)] = arg plotattributes[Symbol(letter,:guidefont)] = arg @@ -95,6 +94,12 @@ function attr!(axis::Axis, args...; kw...) for vi in v discrete_value!(axis, vi) end + #could perhaps use TimeType here, as Date and DateTime are both subtypes of TimeType + # or could perhaps check if dateformatter or datetimeformatter is in use + elseif k == :lims && isa(v, Tuple{Date,Date}) + plotattributes[k] = (v[1].instant.periods.value, v[2].instant.periods.value) + elseif k == :lims && isa(v, Tuple{DateTime,DateTime}) + plotattributes[k] = (v[1].instant.periods.value, v[2].instant.periods.value) else plotattributes[k] = v end @@ -124,11 +129,18 @@ const _label_func = Dict{Symbol,Function}( ) labelfunc(scale::Symbol, backend::AbstractBackend) = get(_label_func, scale, string) -function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) - amin, amax = axis_limits(sp, axis[:letter]) +const _label_func_tex = Dict{Symbol,Function}( + :log10 => x -> "10^{$x}", + :log2 => x -> "2^{$x}", + :ln => x -> "e^{$x}", +) +labelfunc_tex(scale::Symbol) = get(_label_func_tex, scale, convert_sci_unicode) + + +function optimal_ticks_and_labels(ticks, alims, scale, formatter) + amin, amax = alims # scale the limits - scale = axis[:scale] sf = RecipesPipeline.scale_func(scale) # If the axis input was a Date or DateTime use a special logic to find @@ -139,7 +151,7 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) # rather than on the input format # TODO: maybe: non-trivial scale (:ln, :log2, :log10) for date/datetime if ticks === nothing && scale == :identity - if axis[:formatter] == RecipesPipeline.dateformatter + if formatter == RecipesPipeline.dateformatter # optimize_datetime_ticks returns ticks and labels(!) based on # integers/floats corresponding to the DateTime type. Thus, the axes # limits, which resulted from converting the Date type to integers, @@ -150,7 +162,7 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) k_min = 2, k_max = 4) # Now the ticks are converted back to floats corresponding to Dates. return ticks / 864e5, labels - elseif axis[:formatter] == RecipesPipeline.datetimeformatter + elseif formatter == RecipesPipeline.datetimeformatter return optimize_datetime_ticks(amin, amax; k_min = 2, k_max = 4) end end @@ -174,24 +186,17 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) # chosen ticks is not too much bigger than amin - amax: strict_span = false, ) - axis[:lims] = map(RecipesPipeline.inverse_scale_func(scale), (viewmin, viewmax)) + # axis[:lims] = map(RecipesPipeline.inverse_scale_func(scale), (viewmin, viewmax)) else scaled_ticks = map(sf, (filter(t -> amin <= t <= amax, ticks))) end unscaled_ticks = map(RecipesPipeline.inverse_scale_func(scale), scaled_ticks) labels = if any(isfinite, unscaled_ticks) - formatter = axis[:formatter] - if formatter == :auto - # the default behavior is to make strings of the scaled values and then apply the labelfunc - map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, :auto)) - elseif formatter == :plain - # Leave the numbers in plain format - map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, :plain)) - elseif formatter == :scientific - Showoff.showoff(unscaled_ticks, :scientific) - elseif formatter == :latex - map(x -> string("\$", replace(convert_sci_unicode(x), '×' => "\\times"), "\$"), Showoff.showoff(unscaled_ticks, :auto)) + if formatter in (:auto, :plain, :scientific, :engineering) + map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, formatter)) + elseif formatter == :latex + map(x -> string("\$", replace(convert_sci_unicode(x), '×' => "\\times"), "\$"), Showoff.showoff(unscaled_ticks, :auto)) else # there was an override for the formatter... use that on the unscaled ticks map(formatter, unscaled_ticks) @@ -212,50 +217,115 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) end # return (continuous_values, discrete_values) for the ticks on this axis -function get_ticks(sp::Subplot, axis::Axis) - ticks = _transform_ticks(axis[:ticks]) - ticks in (:none, nothing, false) && return nothing - - # treat :native ticks as :auto - ticks = ticks == :native ? :auto : ticks - - dvals = axis[:discrete_values] - cv, dv = if typeof(ticks) <: Symbol - if !isempty(dvals) - # discrete ticks... - n = length(dvals) - rng = if ticks == :auto - Int[round(Int,i) for i in range(1, stop=n, length=min(n,15))] - else # if ticks == :all - 1:n - end - axis[:continuous_values][rng], dvals[rng] - elseif ispolar(axis.sps[1]) && axis[:letter] == :x - #force theta axis to be full circle - (collect(0:pi/4:7pi/4), string.(0:45:315)) +function get_ticks(sp::Subplot, axis::Axis; update = true) + if update || !haskey(axis.plotattributes, :optimized_ticks) + dvals = axis[:discrete_values] + ticks = _transform_ticks(axis[:ticks]) + axis.plotattributes[:optimized_ticks] = if ticks isa Symbol && ticks !== :none && + ispolar(sp) && axis[:letter] === :x && !isempty(dvals) + collect(0:pi/4:7pi/4), string.(0:45:315) else - # compute optimal ticks and labels - optimal_ticks_and_labels(sp, axis) + cvals = axis[:continuous_values] + alims = axis_limits(sp, axis[:letter]) + scale = axis[:scale] + formatter = axis[:formatter] + get_ticks(ticks, cvals, dvals, alims, scale, formatter) end - elseif typeof(ticks) <: Union{AVec, Int} - if !isempty(dvals) && typeof(ticks) <: Int - rng = Int[round(Int,i) for i in range(1, stop=length(dvals), length=ticks)] - axis[:continuous_values][rng], dvals[rng] - else - # override ticks, but get the labels - optimal_ticks_and_labels(sp, axis, ticks) - end - elseif typeof(ticks) <: NTuple{2, Any} - # assuming we're passed (ticks, labels) - ticks - else - error("Unknown ticks type in get_ticks: $(typeof(ticks))") end - # @show ticks dvals cv dv - - return cv, dv + return axis.plotattributes[:optimized_ticks] end +# Ticks getter functions +for l in (:x, :y, :z) + axis = string(l, "-axis") # "x-axis" + ticks = string(l, "ticks") # "xticks" + f = Symbol(ticks) # :xticks + @eval begin + """ + $($f)(p::Plot) + + returns a vector of the $($axis) ticks of the subplots of `p`. + + Example use: + + ```jldoctest + julia> p = plot(1:5, $($ticks)=[1,2]) + + julia> $($f)(p) + 1-element Vector{Tuple{Vector{Float64}, Vector{String}}}: + ([1.0, 2.0], ["1", "2"]) + ``` + + If `p` consists of a single subplot, you might want to grab + only the first element, via + + ```jldoctest + julia> $($f)(p)[1] + ([1.0, 2.0], ["1", "2"]) + ``` + + or you can call $($f) on the first (only) subplot of `p` via + + ```jldoctest + julia> $($f)(p[1]) + ([1.0, 2.0], ["1", "2"]) + ``` + """ + $f(p::Plot) = get_ticks(p, $(Meta.quot(l))) + """ + $($f)(sp::Subplot) + + returns the $($axis) ticks of the subplot `sp`. + + Note that the ticks are returned as tuples of values and labels: + + ```jldoctest + julia> sp = plot(1:5, $($ticks)=[1,2]).subplots[1] + Subplot{1} + + julia> $($f)(sp) + ([1.0, 2.0], ["1", "2"]) + ``` + """ + $f(sp::Subplot) = get_ticks(sp, $(Meta.quot(l))) + export $f + end +end +# get_ticks from axis symbol :x, :y, or :z +get_ticks(sp::Subplot, s::Symbol) = get_ticks(sp, sp[Symbol(s, :axis)]) +get_ticks(p::Plot, s::Symbol) = [get_ticks(sp, s) for sp in p.subplots] + +function get_ticks(ticks::Symbol, cvals::T, dvals, args...) where T + if ticks === :none + return T[], String[] + elseif !isempty(dvals) + n = length(dvals) + if ticks === :all || n < 16 + return cvals, string.(dvals) + else + Δ = ceil(Int, n / 10) + rng = Δ:Δ:n + return cvals[rng], string.(dvals[rng]) + end + else + return optimal_ticks_and_labels(nothing, args...) + end +end +get_ticks(ticks::AVec, cvals, dvals, args...) = optimal_ticks_and_labels(ticks, args...) +function get_ticks(ticks::Int, dvals, cvals, args...) + if !isempty(dvals) + rng = round.(Int, range(1, stop=length(dvals), length=ticks)) + cvals[rng], string.(dvals[rng]) + else + optimal_ticks_and_labels(ticks, args...) + end +end +get_ticks(ticks::NTuple{2, Any}, args...) = ticks +get_ticks(::Nothing, cvals::T, args...) where T = T[], String[] +get_ticks(ticks::Bool, args...) = + ticks ? get_ticks(:auto, args...) : get_ticks(nothing, args...) +get_ticks(::T, args...) where T = error("Unknown ticks type in get_ticks: $T") + _transform_ticks(ticks) = ticks _transform_ticks(ticks::AbstractArray{T}) where T <: Dates.TimeType = Dates.value.(ticks) _transform_ticks(ticks::NTuple{2, Any}) = (_transform_ticks(ticks[1]), ticks[2]) @@ -431,20 +501,22 @@ function widen(lmin, lmax, scale = :identity) end # figure out if widening is a good idea. -const _widen_seriestypes = (:line, :path, :steppre, :steppost, :sticks, :scatter, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :bar, :shape, :path3d, :scatter3d) +const _widen_seriestypes = (:line, :path, :steppre, :stepmid, :steppost, :sticks, :scatter, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :bar, :shape, :path3d, :scatter3d) function default_should_widen(axis::Axis) - should_widen = false - if !(is_2tuple(axis[:lims]) || axis[:lims] == :round) - for sp in axis.sps - for series in series_list(sp) - if series.plotattributes[:seriestype] in _widen_seriestypes - should_widen = true - end + if axis[:widen] isa Bool + return axis[:widen] + end + # automatic behavior: widen if limits aren't specified and series type is appropriate + (is_2tuple(axis[:lims]) || axis[:lims] == :round) && return false + for sp in axis.sps + for series in series_list(sp) + if series.plotattributes[:seriestype] in _widen_seriestypes + return true end end end - should_widen + false end function round_limits(amin,amax) @@ -463,13 +535,20 @@ function axis_limits(sp, letter, should_widen = default_should_widen(sp[Symbol(l has_user_lims = (isa(lims, Tuple) || isa(lims, AVec)) && length(lims) == 2 if has_user_lims lmin, lmax = lims - if lmin != :auto && isfinite(lmin) + if lmin == :auto + elseif isfinite(lmin) amin = lmin end - if lmax != :auto && isfinite(lmax) + if lmax == :auto + elseif isfinite(lmax) amax = lmax end end + if lims == :symmetric + aval = max(abs(amin), abs(amax)) + amin = -aval + amax = aval + end if amax <= amin && isfinite(amin) amax = amin + 1.0 end @@ -485,7 +564,7 @@ function axis_limits(sp, letter, should_widen = default_should_widen(sp[Symbol(l else amin, amax end - elseif should_widen && axis[:widen] + elseif should_widen widen(amin, amax, axis[:scale]) elseif lims == :round round_limits(amin,amax) @@ -577,391 +656,240 @@ end # ------------------------------------------------------------------------- # compute the line segments which should be drawn for this axis -function axis_drawing_info(sp::Subplot) - xaxis, yaxis = sp[:xaxis], sp[:yaxis] - xmin, xmax = axis_limits(sp, :x) - ymin, ymax = axis_limits(sp, :y) - xticks = get_ticks(sp, xaxis) - yticks = get_ticks(sp, yaxis) - xminorticks = get_minor_ticks(sp, xaxis, xticks) - yminorticks = get_minor_ticks(sp, yaxis, yticks) - xaxis_segs = Segments(2) - yaxis_segs = Segments(2) - xtick_segs = Segments(2) - ytick_segs = Segments(2) - xgrid_segs = Segments(2) - ygrid_segs = Segments(2) - xminorgrid_segs = Segments(2) - yminorgrid_segs = Segments(2) - xborder_segs = Segments(2) - yborder_segs = Segments(2) +function axis_drawing_info(sp, letter) + # find out which axis we are dealing with + asym = Symbol(letter, :axis) + isy = letter === :y + oletter = isy ? :x : :y + oasym = Symbol(oletter, :axis) + + # get axis objects, ticks and minor ticks + ax, oax = sp[asym], sp[oasym] + amin, amax = axis_limits(sp, letter) + oamin, oamax = axis_limits(sp, oletter) + ticks = get_ticks(sp, ax, update = false) + minor_ticks = get_minor_ticks(sp, ax, ticks) + + # initialize the segments + segments = Segments(2) + tick_segments = Segments(2) + grid_segments = Segments(2) + minorgrid_segments = Segments(2) + border_segments = Segments(2) if sp[:framestyle] != :none - # xaxis - y1, y2 = if sp[:framestyle] in (:origin, :zerolines) + oa1, oa2 = if sp[:framestyle] in (:origin, :zerolines) 0.0, 0.0 else - xor(xaxis[:mirror], yaxis[:flip]) ? (ymax, ymin) : (ymin, ymax) + xor(ax[:mirror], oax[:flip]) ? (oamax, oamin) : (oamin, oamax) end - if xaxis[:showaxis] + if ax[:showaxis] if sp[:framestyle] != :grid - push!(xaxis_segs, (xmin, y1), (xmax, y1)) + push!(segments, reverse_if((amin, oa1), isy), reverse_if((amax, oa1), isy)) # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(xticks in (:none, nothing, false)) && length(xticks) > 1 - showticks = xticks[1] .!= 0 - xticks = (xticks[1][showticks], xticks[2][showticks]) - end - end - sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2), (xmax, y2)) # top spine - end - if !(xaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(yaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(yaxis[:scale]) - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(ymax) - f(ymin))) - (-t, t) - else - ticks_in = xaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(y1) + 0.012 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - - for xtick in xticks[1] - if xaxis[:showaxis] - push!(xtick_segs, (xtick, tick_start), (xtick, tick_stop)) # bottom tick - end - xaxis[:grid] && push!(xgrid_segs, (xtick, ymin), (xtick, ymax)) # vertical grid - end - - if !(xaxis[:minorticks] in (:none, nothing, false)) || xaxis[:minorgrid] - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(ymax) - f(ymin))) - (-t, t) - else - t = invf(f(y1) + 0.006 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - for xtick in xminorticks - if xaxis[:showaxis] - push!(xtick_segs, (xtick, tick_start), (xtick, tick_stop)) # bottom tick + if sp[:framestyle] == :origin && !(ticks in (:none, nothing, false)) && length(ticks) > 1 + i = findfirst(==(0), ticks[1]) + if i !== nothing + deleteat!(ticks[1], i) + deleteat!(ticks[2], i) end - xaxis[:minorgrid] && push!(xminorgrid_segs, (xtick, ymin), (xtick, ymax)) # vertical grid end end + if sp[:framestyle] in (:semi, :box) # top spine + push!( + border_segments, + reverse_if((amin, oa2), isy), + reverse_if((amax, oa2), isy), + ) + end end + if ax[:ticks] ∉ (:none, nothing, false) + f = RecipesPipeline.scale_func(oax[:scale]) + invf = RecipesPipeline.inverse_scale_func(oax[:scale]) - - # yaxis - x1, x2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(yaxis[:mirror], xaxis[:flip]) ? (xmax, xmin) : (xmin, xmax) - end - if yaxis[:showaxis] - if sp[:framestyle] != :grid - push!(yaxis_segs, (x1, ymin), (x1, ymax)) - # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(yticks in (:none, nothing,false)) && length(yticks) > 1 - showticks = yticks[1] .!= 0 - yticks = (yticks[1][showticks], yticks[2][showticks]) - end - end - sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin), (x2, ymax)) # right spine - end - if !(yaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(xaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(xaxis[:scale]) - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(xmax) - f(xmin))) - (-t, t) - else - ticks_in = yaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(x1) + 0.012 * (f(x2) - f(x1)) * ticks_in) - (x1, t) - end - - for ytick in yticks[1] - if yaxis[:showaxis] - push!(ytick_segs, (tick_start, ytick), (tick_stop, ytick)) # left tick - end - yaxis[:grid] && push!(ygrid_segs, (xmin, ytick), (xmax, ytick)) # horizontal grid - end - - if !(yaxis[:minorticks] in (:none, nothing, false)) || yaxis[:minorgrid] - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(xmax) - f(xmin))) - (-t, t) - else - t = invf(f(x1) + 0.006 * (f(x2) - f(x1)) * ticks_in) - (x1, t) - end - for ytick in yminorticks - if yaxis[:showaxis] - push!(ytick_segs, (tick_start, ytick), (tick_stop, ytick)) # left tick + add_major_or_minor_segments(ticks, grid, segments, factor, cond) = begin + if cond + tick_start, tick_stop = if sp[:framestyle] == :origin + t = invf(f(0) + factor * (f(oamax) - f(oamin))) + (-t, t) + else + ticks_in = ax[:tick_direction] == :out ? -1 : 1 + t = invf(f(oa1) + factor * (f(oa2) - f(oa1)) * ticks_in) + (oa1, t) end - yaxis[:minorgrid] && push!(yminorgrid_segs, (xmin, ytick), (xmax, ytick)) # horizontal grid end + + for tick in ticks + if ax[:showaxis] && cond + push!( + tick_segments, + reverse_if((tick, tick_start), isy), + reverse_if((tick, tick_stop), isy), + ) + end + if grid + push!( + segments, + reverse_if((tick, oamin), isy), + reverse_if((tick, oamax), isy), + ) + end + end + end + + # add major grid segments + add_major_or_minor_segments(ticks[1], ax[:grid], grid_segments, 0.012, ax[:tick_direction] !== :none) + + # add minor grid segments + if ax[:minorticks] ∉ (:none, nothing, false) || ax[:minorgrid] + add_major_or_minor_segments(minor_ticks, ax[:minorgrid], minorgrid_segments, 0.006, true) end end end - xticks, yticks, xaxis_segs, yaxis_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xminorgrid_segs, yminorgrid_segs, xborder_segs, yborder_segs + return ( + ticks = ticks, + segments = segments, + tick_segments = tick_segments, + grid_segments = grid_segments, + minorgrid_segments = minorgrid_segments, + border_segments = border_segments + ) end +function sort_3d_axes(a, b, c, letter) + if letter === :x + a, b, c + elseif letter === :y + b, a, c + else + c, b, a + end +end -function axis_drawing_info_3d(sp::Subplot) - xaxis, yaxis, zaxis = sp[:xaxis], sp[:yaxis], sp[:zaxis] - xmin, xmax = axis_limits(sp, :x) - ymin, ymax = axis_limits(sp, :y) - zmin, zmax = axis_limits(sp, :z) - xticks = get_ticks(sp, xaxis) - yticks = get_ticks(sp, yaxis) - zticks = get_ticks(sp, zaxis) - xminorticks = get_minor_ticks(sp, xaxis, xticks) - yminorticks = get_minor_ticks(sp, yaxis, yticks) - zminorticks = get_minor_ticks(sp, zaxis, zticks) - xaxis_segs = Segments(3) - yaxis_segs = Segments(3) - zaxis_segs = Segments(3) - xtick_segs = Segments(3) - ytick_segs = Segments(3) - ztick_segs = Segments(3) - xgrid_segs = Segments(3) - ygrid_segs = Segments(3) - zgrid_segs = Segments(3) - xminorgrid_segs = Segments(3) - yminorgrid_segs = Segments(3) - zminorgrid_segs = Segments(3) - xborder_segs = Segments(3) - yborder_segs = Segments(3) - zborder_segs = Segments(3) +function axis_drawing_info_3d(sp, letter) + near_letter = letter in (:x, :z) ? :y : :x + far_letter = letter in (:x, :y) ? :z : :x - if sp[:framestyle] != :none + ax = sp[Symbol(letter, :axis)] + nax = sp[Symbol(near_letter, :axis)] + fax = sp[Symbol(far_letter, :axis)] - # xaxis - y1, y2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 + amin, amax = axis_limits(sp, letter) + namin, namax = axis_limits(sp, near_letter) + famin, famax = axis_limits(sp, far_letter) + + ticks = get_ticks(sp, ax, update = false) + minor_ticks = get_minor_ticks(sp, ax, ticks) + + # initialize the segments + segments = Segments(3) + tick_segments = Segments(3) + grid_segments = Segments(3) + minorgrid_segments = Segments(3) + border_segments = Segments(3) + + + if sp[:framestyle] != :none# && letter === :x + na0, na1 = if sp[:framestyle] in (:origin, :zerolines) + 0, 0 else - xor(xaxis[:mirror], yaxis[:flip]) ? (ymax, ymin) : (ymin, ymax) + # reverse_if((namin, namax), xor(ax[:mirror], nax[:flip])) + reverse_if(reverse_if((namin, namax), letter === :y), xor(ax[:mirror], nax[:flip])) end - z1, z2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 + fa0, fa1 = if sp[:framestyle] in (:origin, :zerolines) + 0, 0 else - xor(xaxis[:mirror], zaxis[:flip]) ? (zmax, zmin) : (zmin, zmax) + reverse_if((famin, famax), xor(ax[:mirror], fax[:flip])) end - if xaxis[:showaxis] + if ax[:showaxis] if sp[:framestyle] != :grid - push!(xaxis_segs, (xmin, y1, z1), (xmax, y1, z1)) + push!( + segments, + sort_3d_axes(amin, na0, fa0, letter), + sort_3d_axes(amax, na0, fa0, letter), + ) # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(xticks in (:none, nothing, false)) && length(xticks) > 1 - showticks = xticks[1] .!= 0 - xticks = (xticks[1][showticks], xticks[2][showticks]) + if sp[:framestyle] == :origin && !(ticks in (:none, nothing, false)) && length(ticks) > 1 + i0 = findfirst(==(0), ticks[1]) + if i0 !== nothing + deleteat!(ticks[1], i0) + deleteat!(ticks[2], i0) + end end end - sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2, z2), (xmax, y2, z2)) # top spine + if sp[:framestyle] in (:semi, :box) + push!( + border_segments, + sort_3d_axes(amin, na1, fa1, letter), + sort_3d_axes(amax, na1, fa1, letter), + ) + end end - if !(xaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(yaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(yaxis[:scale]) - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(ymax) - f(ymin))) - (-t, t) - else - ticks_in = xaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(y1) + 0.012 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - for xtick in xticks[1] - if xaxis[:showaxis] - push!(xtick_segs, (xtick, tick_start, z1), (xtick, tick_stop, z1)) # bottom tick - end - if xaxis[:grid] - if sp[:framestyle] in (:origin, :zerolines) - push!(xgrid_segs, (xtick, ymin, 0.0), (xtick, ymax, 0.0)) - push!(xgrid_segs, (xtick, 0.0, zmin), (xtick, 0.0, zmax)) + if ax[:ticks] ∉ (:none, nothing, false) + f = RecipesPipeline.scale_func(nax[:scale]) + invf = RecipesPipeline.inverse_scale_func(nax[:scale]) + ga0, ga1 = sp[:framestyle] in (:origin, :zerolines) ? (namin, namax) : (na0, na1) + + add_major_or_minor_segments(ticks, grid, segments, factor, cond) = begin + if cond + tick_start, tick_stop = if sp[:framestyle] == :origin + t = invf(f(0) + factor * (f(namax) - f(namin))) + (-t, t) else - push!(xgrid_segs, (xtick, y1, z1), (xtick, y2, z1)) - push!(xgrid_segs, (xtick, y2, z1), (xtick, y2, z2)) + ticks_in = ax[:tick_direction] == :out ? -1 : 1 + t = invf(f(na0) + factor * (f(na1) - f(na0)) * ticks_in) + (na0, t) + end + end + + for tick in ticks + if ax[:showaxis] && cond + push!( + tick_segments, + sort_3d_axes(tick, tick_start, fa0, letter), + sort_3d_axes(tick, tick_stop, fa0, letter), + ) + end + if grid + fa0_, fa1_ = reverse_if((fa0, fa1), ax[:mirror]) + ga0_, ga1_ = reverse_if((ga0, ga1), ax[:mirror]) + push!( + segments, + sort_3d_axes(tick, ga0_, fa0_, letter), + sort_3d_axes(tick, ga1_, fa0_, letter), + ) + push!( + segments, + sort_3d_axes(tick, ga1_, fa0_, letter), + sort_3d_axes(tick, ga1_, fa1_, letter), + ) end end end - if !(xaxis[:minorticks] in (:none, nothing, false)) || xaxis[:minorgrid] - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(ymax) - f(ymin))) - (-t, t) - else - t = invf(f(y1) + 0.006 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - for xtick in xminorticks - if xaxis[:showaxis] - push!(xtick_segs, (xtick, tick_start, z1), (xtick, tick_stop, z1)) # bottom tick - end - if xaxis[:minorgrid] - if sp[:framestyle] in (:origin, :zerolines) - push!(xminorgrid_segs, (xtick, ymin, 0.0), (xtick, ymax, 0.0)) - push!(xminorgrid_segs, (xtick, 0.0, zmin), (xtick, 0.0, zmax)) - else - push!(xminorgrid_segs, (xtick, y1, z1), (xtick, y2, z1)) - push!(xminorgrid_segs, (xtick, y2, z1), (xtick, y2, z2)) - end - end - end - end - end + # add major grid segments + add_major_or_minor_segments(ticks[1], ax[:grid], grid_segments, 0.012, ax[:tick_direction] !== :none) - - # yaxis - x1, x2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(yaxis[:mirror], xaxis[:flip]) ? (xmin, xmax) : (xmax, xmin) - end - z1, z2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(yaxis[:mirror], zaxis[:flip]) ? (zmax, zmin) : (zmin, zmax) - end - if yaxis[:showaxis] - if sp[:framestyle] != :grid - push!(yaxis_segs, (x1, ymin, z1), (x1, ymax, z1)) - # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(yticks in (:none, nothing,false)) && length(yticks) > 1 - showticks = yticks[1] .!= 0 - yticks = (yticks[1][showticks], yticks[2][showticks]) - end - end - sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin, z2), (x2, ymax, z2)) # right spine - end - if !(yaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(xaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(xaxis[:scale]) - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(xmax) - f(xmin))) - (-t, t) - else - ticks_in = yaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(x1) + 0.012 * (f(x2) - f(x1)) * ticks_in) - (x1, t) - end - - for ytick in yticks[1] - if yaxis[:showaxis] - push!(ytick_segs, (tick_start, ytick, z1), (tick_stop, ytick, z1)) # left tick - end - if yaxis[:grid] - if sp[:framestyle] in (:origin, :zerolines) - push!(ygrid_segs, (xmin, ytick, 0.0), (xmax, ytick, 0.0)) - push!(ygrid_segs, (0.0, ytick, zmin), (0.0, ytick, zmax)) - else - push!(ygrid_segs, (x1, ytick, z1), (x2, ytick, z1)) - push!(ygrid_segs, (x2, ytick, z1), (x2, ytick, z2)) - end - end - end - - if !(yaxis[:minorticks] in (:none, nothing, false)) || yaxis[:minorgrid] - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(xmax) - f(xmin))) - (-t, t) - else - t = invf(f(x1) + 0.006 * (f(x2) - f(x1)) * ticks_in) - (x1, t) - end - for ytick in yminorticks - if yaxis[:showaxis] - push!(ytick_segs, (tick_start, ytick, z1), (tick_stop, ytick, z1)) # left tick - end - if yaxis[:minorgrid] - if sp[:framestyle] in (:origin, :zerolines) - push!(yminorgrid_segs, (xmin, ytick, 0.0), (xmax, ytick, 0.0)) - push!(yminorgrid_segs, (0.0, ytick, zmin), (0.0, ytick, zmax)) - else - push!(yminorgrid_segs, (x1, ytick, z1), (x2, ytick, z1)) - push!(yminorgrid_segs, (x2, ytick, z1), (x2, ytick, z2)) - end - end - end - end - end - - - # zaxis - x1, x2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(zaxis[:mirror], xaxis[:flip]) ? (xmax, xmin) : (xmin, xmax) - end - y1, y2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(zaxis[:mirror], yaxis[:flip]) ? (ymax, ymin) : (ymin, ymax) - end - if zaxis[:showaxis] - if sp[:framestyle] != :grid - push!(zaxis_segs, (x1, y1, zmin), (x1, y1, zmax)) - # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(zticks in (:none, nothing,false)) && length(zticks) > 1 - showticks = zticks[1] .!= 0 - zticks = (zticks[1][showticks], zticks[2][showticks]) - end - end - sp[:framestyle] in (:semi, :box) && push!(zborder_segs, (x2, y2, zmin), (x2, y2, zmax)) - end - if !(zaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(xaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(xaxis[:scale]) - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(ymax) - f(ymin))) - (-t, t) - else - ticks_in = zaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(y1) + 0.012 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - - for ztick in zticks[1] - if zaxis[:showaxis] - push!(ztick_segs, (x1, tick_start, ztick), (x1, tick_stop, ztick)) # left tick - end - if zaxis[:grid] - if sp[:framestyle] in (:origin, :zerolines) - push!(zgrid_segs, (xmin, 0.0, ztick), (xmax, 0.0, ztick)) - push!(ygrid_segs, (0.0, ymin, ztick), (0.0, ymax, ztick)) - else - push!(ygrid_segs, (x1, y1, ztick), (x1, y2, ztick)) - push!(ygrid_segs, (x1, y2, ztick), (x2, y2, ztick)) - end - end - end - - if !(zaxis[:minorticks] in (:none, nothing, false)) || zaxis[:minorgrid] - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(ymax) - f(ymin))) - (-t, t) - else - t = invf(f(y1) + 0.006 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - for ztick in zminorticks - if zaxis[:showaxis] - push!(ztick_segs, (x1, tick_start, ztick), (x1, tick_stop, ztick)) # left tick - end - if zaxis[:minorgrid] - if sp[:framestyle] in (:origin, :zerolines) - push!(zminorgrid_segs, (xmin, 0.0, ztick), (xmax, 0.0, ztick)) - push!(zminorgrid_segs, (0.0, ymin, ztick), (0.0, ymax, ztick)) - else - push!(zminorgrid_segs, (x1, y1, ztick), (x1, y2, ztick)) - push!(zminorgrid_segs, (x1, y2, ztick), (x2, y2, ztick)) - end - end - end + # add minor grid segments + if ax[:minorticks] ∉ (:none, nothing, false) || ax[:minorgrid] + add_major_or_minor_segments(minor_ticks, ax[:minorgrid], minorgrid_segments, 0.006, true) end end end - xticks, yticks, zticks, xaxis_segs, yaxis_segs, zaxis_segs, xtick_segs, ytick_segs, ztick_segs, xgrid_segs, ygrid_segs, zgrid_segs, xminorgrid_segs, yminorgrid_segs, zminorgrid_segs, xborder_segs, yborder_segs, zborder_segs + return ( + ticks = ticks, + segments = segments, + tick_segments = tick_segments, + grid_segments = grid_segments, + minorgrid_segments = minorgrid_segments, + border_segments = border_segments + ) end + +reverse_if(x, cond) = cond ? reverse(x) : x +axis_tuple(x, y, letter) = reverse_if((x, y), letter === :y) + +axes_shift(t, i) = i % 3 == 0 ? t : i % 3 == 1 ? (t[3], t[1], t[2]) : (t[2], t[3], t[1]) diff --git a/src/backends.jl b/src/backends.jl index d71b3ffb..85ebfe98 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -1,5 +1,3 @@ -using Pkg - struct NoBackend <: AbstractBackend end const _backendType = Dict{Symbol, DataType}(:none => NoBackend) @@ -190,11 +188,15 @@ function backend(sym::Symbol) end end -const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse, :glvisualize] +const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse, :glvisualize, :pgfplots] function warn_on_deprecated_backend(bsym::Symbol) if bsym in _deprecated_backends - @warn("Backend $bsym has been deprecated.") + if bsym == :pgfplots + @warn("Backend $bsym has been deprecated. Use pgfplotsx instead.") + else + @warn("Backend $bsym has been deprecated.") + end end end @@ -314,7 +316,6 @@ const _gr_attr = merge_with_base_supported([ :layout, :title, :window_title, :guide, :lims, :ticks, :scale, :flip, - :match_dimensions, :titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign, :titlefontrotation, :titlefontcolor, :legendfontfamily, :legendfontsize, :legendfonthalign, :legendfontvalign, @@ -365,10 +366,10 @@ is_marker_supported(::GRBackend, shape::Shape) = true function _initialize_backend(pkg::PlotlyBackend) try @eval Main begin - import ORCA + import PlotlyBase end catch - @info "For saving to png with the Plotly backend ORCA has to be installed." + @info "For saving to png with the Plotly backend PlotlyBase has to be installed." end end @@ -427,6 +428,7 @@ const _plotly_seriestype = [ :shape, :scattergl, :straightline, + :mesh3d ] const _plotly_style = [:auto, :solid, :dash, :dot, :dashdot] const _plotly_marker = [ @@ -469,7 +471,6 @@ const _pgfplots_attr = merge_with_base_supported([ :polar, # :normalize, :weights, :contours, :aspect_ratio, - # :match_dimensions, :tick_direction, :framestyle, :camera, @@ -485,7 +486,7 @@ const _pgfplots_scale = [:identity, :ln, :log2, :log10] function _initialize_backend(pkg::PlotlyJSBackend) @eval Main begin - import PlotlyJS, ORCA + import PlotlyJS export PlotlyJS end end @@ -530,6 +531,10 @@ const _pyplot_attr = merge_with_base_supported([ :guidefontfamily, :guidefontsize, :guidefontcolor, :grid, :gridalpha, :gridstyle, :gridlinewidth, :legend, :legendtitle, :colorbar, :colorbar_title, :colorbar_entry, + :colorbar_ticks, :colorbar_tickfontfamily, :colorbar_tickfontsize, + :colorbar_tickfonthalign, :colorbar_tickfontvalign, + :colorbar_tickfontrotation, :colorbar_tickfontcolor, + :colorbar_scale, :marker_z, :line_z, :fill_z, :levels, :ribbon, :quiver, :arrow, @@ -538,7 +543,6 @@ const _pyplot_attr = merge_with_base_supported([ :polar, :normalize, :weights, :contours, :aspect_ratio, - :match_dimensions, :clims, :inset_subplots, :dpi, @@ -551,6 +555,7 @@ const _pyplot_attr = merge_with_base_supported([ const _pyplot_seriestype = [ :path, :steppre, + :stepmid, :steppost, :shape, :straightline, @@ -624,7 +629,6 @@ const _hdf5_attr = merge_with_base_supported([ :polar, :normalize, :weights, :contours, :aspect_ratio, - :match_dimensions, :clims, :inset_subplots, :dpi, @@ -633,6 +637,7 @@ const _hdf5_attr = merge_with_base_supported([ const _hdf5_seriestype = [ :path, :steppre, + :stepmid, :steppost, :shape, :straightline, @@ -662,11 +667,6 @@ mutable struct HDF5Plot_PlotRef end const HDF5PLOT_PLOTREF = HDF5Plot_PlotRef(nothing) -#Simple sub-structures that can just be written out using _hdf5plot_gwritefields: -const HDF5PLOT_SIMPLESUBSTRUCT = Union{Font, BoundingBox, - GridLayout, RootLayout, ColorGradient, SeriesAnnotations, PlotText, - Shape, -} # ------------------------------------------------------------------------------ # inspectdr @@ -702,7 +702,6 @@ const _inspectdr_attr = merge_with_base_supported([ :polar, # :normalize, :weights, # :contours, :aspect_ratio, - :match_dimensions, # :clims, # :inset_subplots, :dpi, @@ -710,7 +709,7 @@ const _inspectdr_attr = merge_with_base_supported([ ]) const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot] const _inspectdr_seriestype = [ - :path, :scatter, :shape, :straightline, #, :steppre, :steppost + :path, :scatter, :shape, :straightline, #, :steppre, :stepmid, :steppost ] #see: _allMarkers, _shape_keys const _inspectdr_marker = Symbol[ @@ -763,7 +762,6 @@ const _pgfplotsx_attr = merge_with_base_supported([ :ticks, :scale, :flip, - :match_dimensions, :titlefontfamily, :titlefontsize, :titlefonthalign, @@ -826,6 +824,7 @@ const _pgfplotsx_seriestype = [ :surface, :wireframe, :heatmap, + :mesh3d, :contour, :contour3d, :quiver, @@ -849,6 +848,7 @@ const _pgfplotsx_marker = [ :rtriangle, :cross, :xcross, + :x, :star5, :pentagon, :hline, diff --git a/src/backends/pgfplots.jl b/src/backends/deprecated/pgfplots.jl similarity index 100% rename from src/backends/pgfplots.jl rename to src/backends/deprecated/pgfplots.jl diff --git a/src/backends/gr.jl b/src/backends/gr.jl index f4bdcd4d..ca31f3d3 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -42,18 +42,50 @@ gr_halign(k) = (left = 1, hcenter = 2, right = 3)[k] gr_valign(k) = (top = 1, vcenter = 3, bottom = 5)[k] const gr_font_family = Dict( - "times" => 1, - "helvetica" => 5, - "courier" => 9, - "bookman" => 14, - "newcenturyschlbk" => 18, - "avantgarde" => 22, - "palatino" => 26 -) - -const gr_vector_font = Dict( + # compat: + "times" => 101, + "helvetica" => 105, + "courier" => 109, + "bookman" => 114, + "newcenturyschlbk" => 118, + "avantgarde" => 122, + "palatino" => 126, "serif-roman" => 232, - "sans-serif" => 233 + "sans-serif" => 233, + # https://gr-framework.org/fonts.html: + "times roman" => 101, + "times italic" => 102, + "times bold" => 103, + "times bold italic" => 104, + "helvetica" => 105, + "helvetica oblique" => 106, + "helvetica bold" => 107, + "helvetica bold oblique" => 108, + "courier" => 109, + "courier oblique" => 110, + "courier bold" => 111, + "courier bold oblique" => 112, + "symbol" => 113, + "bookman light" => 114, + "bookman light italic" => 115, + "bookman demi" => 116, + "bookman demi italic" => 117, + "new century schoolbook roman" => 118, + "new century schoolbook italic" => 119, + "new century schoolbook bold" => 120, + "new century schoolbook bold italic" => 121, + "avantgarde book" => 122, + "avantgarde book oblique" => 123, + "avantgarde demi" => 124, + "avantgarde demi oblique" => 125, + "palatino roman" => 126, + "palatino italic" => 127, + "palatino bold" => 128, + "palatino bold italic" => 129, + "zapf chancery medium italic" => 130, + "zapf dingbats" => 131, + "computer modern" => 232, + "dejavu sans" => 233, ) # -------------------------------------------------------------------------------------- @@ -182,24 +214,24 @@ function gr_polyline3d(x, y, z, func = GR.polyline3d) end end -gr_inqtext(x, y, s::Symbol) = gr_inqtext(x, y, string(s)) +gr_inqtext(x, y, s) = gr_inqtext(x, y, string(s)) -function gr_inqtext(x, y, s) +function gr_inqtext(x, y, s::AbstractString) if length(s) >= 2 && s[1] == '$' && s[end] == '$' GR.inqmathtex(x, y, s[2:end-1]) - elseif findfirst(isequal('\\'), s) !== nothing || occursin("10^{", s) + elseif occursin('\\', s) || occursin("10^{", s) GR.inqtextext(x, y, s) else GR.inqtext(x, y, s) end end -gr_text(x, y, s::Symbol) = gr_text(x, y, string(s)) +gr_text(x, y, s) = gr_text(x, y, string(s)) -function gr_text(x, y, s) +function gr_text(x, y, s::AbstractString) if length(s) >= 2 && s[1] == '$' && s[end] == '$' GR.mathtex(x, y, s[2:end-1]) - elseif findfirst(isequal('\\'), s) !== nothing || occursin("10^{", s) + elseif occursin('\\', s) || occursin("10^{", s) GR.textext(x, y, s) else GR.text(x, y, s) @@ -215,8 +247,7 @@ function gr_polaraxes(rmin::Real, rmax::Real, sp::Subplot) a = α .+ 90 sinf = sind.(a) cosf = cosd.(a) - rtick_values, rtick_labels = get_ticks(sp, yaxis) - rtick_labels = gr_tick_label.((yaxis,), rtick_labels) + rtick_values, rtick_labels = get_ticks(sp, yaxis, update = false) #draw angular grid if xaxis[:grid] @@ -317,7 +348,7 @@ function gr_draw_marker(series, xi, yi, clims, i, msize, strokewidth, shape::Sha GR.selntran(1) end -function nominal_size(s) +function gr_nominal_size(s) w, h = get_size(s) min(w, h) / 500 end @@ -329,45 +360,20 @@ function gr_draw_marker(series, xi, yi, clims, i, msize, strokewidth, shape::Sym gr_set_markercolor(get_markercolor(series, clims, i)); gr_set_transparency(get_markeralpha(series, i)) GR.setmarkertype(gr_markertype(shape)) - GR.setmarkersize(0.3msize / nominal_size(series)) + GR.setmarkersize(0.3msize / gr_nominal_size(series)) GR.polymarker([xi], [yi]) end -# draw the markers, one at a time -function gr_draw_markers( - series::Series, - x, - y, - clims, - msize = series[:markersize], - strokewidth = series[:markerstrokewidth], -) - - isempty(x) && return - GR.setfillintstyle(GR.INTSTYLE_SOLID) - - shapes = series[:markershape] - if shapes != :none - for i in eachindex(x) - ms = get_thickness_scaling(series) * _cycle(msize, i) - msw = get_thickness_scaling(series) * _cycle(strokewidth, i) - shape = _cycle(shapes, i) - gr_draw_marker(series, x[i], y[i], clims, i, ms, msw, shape) - end - end -end - # --------------------------------------------------------- function gr_set_line(lw, style, c, s) # s can be Subplot or Series GR.setlinetype(gr_linetype(style)) - GR.setlinewidth(get_thickness_scaling(s) * max(0, lw / nominal_size(s))) + GR.setlinewidth(get_thickness_scaling(s) * max(0, lw / gr_nominal_size(s))) gr_set_linecolor(c) end - function gr_set_fill(c) #, a) gr_set_fillcolor(c) #, a) GR.setfillintstyle(GR.INTSTYLE_SOLID) @@ -384,9 +390,10 @@ function gr_set_font(f::Font, s; halign = f.halign, valign = f.valign, GR.setcharheight(gr_point_mult(s) * f.pointsize) GR.setcharup(sind(-rotation), cosd(-rotation)) if haskey(gr_font_family, family) - GR.settextfontprec(100 + gr_font_family[family], GR.TEXT_PRECISION_STRING) - elseif haskey(gr_vector_font, family) - GR.settextfontprec(gr_vector_font[family], 3) + GR.settextfontprec( + gr_font_family[family], + gr_font_family[family] >= 200 ? 3 : GR.TEXT_PRECISION_STRING + ) end gr_set_textcolor(color) GR.settextalign(gr_halign(halign), gr_valign(valign)) @@ -394,7 +401,7 @@ end function gr_nans_to_infs!(z) for (i,zi) in enumerate(z) - if zi == NaN + if isnan(zi) z[i] = Inf end end @@ -564,56 +571,25 @@ end gr_view_xcenter(viewport_plotarea) = 0.5 * (viewport_plotarea[1] + viewport_plotarea[2]) gr_view_ycenter(viewport_plotarea) = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4]) -function gr_legend_pos(sp::Subplot, w, h, viewport_plotarea) - s = sp[:legend] - typeof(s) <: Symbol || return gr_legend_pos(s, w, h, viewport_plotarea) - str = string(s) - if str == "best" - str = "topright" +gr_view_xposition(viewport_plotarea, position) = viewport_plotarea[1] + position * (viewport_plotarea[2] - viewport_plotarea[1]) +gr_view_yposition(viewport_plotarea, position) = viewport_plotarea[3] + position * (viewport_plotarea[4] - viewport_plotarea[3]) + +function position(symb) + if symb == :top || symb == :right + return 0.95 + elseif symb == :left || symb == :bottom + return 0.05 end - if occursin("outer", str) - xaxis, yaxis = sp[:xaxis], sp[:yaxis] - xmirror = xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) - ymirror = yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) - end - if occursin("right", str) - if occursin("outer", str) - # As per https://github.com/jheinen/GR.jl/blob/master/src/jlgr.jl#L525 - xpos = viewport_plotarea[2] + 0.11 + ymirror * gr_axis_width(sp, sp[:yaxis]) - else - xpos = viewport_plotarea[2] - 0.05 - w - end - elseif occursin("left", str) - if occursin("outer", str) - xpos = viewport_plotarea[1] - 0.05 - w - !ymirror * gr_axis_width(sp, sp[:yaxis]) - else - xpos = viewport_plotarea[1] + 0.11 - end - else - xpos = (viewport_plotarea[2]-viewport_plotarea[1])/2 - w/2 +.04 - end - if occursin("top", str) - if s == :outertop - ypos = viewport_plotarea[4] + 0.02 + h + xmirror * gr_axis_height(sp, sp[:xaxis]) - else - ypos = viewport_plotarea[4] - 0.06 - end - elseif occursin("bottom", str) - if s == :outerbottom - ypos = viewport_plotarea[3] - 0.05 - !xmirror * gr_axis_height(sp, sp[:xaxis]) - else - ypos = viewport_plotarea[3] + h + 0.06 - end - else - ypos = (viewport_plotarea[4]-viewport_plotarea[3])/2 + h/2 - end - (xpos,ypos) + return 0.5 end -function gr_legend_pos(v::Tuple{S,T}, w, h, viewport_plotarea) where {S<:Real, T<:Real} - xpos = v[1] * (viewport_plotarea[2] - viewport_plotarea[1]) + viewport_plotarea[1] - ypos = v[2] * (viewport_plotarea[4] - viewport_plotarea[3]) + viewport_plotarea[3] - (xpos,ypos) +function alignment(symb) + if symb == :top || symb == :right + return GR.TEXT_HALIGN_RIGHT + elseif symb == :left || symb == :bottom + return GR.TEXT_HALIGN_LEFT + end + return GR.TEXT_HALIGN_CENTER end # -------------------------------------------------------------------------------------- @@ -637,9 +613,6 @@ function gr_display(plt::Plot, fmt="") GR.clearws() dpi_factor = plt[:dpi] / Plots.DPI - if fmt == "svg" - dpi_factor *= 4 - end # collect some monitor/display sizes in meters and pixels display_width_meters, display_height_meters, display_width_px, display_height_px = GR.inqdspsize() @@ -676,32 +649,28 @@ function gr_display(plt::Plot, fmt="") GR.updatews() end +function gr_set_tickfont(sp, letter) + axis = sp[Symbol(letter, :axis)] -function gr_set_xticks_font(sp) - flip = sp[:yaxis][:flip] - mirror = sp[:xaxis][:mirror] + # invalidate alignment changes for small rotations (|θ| < 45°) + trigger(rot) = abs(sind(rot)) < abs(cosd(rot)) ? 0 : sign(rot) + + rot = axis[:rotation] + if letter === :x || (RecipesPipeline.is3d(sp) && letter === :y) + halign = (:left, :hcenter, :right)[trigger(rot) + 2] + valign = (axis[:mirror] ? :bottom : :top, :vcenter)[trigger(abs(rot)) + 1] + else + halign = (axis[:mirror] ? :left : :right, :hcenter)[trigger(abs(rot)) + 1] + valign = (:top, :vcenter, :bottom)[trigger(rot) + 2] + end gr_set_font( - tickfont(sp[:xaxis]), + tickfont(axis), sp, - halign = (:left, :hcenter, :right)[sign(sp[:xaxis][:rotation]) + 2], - valign = (mirror ? :bottom : :top), - rotation = sp[:xaxis][:rotation], + halign = halign, + valign = valign, + rotation = axis[:rotation], + color = axis[:tickfontcolor], ) - return flip, mirror -end - - -function gr_set_yticks_font(sp) - flip = sp[:xaxis][:flip] - mirror = sp[:yaxis][:mirror] - gr_set_font( - tickfont(sp[:yaxis]), - sp, - halign = (mirror ? :left : :right), - valign = (:top, :vcenter, :bottom)[sign(sp[:yaxis][:rotation]) + 2], - rotation = sp[:yaxis][:rotation], - ) - return flip, mirror end function gr_text_size(str) @@ -741,22 +710,15 @@ function gr_get_ticks_size(ticks, rot) return w, h end -gr_tick_label(axis, label) = - (axis[:formatter] in (:scientific, :auto)) ? gr_convert_sci_tick_label(string(label)) : - string(label) - -function gr_convert_sci_tick_label(label) - caret_split = split(label,'^') - if length(caret_split) == 2 - base, exponent = caret_split - label = "$base^{$exponent}" - end - convert_sci_unicode(label) +function labelfunc(scale::Symbol, backend::GRBackend) + texfunc = labelfunc_tex(scale) + # replace dash with \minus (U+2212) + label -> replace(texfunc(label), "-" => "−") end function gr_axis_height(sp, axis) GR.savestate() - ticks = get_ticks(sp, axis) + ticks = get_ticks(sp, axis, update = false) gr_set_font(tickfont(axis), sp) h = (ticks in (nothing, false, :none) ? 0 : last(gr_get_ticks_size(ticks, axis[:rotation]))) if axis[:guide] != "" @@ -769,7 +731,7 @@ end function gr_axis_width(sp, axis) GR.savestate() - ticks = get_ticks(sp, axis) + ticks = get_ticks(sp, axis, update = false) gr_set_font(tickfont(axis), sp) w = (ticks in (nothing, false, :none) ? 0 : first(gr_get_ticks_size(ticks, axis[:rotation]))) if axis[:guide] != "" @@ -806,7 +768,7 @@ function _update_min_padding!(sp::Subplot{GRBackend}) xticks, yticks, zticks = get_ticks(sp, xaxis), get_ticks(sp, yaxis), get_ticks(sp, zaxis) # Add margin for x and y ticks h = 0mm - if !(xticks in (nothing, false, :none)) + if !isempty(first(xticks)) gr_set_font( tickfont(xaxis), halign = (:left, :hcenter, :right)[sign(xaxis[:rotation]) + 2], @@ -817,7 +779,7 @@ function _update_min_padding!(sp::Subplot{GRBackend}) l = 0.01 + last(gr_get_ticks_size(xticks, xaxis[:rotation])) h = max(h, 1mm + get_size(sp)[2] * l * px) end - if !(yticks in (nothing, false, :none)) + if !isempty(first(yticks)) gr_set_font( tickfont(yaxis), halign = (:left, :hcenter, :right)[sign(yaxis[:rotation]) + 2], @@ -837,7 +799,7 @@ function _update_min_padding!(sp::Subplot{GRBackend}) end end - if !(zticks in (nothing, false, :none)) + if !isempty(first(zticks)) gr_set_font( tickfont(zaxis), halign = (zaxis[:mirror] ? :left : :right), @@ -888,21 +850,21 @@ function _update_min_padding!(sp::Subplot{GRBackend}) else # Add margin for x and y ticks xticks, yticks = get_ticks(sp, sp[:xaxis]), get_ticks(sp, sp[:yaxis]) - if !(xticks in (nothing, false, :none)) - flip, mirror = gr_set_xticks_font(sp) + if !isempty(first(xticks)) + gr_set_tickfont(sp, :x) l = 0.01 + last(gr_get_ticks_size(xticks, sp[:xaxis][:rotation])) h = 1mm + get_size(sp)[2] * l * px - if mirror + if sp[:xaxis][:mirror] toppad += h else bottompad += h end end - if !(yticks in (nothing, false, :none)) - flip, mirror = gr_set_yticks_font(sp) + if !isempty(first(yticks)) + gr_set_tickfont(sp, :y) l = 0.01 + first(gr_get_ticks_size(yticks, sp[:yaxis][:rotation])) w = 1mm + get_size(sp)[1] * l * px - if mirror + if sp[:yaxis][:mirror] rightpad += w else leftpad += w @@ -943,38 +905,263 @@ function is_equally_spaced(v) all(d .≈ d[1]) end +remap(x, lo, hi) = (x - lo) / (hi - lo) +function get_z_normalized(z, clims...) + isnan(z) && return 256 / 255 + return remap(clamp(z, clims...), clims...) +end + +function gr_clims(args...) + lo, hi = get_clims(args...) + if lo == hi + if lo == 0 + hi = one(hi) + elseif lo < 0 + hi = zero(hi) + else + lo = zero(lo) + end + end + return lo, hi +end + function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) _update_min_padding!(sp) # the viewports for this subplot viewport_subplot = gr_viewport_from_bbox(sp, bbox(sp), w, h, viewport_canvas) viewport_plotarea = gr_viewport_from_bbox(sp, plotarea(sp), w, h, viewport_canvas) - # get data limits - data_lims = gr_xy_axislims(sp) - xy_lims = data_lims - ratio = get_aspect_ratio(sp) - if ratio != :none - if ratio == :equal - ratio = 1 - end - viewport_ratio = (viewport_plotarea[2] - viewport_plotarea[1]) / (viewport_plotarea[4] - viewport_plotarea[3]) - window_ratio = (data_lims[2] - data_lims[1]) / (data_lims[4] - data_lims[3]) / ratio - if window_ratio < viewport_ratio - viewport_center = 0.5 * (viewport_plotarea[1] + viewport_plotarea[2]) - viewport_size = (viewport_plotarea[2] - viewport_plotarea[1]) * window_ratio / viewport_ratio - viewport_plotarea[1] = viewport_center - 0.5 * viewport_size - viewport_plotarea[2] = viewport_center + 0.5 * viewport_size - elseif window_ratio > viewport_ratio - viewport_center = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4]) - viewport_size = (viewport_plotarea[4] - viewport_plotarea[3]) * viewport_ratio / window_ratio - viewport_plotarea[3] = viewport_center - 0.5 * viewport_size - viewport_plotarea[4] = viewport_center + 0.5 * viewport_size - end + # update viewport_plotarea + leg = gr_get_legend_geometry(viewport_plotarea, sp) + gr_update_viewport_legend!(viewport_plotarea, sp, leg) + gr_update_viewport_ratio!(viewport_plotarea, sp) + + # fill in the plot area background + gr_fill_plotarea(sp, viewport_plotarea) + + # set our plot area view + GR.setviewport(viewport_plotarea...) + + # set the scale flags and window + gr_set_window(sp, viewport_plotarea) + + # draw the axes + gr_draw_axes(sp, viewport_plotarea) + gr_add_title(sp, viewport_plotarea, viewport_subplot) + + # this needs to be here to point the colormap to the right indices + GR.setcolormap(1000 + GR.COLORMAP_COOLWARM) + + # init the colorbar + cbar = GRColorbar() + + for series in series_list(sp) + gr_add_series(sp, series) + gr_update_colorbar!(cbar, series) end - # calculate legend size - # has to be done now due to a potential adjustment to the plotarea given an outer legend. + # draw the colorbar + hascolorbar(sp) && gr_draw_colorbar(cbar, sp, gr_clims(sp), viewport_plotarea) + + # add the legend + gr_add_legend(sp, leg, viewport_plotarea) + + # add annotations + for ann in sp[:annotations] + x, y, val = locate_annotation(sp, ann...) + x, y = if RecipesPipeline.is3d(sp) + gr_w3tondc(x, y, z) + else + GR.wctondc(x, y) + end + gr_set_font(val.font, sp) + gr_text(x, y, val.str) + end +end + + +## Legend + +function gr_add_legend(sp, leg, viewport_plotarea) + if !(sp[:legend] in(:none, :inline)) + GR.savestate() + GR.selntran(0) + GR.setscale(0) + gr_set_font(legendfont(sp), sp) + if leg.w > 0 + xpos, ypos = gr_legend_pos(sp, leg, viewport_plotarea) + GR.setfillintstyle(GR.INTSTYLE_SOLID) + gr_set_fillcolor(sp[:background_color_legend]) + GR.fillrect( + xpos - leg.leftw, xpos + leg.textw + leg.rightw, + ypos + leg.dy, ypos - leg.h + ) # Allocating white space for actual legend width here + gr_set_line(1, :solid, sp[:foreground_color_legend], sp) + GR.drawrect( + xpos - leg.leftw, xpos + leg.textw + leg.rightw, + ypos + leg.dy, ypos - leg.h + ) # Drawing actual legend width here + i = 0 + if sp[:legendtitle] !== nothing + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_HALF) + gr_set_font(legendtitlefont(sp), sp) + gr_text(xpos - 0.03 + 0.5 * leg.w, ypos, string(sp[:legendtitle])) + ypos -= leg.dy + gr_set_font(legendfont(sp), sp) + end + for series in series_list(sp) + clims = gr_clims(sp, series) + should_add_to_legend(series) || continue + st = series[:seriestype] + lc = get_linecolor(series, clims) + gr_set_line(sp[:legendfontsize] / 8, get_linestyle(series), lc, sp) + + if (st == :shape || series[:fillrange] !== nothing) && series[:ribbon] === nothing + fc = get_fillcolor(series, clims) + gr_set_fill(fc) #, series[:fillalpha]) + l, r = xpos - leg.width_factor * 3.5, xpos - leg.width_factor / 2 + b, t = ypos - 0.4 * leg.dy, ypos + 0.4 * leg.dy + x = [l, r, r, l, l] + y = [b, b, t, t, b] + gr_set_transparency(fc, get_fillalpha(series)) + gr_polyline(x, y, GR.fillarea) + lc = get_linecolor(series, clims) + gr_set_transparency(lc, get_linealpha(series)) + gr_set_line(get_linewidth(series), get_linestyle(series), lc, sp) + st == :shape && gr_polyline(x, y) + end + + if st in (:path, :straightline, :path3d) + gr_set_transparency(lc, get_linealpha(series)) + if series[:fillrange] === nothing || series[:ribbon] !== nothing + GR.polyline( + [xpos - leg.width_factor * 3.5, xpos - leg.width_factor / 2], + [ypos, ypos], + ) + else + GR.polyline( + [xpos - leg.width_factor * 3.5, xpos - leg.width_factor / 2], + [ypos + 0.4 * leg.dy, ypos + 0.4 * leg.dy], + ) + end + end + + if series[:markershape] != :none + ms = first(series[:markersize]) + msw = first(series[:markerstrokewidth]) + s, sw = if ms > 0 + 0.8 * sp[:legendfontsize], 0.8 * sp[:legendfontsize] * msw / ms + else + 0, 0.8 * sp[:legendfontsize] * msw / 8 + end + gr_draw_markers(series, xpos - leg.width_factor * 2, ypos, clims, s, sw) + end + + lab = series[:label] + GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) + gr_set_textcolor(plot_color(sp[:legendfontcolor])) + gr_text(xpos, ypos, string(lab)) + ypos -= leg.dy + end + end + GR.selntran(1) + GR.restorestate() + end +end + +function gr_legend_pos(sp::Subplot, leg, viewport_plotarea) + s = sp[:legend] + s isa Real && return gr_legend_pos(s, leg, viewport_plotarea) + if s isa Tuple{<:Real,Symbol} + if s[2] !== :outer + return gr_legend_pos(s[1], leg, viewport_plotarea) + end + + xaxis, yaxis = sp[:xaxis], sp[:yaxis] + xmirror = xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) + ymirror = yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) + axisclearance = [ + !ymirror*gr_axis_width(sp, sp[:yaxis]), + ymirror*gr_axis_width(sp,sp[:yaxis]), + !xmirror*gr_axis_height(sp,sp[:xaxis]), + xmirror*gr_axis_height(sp,sp[:xaxis]), + ] + return gr_legend_pos(s[1], leg, viewport_plotarea; axisclearance) + end + s isa Symbol || return gr_legend_pos(s, viewport_plotarea) + str = string(s) + if str == "best" + str = "topright" + end + if occursin("outer", str) + xaxis, yaxis = sp[:xaxis], sp[:yaxis] + xmirror = xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) + ymirror = yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) + end + if occursin("right", str) + if occursin("outer", str) + # As per https://github.com/jheinen/GR.jl/blob/master/src/jlgr.jl#L525 + xpos = viewport_plotarea[2] + leg.xoffset + leg.leftw + ymirror * gr_axis_width(sp, sp[:yaxis]) + else + xpos = viewport_plotarea[2] - leg.rightw - leg.textw - leg.xoffset + end + elseif occursin("left", str) + if occursin("outer", str) + xpos = viewport_plotarea[1] - !ymirror * gr_axis_width(sp, sp[:yaxis]) - leg.xoffset * 2 - leg.rightw - leg.textw + else + xpos = viewport_plotarea[1] + leg.leftw + leg.xoffset + end + else + xpos = (viewport_plotarea[2] - viewport_plotarea[1]) / 2 + viewport_plotarea[1] + leg.leftw - leg.rightw - leg.textw - leg.xoffset * 2 + end + if occursin("top", str) + if s == :outertop + ypos = viewport_plotarea[4] + leg.yoffset + leg.h + xmirror * gr_axis_height(sp, sp[:xaxis]) + else + ypos = viewport_plotarea[4] - leg.yoffset - leg.dy + end + elseif occursin("bottom", str) + if s == :outerbottom + ypos = viewport_plotarea[3] - leg.yoffset - leg.dy - !xmirror * gr_axis_height(sp, sp[:xaxis]) + else + ypos = viewport_plotarea[3] + leg.yoffset + leg.h + end + else + # Adding min y to shift legend pos to correct graph (#2377) + ypos = (viewport_plotarea[4] - viewport_plotarea[3] + leg.h) / 2 + viewport_plotarea[3] + end + return xpos, ypos +end + +function gr_legend_pos(v::Tuple{S,T}, viewport_plotarea) where {S<:Real, T<:Real} + xpos = v[1] * (viewport_plotarea[2] - viewport_plotarea[1]) + viewport_plotarea[1] + ypos = v[2] * (viewport_plotarea[4] - viewport_plotarea[3]) + viewport_plotarea[3] + (xpos,ypos) +end + +function gr_legend_pos(theta::Real, leg, viewport_plotarea; axisclearance=nothing) + xcenter = +(viewport_plotarea[1:2]...)/2 + ycenter = +(viewport_plotarea[3:4]...)/2 + + if isnothing(axisclearance) + # Inner + # rectangle where the anchor can legally be + xmin = viewport_plotarea[1] + leg.xoffset + leg.leftw + xmax = viewport_plotarea[2] - leg.xoffset - leg.rightw - leg.textw + ymin = viewport_plotarea[3] + leg.yoffset + leg.h + ymax = viewport_plotarea[4] - leg.yoffset - leg.dy + else + # Outer + xmin = viewport_plotarea[1] - leg.xoffset - leg.rightw - leg.textw - axisclearance[1] + xmax = viewport_plotarea[2] + leg.xoffset + leg.leftw + axisclearance[2] + ymin = viewport_plotarea[3] - leg.yoffset - leg.dy - axisclearance[3] + ymax = viewport_plotarea[4] + leg.yoffset + leg.h + axisclearance[4] + end + return legend_pos_from_angle(theta,xmin,xcenter,xmax,ymin,ycenter,ymax) +end + +function gr_get_legend_geometry(viewport_plotarea, sp) legendn = 0 legendw = 0 if sp[:legend] != :none @@ -993,7 +1180,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) legendn += 1 lab = series[:label] tbx, tby = gr_inqtext(0, 0, string(lab)) - legendw = max(legendw, tbx[3] - tbx[1]) + legendw = max(legendw, tbx[3] - tbx[1]) # Holds text width right now end GR.setscale(1) @@ -1001,482 +1188,425 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) GR.restorestate() end + legend_width_factor = (viewport_plotarea[2] - viewport_plotarea[1]) / 45 # Determines the width of legend box + legend_textw = legendw + legend_rightw = legend_width_factor + legend_leftw = legend_width_factor * 4 + total_legendw = legend_textw + legend_leftw + legend_rightw + + x_legend_offset = (viewport_plotarea[2] - viewport_plotarea[1]) / 30 + y_legend_offset = (viewport_plotarea[4] - viewport_plotarea[3]) / 30 + dy = gr_point_mult(sp) * sp[:legendfontsize] * 1.75 legendh = dy * legendn - leg_str = string(sp[:legend]) + + return ( + w = legendw, + h = legendh, + dy = dy, + leftw = legend_leftw, + textw = legend_textw, + rightw = legend_rightw, + xoffset = x_legend_offset, + yoffset = y_legend_offset, + width_factor = legend_width_factor, + ) +end + + +## Viewport, window and scale + +function gr_update_viewport_legend!(viewport_plotarea, sp, leg) + s = sp[:legend] + + xaxis, yaxis = sp[:xaxis], sp[:yaxis] + xmirror = xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) + ymirror = yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) + + if s isa Tuple{<:Real,Symbol} + if s[2] === :outer + (x,y) = gr_legend_pos(sp, leg, viewport_plotarea) # Dry run, to figure out + if x < viewport_plotarea[1] + viewport_plotarea[1] += leg.leftw + leg.textw + leg.rightw + leg.xoffset + !ymirror * gr_axis_width(sp, sp[:yaxis]) + elseif x > viewport_plotarea[2] + viewport_plotarea[2] -= leg.leftw + leg.textw + leg.rightw + leg.xoffset + end + if y < viewport_plotarea[3] + viewport_plotarea[3] += leg.h + leg.dy + leg.yoffset + !xmirror * gr_axis_height(sp, sp[:xaxis]) + elseif y > viewport_plotarea[4] + viewport_plotarea[4] -= leg.h + leg.dy + leg.yoffset + end + end + end + leg_str = string(s) if occursin("outer", leg_str) if occursin("right", leg_str) - viewport_plotarea[2] -= legendw + 0.12 + viewport_plotarea[2] -= leg.leftw + leg.textw + leg.rightw + leg.xoffset elseif occursin("left", leg_str) - viewport_plotarea[1] += legendw + 0.11 + viewport_plotarea[1] += leg.leftw + leg.textw + leg.rightw + leg.xoffset + !ymirror * gr_axis_width(sp, sp[:yaxis]) elseif occursin("top", leg_str) - viewport_plotarea[4] -= legendh + 0.03 + viewport_plotarea[4] -= leg.h + leg.dy + leg.yoffset elseif occursin("bottom", leg_str) - viewport_plotarea[3] += legendh + 0.04 + viewport_plotarea[3] += leg.h + leg.dy + leg.yoffset + !xmirror * gr_axis_height(sp, sp[:xaxis]) end end - if sp[:legend] == :inline + if s === :inline if sp[:yaxis][:mirror] - viewport_plotarea[1] += legendw + viewport_plotarea[1] += leg.w else - viewport_plotarea[2] -= legendw + viewport_plotarea[2] -= leg.w end end +end - # fill in the plot area background - bg = plot_color(sp[:background_color_inside]) - RecipesPipeline.is3d(sp) || gr_fill_viewport(viewport_plotarea, bg) - - # reduced from before... set some flags based on the series in this subplot - # TODO: can these be generic flags? - outside_ticks = false - cbar = GRColorbar() - - draw_axes = sp[:framestyle] != :none - # axes_2d = true - for series in series_list(sp) - st = series[:seriestype] - if st in (:heatmap, :image) - outside_ticks = true - x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) - xy_lims = x[1], x[end], y[1], y[end] - expand_extrema!(sp[:xaxis], x) - expand_extrema!(sp[:yaxis], y) - data_lims = gr_xy_axislims(sp) +function gr_update_viewport_ratio!(viewport_plotarea, sp) + ratio = get_aspect_ratio(sp) + if ratio != :none + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) + if ratio == :equal + ratio = 1 + end + viewport_ratio = (viewport_plotarea[2] - viewport_plotarea[1]) / (viewport_plotarea[4] - viewport_plotarea[3]) + window_ratio = (xmax - xmin) / (ymax - ymin) / ratio + if window_ratio < viewport_ratio + viewport_center = 0.5 * (viewport_plotarea[1] + viewport_plotarea[2]) + viewport_size = (viewport_plotarea[2] - viewport_plotarea[1]) * window_ratio / viewport_ratio + viewport_plotarea[1] = viewport_center - 0.5 * viewport_size + viewport_plotarea[2] = viewport_center + 0.5 * viewport_size + elseif window_ratio > viewport_ratio + viewport_center = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4]) + viewport_size = (viewport_plotarea[4] - viewport_plotarea[3]) * viewport_ratio / window_ratio + viewport_plotarea[3] = viewport_center - 0.5 * viewport_size + viewport_plotarea[4] = viewport_center + 0.5 * viewport_size end - - gr_update_colorbar!(cbar,series) end +end - # set our plot area view - GR.setviewport(viewport_plotarea...) - - # these are the Axis objects, which hold scale, lims, etc - xaxis = sp[:xaxis] - yaxis = sp[:yaxis] - zaxis = sp[:zaxis] - - # set the scale flags and window - xmin, xmax, ymin, ymax = data_lims - scaleop = 0 - xtick, ytick = 1, 1 - if xmax > xmin && ymax > ymin - # NOTE: for log axes, the major_x and major_y - if non-zero (omit labels) - control the minor grid lines (1 = draw 9 minor grid lines, 2 = no minor grid lines) - # NOTE: for log axes, the x_tick and y_tick - if non-zero (omit axes) - only affect the output appearance (1 = nomal, 2 = scientiic notation) - xaxis[:scale] == :log10 && (scaleop |= GR.OPTION_X_LOG) - yaxis[:scale] == :log10 && (scaleop |= GR.OPTION_Y_LOG) - xaxis[:flip] && (scaleop |= GR.OPTION_FLIP_X) - yaxis[:flip] && (scaleop |= GR.OPTION_FLIP_Y) - if scaleop & GR.OPTION_X_LOG == 0 - majorx = 1 #5 - xtick = GR.tick(xmin, xmax) / majorx +function gr_set_window(sp, viewport_plotarea) + if ispolar(sp) + gr_set_viewport_polar(viewport_plotarea) + else + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) + needs_3d = needs_any_3d_axes(sp) + if needs_3d + zmin, zmax = gr_z_axislims(sp) + zok = zmax > zmin else - # log axis - xtick = 2 # scientific notation - majorx = 2 # no minor grid lines - end - if scaleop & GR.OPTION_Y_LOG == 0 - majory = 1 #5 - ytick = GR.tick(ymin, ymax) / majory - else - # log axis - ytick = 2 # scientific notation - majory = 2 # no minor grid lines + zok = true end - # NOTE: setwindow sets the "data coordinate" limits of the current "viewport" - GR.setwindow(xmin, xmax, ymin, ymax) - GR.setscale(scaleop) + scaleop = 0 + if xmax > xmin && ymax > ymin && zok + sp[:xaxis][:scale] == :log10 && (scaleop |= GR.OPTION_X_LOG) + sp[:yaxis][:scale] == :log10 && (scaleop |= GR.OPTION_Y_LOG) + needs_3d && sp[:zaxis][:scale] == :log10 && (scaleop |= GR.OPTION_Z_LOG) + sp[:xaxis][:flip] && (scaleop |= GR.OPTION_FLIP_X) + sp[:yaxis][:flip] && (scaleop |= GR.OPTION_FLIP_Y) + needs_3d && sp[:zaxis][:flip] && (scaleop |= GR.OPTION_FLIP_Z) + # NOTE: setwindow sets the "data coordinate" limits of the current "viewport" + GR.setwindow(xmin, xmax, ymin, ymax) + GR.setscale(scaleop) + end end +end - # draw the axes - gr_set_font(tickfont(xaxis), sp) +function gr_fill_plotarea(sp, viewport_plotarea) + if !RecipesPipeline.is3d(sp) + gr_fill_viewport(viewport_plotarea, plot_color(sp[:background_color_inside])) + end +end + + +## Axes + +function gr_draw_axes(sp, viewport_plotarea) GR.setlinewidth(sp.plt[:thickness_scaling]) if RecipesPipeline.is3d(sp) - zmin, zmax = axis_limits(sp, :z) + # set space + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) + zmin, zmax = gr_z_axislims(sp) GR.setspace(zmin, zmax, round.(Int, sp[:camera])...) - xticks, yticks, zticks, xaxis_segs, yaxis_segs, zaxis_segs, xtick_segs, ytick_segs, ztick_segs, xgrid_segs, ygrid_segs, zgrid_segs, xminorgrid_segs, yminorgrid_segs, zminorgrid_segs, xborder_segs, yborder_segs, zborder_segs = axis_drawing_info_3d(sp) - # fill the plot area - gr_set_fill(sp[:background_color_inside]) + gr_set_fill(plot_color(sp[:background_color_inside])) plot_area_x = [xmin, xmin, xmin, xmax, xmax, xmax, xmin] plot_area_y = [ymin, ymin, ymax, ymax, ymax, ymin, ymin] plot_area_z = [zmin, zmax, zmax, zmax, zmin, zmin, zmin] x_bg, y_bg = RecipesPipeline.unzip(GR.wc3towc.(plot_area_x, plot_area_y, plot_area_z)) GR.fillarea(x_bg, y_bg) - # draw the grid lines - if xaxis[:grid] - gr_set_line( - xaxis[:gridlinewidth], - xaxis[:gridstyle], - xaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(xaxis[:foreground_color_grid], xaxis[:gridalpha]) - gr_polyline3d(coords(xgrid_segs)...) + for letter in (:x, :y, :z) + gr_draw_axis_3d(sp, letter, viewport_plotarea) end - if yaxis[:grid] - gr_set_line( - yaxis[:gridlinewidth], - yaxis[:gridstyle], - yaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(yaxis[:foreground_color_grid], yaxis[:gridalpha]) - gr_polyline3d(coords(ygrid_segs)...) - end - if zaxis[:grid] - gr_set_line( - zaxis[:gridlinewidth], - zaxis[:gridstyle], - zaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(zaxis[:foreground_color_grid], zaxis[:gridalpha]) - gr_polyline3d(coords(zgrid_segs)...) - end - - if xaxis[:minorgrid] - gr_set_line( - xaxis[:minorgridlinewidth], - xaxis[:minorgridstyle], - xaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(xaxis[:foreground_color_minor_grid], xaxis[:minorgridalpha]) - gr_polyline3d(coords(xminorgrid_segs)...) - end - if yaxis[:minorgrid] - gr_set_line( - yaxis[:minorgridlinewidth], - yaxis[:minorgridstyle], - yaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(yaxis[:foreground_color_minor_grid], yaxis[:minorgridalpha]) - gr_polyline3d(coords(yminorgrid_segs)...) - end - if zaxis[:minorgrid] - gr_set_line( - zaxis[:minorgridlinewidth], - zaxis[:minorgridstyle], - zaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(zaxis[:foreground_color_minor_grid], zaxis[:minorgridalpha]) - gr_polyline3d(coords(zminorgrid_segs)...) - end - gr_set_transparency(1.0) - - # axis lines - if xaxis[:showaxis] - gr_set_line(1, :solid, xaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline3d(coords(xaxis_segs)...) - end - if yaxis[:showaxis] - gr_set_line(1, :solid, yaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline3d(coords(yaxis_segs)...) - end - if zaxis[:showaxis] - gr_set_line(1, :solid, zaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline3d(coords(zaxis_segs)...) - end - GR.setclip(1) - - # axis ticks - if xaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, xaxis[:foreground_color_grid], sp) - gr_set_transparency( - xaxis[:foreground_color_grid], - xaxis[:tick_direction] == :out ? xaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, xaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline3d(coords(xtick_segs)...) - end - if yaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, yaxis[:foreground_color_grid], sp) - gr_set_transparency( - yaxis[:foreground_color_grid], - yaxis[:tick_direction] == :out ? yaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, yaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline3d(coords(ytick_segs)...) - end - if zaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, zaxis[:foreground_color_grid], sp) - gr_set_transparency( - zaxis[:foreground_color_grid], - zaxis[:tick_direction] == :out ? zaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, zaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline3d(coords(ztick_segs)...) - end - GR.setclip(1) - - # tick marks - if !(xticks in (:none, nothing, false)) && xaxis[:showaxis] - # x labels - gr_set_font( - tickfont(xaxis), - halign = (:left, :hcenter, :right)[sign(xaxis[:rotation]) + 2], - valign = (xaxis[:mirror] ? :bottom : :top), - rotation = xaxis[:rotation], - color = xaxis[:tickfontcolor], - sp - ) - yt = if sp[:framestyle] == :origin - 0 - elseif xor(xaxis[:mirror], yaxis[:flip]) - ymax - else - ymin - end - zt = if sp[:framestyle] == :origin - 0 - elseif xor(xaxis[:mirror], zaxis[:flip]) - zmax - else - zmin - end - for (cv, dv) in zip(xticks...) - xi, yi = gr_w3tondc(cv, yt, zt) - xi += (yaxis[:mirror] ? 1 : -1) * 1e-2 * (xaxis[:tick_direction] == :out ? 1.5 : 1.0) - yi += (xaxis[:mirror] ? 1 : -1) * 5e-3 * (xaxis[:tick_direction] == :out ? 1.5 : 1.0) - gr_text(xi, yi, gr_tick_label(xaxis, dv)) - end - end - - if !(yticks in (:none, nothing, false)) && yaxis[:showaxis] - # y labels - gr_set_font( - tickfont(yaxis), - halign = (:left, :hcenter, :right)[sign(yaxis[:rotation]) + 2], - valign = (yaxis[:mirror] ? :bottom : :top), - rotation = yaxis[:rotation], - color = yaxis[:tickfontcolor], - sp - ) - xt = if sp[:framestyle] == :origin - 0 - elseif xor(yaxis[:mirror], xaxis[:flip]) - xmin - else - xmax - end - zt = if sp[:framestyle] == :origin - 0 - elseif xor(yaxis[:mirror], zaxis[:flip]) - zmax - else - zmin - end - for (cv, dv) in zip(yticks...) - xi, yi = gr_w3tondc(xt, cv, zt) - gr_text(xi + (yaxis[:mirror] ? -1 : 1) * 1e-2 * (yaxis[:tick_direction] == :out ? 1.5 : 1.0), - yi + (yaxis[:mirror] ? 1 : -1) * 5e-3 * (yaxis[:tick_direction] == :out ? 1.5 : 1.0), - gr_tick_label(yaxis, dv)) - end - end - - if !(zticks in (:none, nothing, false)) && zaxis[:showaxis] - # z labels - gr_set_font( - tickfont(zaxis), - halign = (zaxis[:mirror] ? :left : :right), - valign = (:top, :vcenter, :bottom)[sign(zaxis[:rotation]) + 2], - rotation = zaxis[:rotation], - color = zaxis[:tickfontcolor], - sp - ) - xt = if sp[:framestyle] == :origin - 0 - elseif xor(zaxis[:mirror], xaxis[:flip]) - xmax - else - xmin - end - yt = if sp[:framestyle] == :origin - 0 - elseif xor(zaxis[:mirror], yaxis[:flip]) - ymax - else - ymin - end - for (cv, dv) in zip(zticks...) - xi, yi = gr_w3tondc(xt, yt, cv) - gr_text(xi + (zaxis[:mirror] ? 1 : -1) * 1e-2 * (zaxis[:tick_direction] == :out ? 1.5 : 1.0), - yi, gr_tick_label(zaxis, dv)) - end - end - # - # # border - # intensity = sp[:framestyle] == :semi ? 0.5 : 1.0 - # if sp[:framestyle] in (:box, :semi) - # gr_set_line(intensity, :solid, xaxis[:foreground_color_border], sp) - # gr_set_transparency(xaxis[:foreground_color_border], intensity) - # gr_polyline3d(coords(xborder_segs)...) - # gr_set_line(intensity, :solid, yaxis[:foreground_color_border], sp) - # gr_set_transparency(yaxis[:foreground_color_border], intensity) - # gr_polyline3d(coords(yborder_segs)...) - # end - elseif ispolar(sp) r = gr_set_viewport_polar(viewport_plotarea) #rmin, rmax = GR.adjustrange(ignorenan_minimum(r), ignorenan_maximum(r)) rmin, rmax = axis_limits(sp, :y) gr_polaraxes(rmin, rmax, sp) - - elseif draw_axes - if xmax > xmin && ymax > ymin - GR.setwindow(xmin, xmax, ymin, ymax) - end - - xticks, yticks, xspine_segs, yspine_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xminorgrid_segs, yminorgrid_segs, xborder_segs, yborder_segs = axis_drawing_info(sp) - - # draw the grid lines - if xaxis[:grid] - # GR.grid(xtick, ytick, 0, 0, majorx, majory) - gr_set_line( - xaxis[:gridlinewidth], - xaxis[:gridstyle], - xaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(xaxis[:foreground_color_grid], xaxis[:gridalpha]) - gr_polyline(coords(xgrid_segs)...) - end - if yaxis[:grid] - gr_set_line( - yaxis[:gridlinewidth], - yaxis[:gridstyle], - yaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(yaxis[:foreground_color_grid], yaxis[:gridalpha]) - gr_polyline(coords(ygrid_segs)...) - end - if xaxis[:minorgrid] - gr_set_line( - xaxis[:minorgridlinewidth], - xaxis[:minorgridstyle], - xaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(xaxis[:foreground_color_minor_grid], xaxis[:minorgridalpha]) - gr_polyline(coords(xminorgrid_segs)...) - end - if yaxis[:minorgrid] - gr_set_line( - yaxis[:minorgridlinewidth], - yaxis[:minorgridstyle], - yaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(yaxis[:foreground_color_minor_grid], yaxis[:minorgridalpha]) - gr_polyline(coords(yminorgrid_segs)...) - end - gr_set_transparency(1.0) - - # axis lines - if xaxis[:showaxis] - gr_set_line(1, :solid, xaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline(coords(xspine_segs)...) - end - if yaxis[:showaxis] - gr_set_line(1, :solid, yaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline(coords(yspine_segs)...) - end - GR.setclip(1) - - # axis ticks - if xaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, xaxis[:foreground_color_grid], sp) - gr_set_transparency( - xaxis[:foreground_color_grid], - xaxis[:tick_direction] == :out ? xaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, xaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline(coords(xtick_segs)...) - end - if yaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, yaxis[:foreground_color_grid], sp) - gr_set_transparency( - yaxis[:foreground_color_grid], - yaxis[:tick_direction] == :out ? yaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, yaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline(coords(ytick_segs)...) - end - GR.setclip(1) - - # tick marks - if !(xticks in (:none, nothing, false)) && xaxis[:showaxis] - # x labels - flip, mirror = gr_set_xticks_font(sp) - for (cv, dv) in zip(xticks...) - xi, yi = GR.wctondc(cv, sp[:framestyle] == :origin ? 0 : xor(flip, mirror) ? ymax : ymin) - gr_text(xi, yi + (mirror ? 1 : -1) * 5e-3 * (xaxis[:tick_direction] == :out ? 1.5 : 1.0), - gr_tick_label(xaxis, dv)) - end - end - - if !(yticks in (:none, nothing, false)) && yaxis[:showaxis] - # y labels - flip, mirror = gr_set_yticks_font(sp) - for (cv, dv) in zip(yticks...) - xi, yi = GR.wctondc(sp[:framestyle] == :origin ? 0 : xor(flip, mirror) ? xmax : xmin, cv) - gr_text(xi + (mirror ? 1 : -1) * 1e-2 * (yaxis[:tick_direction] == :out ? 1.5 : 1.0), - yi, - gr_tick_label(yaxis, dv)) - end - end - - # border - intensity = sp[:framestyle] == :semi ? 0.5 : 1 - if sp[:framestyle] in (:box, :semi) - GR.setclip(0) - gr_set_line(intensity, :solid, xaxis[:foreground_color_border], sp) - gr_set_transparency(xaxis[:foreground_color_border], intensity) - gr_polyline(coords(xborder_segs)...) - gr_set_line(intensity, :solid, yaxis[:foreground_color_border], sp) - gr_set_transparency(yaxis[:foreground_color_border], intensity) - gr_polyline(coords(yborder_segs)...) - GR.setclip(1) + elseif sp[:framestyle] != :none + for letter in (:x, :y) + gr_draw_axis(sp, letter, viewport_plotarea) end end - # end +end - # add the guides - GR.savestate() +function gr_draw_axis(sp, letter, viewport_plotarea) + ax = axis_drawing_info(sp, letter) + axis = sp[Symbol(letter, :axis)] + + # draw segments + gr_draw_grid(sp, axis, ax.grid_segments) + gr_draw_minorgrid(sp, axis, ax.minorgrid_segments) + gr_draw_spine(sp, axis, ax.segments) + gr_draw_border(sp, axis, ax.border_segments) + gr_draw_ticks(sp, axis, ax.tick_segments) + + # labels + gr_label_ticks(sp, letter, ax.ticks) + gr_label_axis(sp, letter, viewport_plotarea) +end + +function gr_draw_axis_3d(sp, letter, viewport_plotarea) + ax = axis_drawing_info_3d(sp, letter) + axis = sp[Symbol(letter, :axis)] + + # draw segments + gr_draw_grid(sp, axis, ax.grid_segments, gr_polyline3d) + gr_draw_minorgrid(sp, axis, ax.minorgrid_segments, gr_polyline3d) + gr_draw_spine(sp, axis, ax.segments, gr_polyline3d) + gr_draw_border(sp, axis, ax.border_segments, gr_polyline3d) + gr_draw_ticks(sp, axis, ax.tick_segments, gr_polyline3d) + + # labels + GR.setscale(0) + gr_label_ticks_3d(sp, letter, ax.ticks) + gr_label_axis_3d(sp, letter) + gr_set_window(sp, viewport_plotarea) +end + +function gr_draw_grid(sp, axis, segments, func = gr_polyline) + if axis[:grid] + gr_set_line( + axis[:gridlinewidth], + axis[:gridstyle], + axis[:foreground_color_grid], + sp + ) + gr_set_transparency(axis[:foreground_color_grid], axis[:gridalpha]) + func(coords(segments)...) + end +end + +function gr_draw_minorgrid(sp, axis, segments, func = gr_polyline) + if axis[:grid] + gr_set_line( + axis[:minorgridlinewidth], + axis[:minorgridstyle], + axis[:foreground_color_minor_grid], + sp + ) + gr_set_transparency(axis[:foreground_color_minor_grid], axis[:minorgridalpha]) + func(coords(segments)...) + end +end + +function gr_draw_spine(sp, axis, segments, func = gr_polyline) + if axis[:showaxis] + gr_set_line(1, :solid, axis[:foreground_color_border], sp) + gr_set_transparency(1.0) + GR.setclip(0) + func(coords(segments)...) + GR.setclip(1) + end +end + +function gr_draw_border(sp, axis, segments, func = gr_polyline) + intensity = sp[:framestyle] == :semi ? 0.5 : 1 + if sp[:framestyle] in (:box, :semi) + GR.setclip(0) + gr_set_line(intensity, :solid, axis[:foreground_color_border], sp) + gr_set_transparency(axis[:foreground_color_border], intensity) + func(coords(segments)...) + GR.setclip(1) + end +end + +function gr_draw_ticks(sp, axis, segments, func = gr_polyline) + if axis[:showaxis] + if sp[:framestyle] in (:zerolines, :grid) + gr_set_line(1, :solid, axis[:foreground_color_grid], sp) + gr_set_transparency( + axis[:foreground_color_grid], + axis[:tick_direction] == :out ? axis[:gridalpha] : 0, + ) + else + gr_set_line(1, :solid, axis[:foreground_color_axis], sp) + end + GR.setclip(0) + func(coords(segments)...) + GR.setclip(1) + end +end + +function gr_label_ticks(sp, letter, ticks) + axis = sp[Symbol(letter, :axis)] + isy = letter === :y + oletter = isy ? :x : :y + oaxis = sp[Symbol(oletter, :axis)] + oamin, oamax = axis_limits(sp, oletter) + gr_set_tickfont(sp, letter) + out_factor = ifelse(axis[:tick_direction] === :out, 1.5, 1) + x_offset = isy ? -1.5e-2 * out_factor : 0 + y_offset = isy ? 0 : -8e-3 * out_factor + + ov = sp[:framestyle] == :origin ? 0 : xor(oaxis[:flip], axis[:mirror]) ? oamax : oamin + sgn = axis[:mirror] ? -1 : 1 + for (cv, dv) in zip(ticks...) + x, y = GR.wctondc(reverse_if((cv, ov), isy)...) + gr_text(x + sgn * x_offset, y + sgn * y_offset, dv) + end +end + +function gr_label_ticks(sp, letter, ticks::Nothing) end + +function gr_label_ticks_3d(sp, letter, ticks) + near_letter = letter in (:x, :z) ? :y : :x + far_letter = letter in (:x, :y) ? :z : :x + + ax = sp[Symbol(letter, :axis)] + nax = sp[Symbol(near_letter, :axis)] + fax = sp[Symbol(far_letter, :axis)] + + amin, amax = axis_limits(sp, letter) + namin, namax = axis_limits(sp, near_letter) + famin, famax = axis_limits(sp, far_letter) + n0, n1 = letter === :y ? (namax, namin) : (namin, namax) + + + # find out which axes we are dealing with + i = findfirst(==(letter), (:x, :y, :z)) + letters = axes_shift((:x, :y, :z), 1 - i) + asyms = Symbol.(letters, :axis) + + # get axis objects, ticks and minor ticks + # regardless of the `letter` we now use the convention that `x` in variable names refer to + # the first axesm `y` to the second, etc ... + ylims, zlims = axis_limits.(Ref(sp), letters[2:3]) + xax, yax, zax = getindex.(Ref(sp), asyms) + + gr_set_tickfont(sp, letter) + nt = sp[:framestyle] == :origin ? 0 : ax[:mirror] ? n1 : n0 + ft = sp[:framestyle] == :origin ? 0 : ax[:mirror] ? famax : famin + + xoffset = if letter === :x + (sp[:yaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:xaxis][:tick_direction] == :out ? 1.5 : 1) + elseif letter === :y + (sp[:yaxis][:mirror] ? -1 : 1) * 1e-2 * (sp[:yaxis][:tick_direction] == :out ? 1.5 : 1) + else + (sp[:zaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:zaxis][:tick_direction] == :out ? 1.5 : 1) + end + yoffset = if letter === :x + (sp[:xaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:xaxis][:tick_direction] == :out ? 1.5 : 1) + elseif letter === :y + (sp[:yaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:yaxis][:tick_direction] == :out ? 1.5 : 1) + else + 0 + end + + cvs, dvs = ticks + ax[:flip] && reverse!(cvs) + + for (cv, dv) in zip((cvs, dvs)...) + xi, yi = gr_w3tondc(sort_3d_axes(cv, nt, ft, letter)...) + gr_text(xi + xoffset, yi + yoffset, dv) + end +end + +function gr_label_axis(sp, letter, viewport_plotarea) + axis = sp[Symbol(letter, :axis)] + mirror = axis[:mirror] + # guide + if axis[:guide] != "" + GR.savestate() + gr_set_font(guidefont(axis), sp) + guide_position = axis[:guide_position] + angle = float(axis[:guidefontrotation]) # github.com/JuliaPlots/Plots.jl/issues/3089 + if letter === :y + angle += 180. # default angle = 0. should yield GR.setcharup(-1, 0) i.e. 180° + GR.setcharup(cosd(angle), sind(angle)) + ypos = gr_view_yposition(viewport_plotarea, position(axis[:guidefontvalign])) + yalign = alignment(axis[:guidefontvalign]) + if guide_position === :right || (guide_position == :auto && mirror) + GR.settextalign(yalign, GR.TEXT_VALIGN_BOTTOM) + xpos = viewport_plotarea[2] + 0.03 + mirror * gr_axis_width(sp, axis) + else + GR.settextalign(yalign, GR.TEXT_VALIGN_TOP) + xpos = viewport_plotarea[1] - 0.03 - !mirror * gr_axis_width(sp, axis) + end + else + angle += 90. # default angle = 0. should yield GR.setcharup(0, 1) i.e. 90° + GR.setcharup(cosd(angle), sind(angle)) + xpos = gr_view_xposition(viewport_plotarea, position(axis[:guidefonthalign])) + xalign = alignment(axis[:guidefonthalign]) + if guide_position === :top || (guide_position == :auto && mirror) + GR.settextalign(xalign, GR.TEXT_VALIGN_TOP) + ypos = viewport_plotarea[4] + 0.015 + (mirror ? gr_axis_height(sp, axis) : 0.015) + else + GR.settextalign(xalign, GR.TEXT_VALIGN_BOTTOM) + ypos = viewport_plotarea[3] - 0.015 - (mirror ? 0.015 : gr_axis_height(sp, axis)) + end + end + gr_text(xpos, ypos, axis[:guide]) + GR.restorestate() + end +end + +function gr_label_axis_3d(sp, letter) + ax = sp[Symbol(letter, :axis)] + if ax[:guide] != "" + near_letter = letter in (:x, :z) ? :y : :x + far_letter = letter in (:x, :y) ? :z : :x + + nax = sp[Symbol(near_letter, :axis)] + fax = sp[Symbol(far_letter, :axis)] + + amin, amax = axis_limits(sp, letter) + namin, namax = axis_limits(sp, near_letter) + famin, famax = axis_limits(sp, far_letter) + n0, n1 = letter === :y ? (namax, namin) : (namin, namax) + + GR.savestate() + gr_set_font( + guidefont(ax), + sp, + halign = (:left, :hcenter, :right)[sign(ax[:rotation]) + 2], + valign = ax[:mirror] ? :bottom : :top, + rotation = ax[:rotation], + # color = ax[:guidefontcolor], + ) + ag = (amin + amax) / 2 + ng = ax[:mirror] ? n1 : n0 + fg = ax[:mirror] ? famax : famin + x, y = gr_w3tondc(sort_3d_axes(ag, ng, fg, letter)...) + if letter in (:x, :y) + h = gr_axis_height(sp, ax) + x_offset = letter === :x ? -h : h + y_offset = -h + else + x_offset = -0.03 - gr_axis_width(sp, ax) + y_offset = 0 + end + letter === :z && GR.setcharup(-1, 0) + sgn = ax[:mirror] ? -1 : 1 + gr_text(x + sgn * x_offset, y + sgn * y_offset, ax[:guide]) + GR.restorestate() + end +end + +function gr_add_title(sp, viewport_plotarea, viewport_subplot) if sp[:title] != "" + GR.savestate() gr_set_font(titlefont(sp), sp) loc = sp[:titlelocation] if loc == :left @@ -1491,443 +1621,285 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) end GR.settextalign(halign, GR.TEXT_VALIGN_TOP) gr_text(xpos, viewport_subplot[4], sp[:title]) + GR.restorestate() end - if RecipesPipeline.is3d(sp) - if xaxis[:guide] != "" - gr_set_font( - guidefont(xaxis), - halign = (:left, :hcenter, :right)[sign(xaxis[:rotation]) + 2], - valign = (xaxis[:mirror] ? :bottom : :top), - rotation = xaxis[:rotation], - sp - ) - yg = xor(xaxis[:mirror], yaxis[:flip]) ? ymax : ymin - zg = xor(xaxis[:mirror], zaxis[:flip]) ? zmax : zmin - xg = (xmin + xmax) / 2 - xndc, yndc = gr_w3tondc(xg, yg, zg) - h = gr_axis_height(sp, xaxis) - gr_text(xndc - h, yndc - h, xaxis[:guide]) - end +end - if yaxis[:guide] != "" - gr_set_font( - guidefont(yaxis), - halign = (:left, :hcenter, :right)[sign(yaxis[:rotation]) + 2], - valign = (yaxis[:mirror] ? :bottom : :top), - rotation = yaxis[:rotation], - sp - ) - xg = xor(yaxis[:mirror], xaxis[:flip]) ? xmin : xmax - yg = (ymin + ymax) / 2 - zg = xor(yaxis[:mirror], zaxis[:flip]) ? zmax : zmin - xndc, yndc = gr_w3tondc(xg, yg, zg) - h = gr_axis_height(sp, yaxis) - gr_text(xndc + h, yndc - h, yaxis[:guide]) - end - if zaxis[:guide] != "" - gr_set_font( - guidefont(zaxis), - halign = (:left, :hcenter, :right)[sign(zaxis[:rotation]) + 2], - valign = (zaxis[:mirror] ? :bottom : :top), - rotation = zaxis[:rotation], - sp - ) - xg = xor(zaxis[:mirror], xaxis[:flip]) ? xmax : xmin - yg = xor(zaxis[:mirror], yaxis[:flip]) ? ymax : ymin - zg = (zmin + zmax) / 2 - xndc, yndc = gr_w3tondc(xg, yg, zg) - w = gr_axis_width(sp, zaxis) - GR.setcharup(-1, 0) - gr_text(xndc - w, yndc, zaxis[:guide]) - end - else - if xaxis[:guide] != "" - h = 0.01 + gr_axis_height(sp, xaxis) - gr_set_font(guidefont(xaxis), sp) - if xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) - gr_text(gr_view_xcenter(viewport_plotarea), viewport_plotarea[4] + h, xaxis[:guide]) - else - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) - gr_text(gr_view_xcenter(viewport_plotarea), viewport_plotarea[3] - h, xaxis[:guide]) - end - end +## Series - if yaxis[:guide] != "" - w = 0.02 + gr_axis_width(sp, yaxis) - gr_set_font(guidefont(yaxis), sp) - GR.setcharup(-1, 0) - if yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) - gr_text(viewport_plotarea[2] + w, gr_view_ycenter(viewport_plotarea), yaxis[:guide]) - else - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) - gr_text(viewport_plotarea[1] - w, gr_view_ycenter(viewport_plotarea), yaxis[:guide]) - end +function gr_add_series(sp, series) + st = series[:seriestype] + + # update the current stored gradient + gr_set_gradient(series) + + GR.savestate() + + x, y, z = (handle_surface(series[letter]) for letter in (:x, :y, :z)) + xscale, yscale = sp[:xaxis][:scale], sp[:yaxis][:scale] + frng = series[:fillrange] + + # recompute data + if ispolar(sp) && z === nothing + rmin, rmax = axis_limits(sp, :y) + if frng !== nothing + _, frng = convert_to_polar(x, frng, (rmin, rmax)) end + x, y = convert_to_polar(x, y, (rmin, rmax)) end - GR.restorestate() - gr_set_font(tickfont(xaxis), sp) + clims = gr_clims(sp, series) - # this needs to be here to point the colormap to the right indices - GR.setcolormap(1000 + GR.COLORMAP_COOLWARM) + # add custom frame shapes to markershape? + series_annotations_shapes!(series) + # ------------------------------------------------------- - for (idx, series) in enumerate(series_list(sp)) - st = series[:seriestype] - - # update the current stored gradient - gr_set_gradient(series) - - GR.savestate() - - # update the bounding window - if ispolar(sp) - gr_set_viewport_polar(viewport_plotarea) - else - xmin, xmax, ymin, ymax = data_lims - if xmax > xmin && ymax > ymin - GR.setwindow(xmin, xmax, ymin, ymax) - end - end - - x, y, z = series[:x], series[:y], series[:z] - frng = series[:fillrange] - - clims = get_clims(sp, series) - - # add custom frame shapes to markershape? - series_annotations_shapes!(series) - # ------------------------------------------------------- - - # recompute data - if typeof(z) <: Surface - z = vec(transpose_z(series, z.surf, false)) - elseif ispolar(sp) - if frng !== nothing - _, frng = convert_to_polar(x, frng, (rmin, rmax)) - end - x, y = convert_to_polar(x, y, (rmin, rmax)) - end - - if st == :straightline + # draw the series + if st in (:path, :scatter, :straightline) + if st === :straightline x, y = straightline_data(series) end - - if st in (:path, :scatter, :straightline) - if x !== nothing && length(x) > 1 - lz = series[:line_z] - segments = iter_segments(series) - # do area fill - if frng !== nothing - GR.setfillintstyle(GR.INTSTYLE_SOLID) - fr_from, fr_to = (is_2tuple(frng) ? frng : (y, frng)) - for (i, rng) in enumerate(segments) - fc = get_fillcolor(series, clims, i) - gr_set_fillcolor(fc) - fx = _cycle(x, vcat(rng, reverse(rng))) - fy = vcat(_cycle(fr_from,rng), _cycle(fr_to,reverse(rng))) - gr_set_transparency(fc, get_fillalpha(series, i)) - GR.fillarea(fx, fy) - end - end - - # draw the line(s) - if st in (:path, :straightline) - for (i, rng) in enumerate(segments) - lc = get_linecolor(series, clims, i) - gr_set_line( - get_linewidth(series, i), get_linestyle(series, i), lc, sp - ) - arrowside = isa(series[:arrow], Arrow) ? series[:arrow].side : :none - arrowstyle = isa(series[:arrow], Arrow) ? series[:arrow].style : :simple - gr_set_fillcolor(lc) - gr_set_transparency(lc, get_linealpha(series, i)) - gr_polyline(x[rng], y[rng]; arrowside = arrowside, arrowstyle = arrowstyle) - end - end - end - - if series[:markershape] != :none - gr_draw_markers(series, x, y, clims) - end - - elseif st == :contour - GR.setspace(clims[1], clims[2], 0, 90) - GR.setlinetype(gr_linetype(get_linestyle(series))) - GR.setlinewidth(max(0, get_linewidth(series)) / nominal_size(sp)) - is_lc_black = let black=plot_color(:black) - plot_color(series[:linecolor]) in (black,[black]) - end - h = gr_contour_levels(series, clims) - if series[:fillrange] !== nothing - if series[:fillcolor] != series[:linecolor] && !is_lc_black - @warn("GR: filled contour only supported with black contour lines") - end - GR.contourf(x, y, h, z, series[:contour_labels] == true ? 1 : 0) - else - coff = is_lc_black ? 0 : 1000 - GR.contour(x, y, h, z, coff + (series[:contour_labels] == true ? 1 : 0)) - end - - elseif st in [:surface, :wireframe] - if st == :surface - if length(x) == length(y) == length(z) - GR.trisurface(x, y, z) - else - try - GR.gr3.surface(x, y, z, GR.OPTION_COLORED_MESH) - catch - GR.surface(x, y, z, GR.OPTION_COLORED_MESH) - end - end - else - GR.setfillcolorind(0) - GR.surface(x, y, z, GR.OPTION_FILLED_MESH) - end - - elseif st == :volume - sp[:legend] = :none - GR.gr3.clear() - dmin, dmax = GR.gr3.volume(y.v, 0) - - elseif st == :heatmap - zmin, zmax = clims - fillgrad = _as_gradient(series[:fillcolor]) - if !ispolar(sp) - GR.setspace(clims..., 0, 90) - x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) - w, h = length(x) - 1, length(y) - 1 - if is_uniformly_spaced(x) && is_uniformly_spaced(y) - # For uniformly spaced data use GR.drawimage, which can be - # much faster than GR.nonuniformcellarray, especially for - # pdf output, and also supports alpha values. - # Note that drawimage draws uniformly spaced data correctly - # even on log scales, where it is visually non-uniform. - colors = plot_color.(get(fillgrad, z, clims), series[:fillalpha]) - rgba = gr_color.(colors) - GR.drawimage(first(x), last(x), last(y), first(y), w, h, rgba) - else - if something(series[:fillalpha],1) < 1 - @warn "GR: transparency not supported in non-uniform heatmaps. Alpha values ignored." - end - colors = get(fillgrad, z, clims) - z_normalized = map(c -> c == invisible() ? 256/255 : PlotUtils.getinverse(fillgrad, c), colors) - rgba = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] - GR.nonuniformcellarray(x, y, w, h, rgba) - end - else - phimin, phimax = 0.0, 360.0 # nonuniform polar array is not yet supported in GR.jl - nx, ny = length(series[:x]), length(series[:y]) - xmin, xmax, ymin, ymax = xy_lims - rmax = data_lims[4] - GR.setwindow(-rmax, rmax, -rmax, rmax) - if ymin > 0 - @warn "'ymin[1] > 0' (rmin) is not yet supported." - end - if series[:y][end] != ny - @warn "Right now only the maximum value of y (r) is taken into account." - end - colors = get(fillgrad, z, clims) - z_normalized = map(c -> c == invisible() ? 256/255 : PlotUtils.getinverse(fillgrad.colors, c), colors) - rgba = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] - # GR.polarcellarray(0, 0, phimin, phimax, ymin, ymax, nx, ny, colors) - GR.polarcellarray(0, 0, phimin, phimax, 0, ymax, nx, ny, rgba) - # Right now only the maximum value of y (r) is taken into account. - # This is certainly not perfect but nonuniform polar array is not yet supported in GR.jl - end - - elseif st in (:path3d, :scatter3d) - # draw path - if st == :path3d - if length(x) > 1 - lz = series[:line_z] - segments = iter_segments(series) - for (i, rng) in enumerate(segments) - lc = get_linecolor(series, clims, i) - gr_set_line( - get_linewidth(series, i), get_linestyle(series, i), lc, sp - ) - gr_set_transparency(lc, get_linealpha(series, i)) - GR.polyline3d(x[rng], y[rng], z[rng]) - end - end - end - - # draw markers - if st == :scatter3d || series[:markershape] != :none - x2, y2 = RecipesPipeline.unzip(map(GR.wc3towc, x, y, z)) - gr_draw_markers(series, x2, y2, clims) - end - - elseif st == :shape - x, y = shape_data(series) - for (i,rng) in enumerate(iter_segments(x, y)) - if length(rng) > 1 - # connect to the beginning - rng = vcat(rng, rng[1]) - - # get the segments - xseg, yseg = x[rng], y[rng] - - # draw the interior - fc = get_fillcolor(series, clims, i) - gr_set_fill(fc) - gr_set_transparency(fc, get_fillalpha(series, i)) - GR.fillarea(xseg, yseg) - - # draw the shapes - lc = get_linecolor(series, clims, i) - gr_set_line(get_linewidth(series, i), get_linestyle(series, i), lc, sp) - gr_set_transparency(lc, get_linealpha(series, i)) - GR.polyline(xseg, yseg) - end - end - - - elseif st == :image - z = transpose_z(series, series[:z].surf, true)' - x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(z)) - w, h = size(z) - xmin, xmax = ignorenan_extrema(x) - ymin, ymax = ignorenan_extrema(y) - rgba = gr_color.(z) - GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba) + gr_draw_segments(series, x, y, frng, clims) + if series[:markershape] !== :none + gr_draw_markers(series, x, y, clims) end - - # this is all we need to add the series_annotations text - anns = series[:series_annotations] - for (xi,yi,str,fnt) in EachAnn(anns, x, y) - gr_set_font(fnt, sp) - gr_text(GR.wctondc(xi, yi)..., str) + elseif st === :shape + gr_draw_shapes(series, clims) + elseif st in (:path3d, :scatter3d) + gr_draw_segments_3d(series, x, y, z, clims) + if st === :scatter3d || series[:markershape] !== :none + # TODO: Do we need to transform to 2d coordinates here? + x2, y2 = RecipesPipeline.unzip(map(GR.wc3towc, x, y, z)) + gr_draw_markers(series, x2, y2, clims) end - - if sp[:legend] == :inline && should_add_to_legend(series) - gr_set_font(legendfont(sp), sp) - gr_set_textcolor(plot_color(sp[:legendfontcolor])) - if sp[:yaxis][:mirror] - (_,i) = sp[:xaxis][:flip] ? findmax(x) : findmin(x) - GR.settextalign(GR.TEXT_HALIGN_RIGHT, GR.TEXT_VALIGN_HALF) - offset = -0.01 - else - (_,i) = sp[:xaxis][:flip] ? findmin(x) : findmax(x) - GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) - offset = 0.01 - end - (x_l,y_l) = GR.wctondc(x[i],y[i]) - gr_text(x_l+offset,y_l,series[:label]) - end - GR.restorestate() + elseif st === :contour + gr_draw_contour(series, x, y, z, clims) + elseif st in (:surface, :wireframe) + gr_draw_surface(series, x, y, z, clims) + elseif st === :volume + sp[:legend] = :none + GR.gr3.clear() + dmin, dmax = GR.gr3.volume(y.v, 0) + elseif st === :heatmap + # `z` is already transposed, so we need to reverse before passing its size. + x, y = heatmap_edges(x, xscale, y, yscale, reverse(size(z)), ispolar(series)) + gr_draw_heatmap(series, x, y, z, clims) + elseif st === :image + gr_draw_image(series, x, y, z, clims) end - # draw the colorbar - hascolorbar(sp) && gr_draw_colorbar(cbar, sp, get_clims(sp), viewport_plotarea) + # this is all we need to add the series_annotations text + anns = series[:series_annotations] + for (xi,yi,str,fnt) in EachAnn(anns, x, y) + gr_set_font(fnt, sp) + gr_text(GR.wctondc(xi, yi)..., str) + end - # add the legend - if !(sp[:legend] in(:none, :inline)) - GR.savestate() - GR.selntran(0) - GR.setscale(0) + if sp[:legend] == :inline && should_add_to_legend(series) gr_set_font(legendfont(sp), sp) - w = legendw - n = legendn - if w > 0 - dy = gr_point_mult(sp) * sp[:legendfontsize] * 1.75 - h = dy*n - xpos, ypos = gr_legend_pos(sp, w, h, viewport_plotarea) - GR.setfillintstyle(GR.INTSTYLE_SOLID) - gr_set_fillcolor(sp[:background_color_legend]) - GR.fillrect(xpos - 0.08, xpos + w + 0.02, ypos + dy, ypos - dy * n) - gr_set_line(1, :solid, sp[:foreground_color_legend], sp) - GR.drawrect(xpos - 0.08, xpos + w + 0.02, ypos + dy, ypos - dy * n) - i = 0 - if sp[:legendtitle] !== nothing - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_HALF) - gr_set_font(legendtitlefont(sp), sp) - gr_text(xpos - 0.03 + 0.5*w, ypos, string(sp[:legendtitle])) - ypos -= dy - gr_set_font(legendfont(sp), sp) - end - for series in series_list(sp) - clims = get_clims(sp, series) - should_add_to_legend(series) || continue - st = series[:seriestype] - lc = get_linecolor(series, clims) - gr_set_line(sp[:legendfontsize] / 8, get_linestyle(series), lc, sp) - - if (st == :shape || series[:fillrange] !== nothing) && series[:ribbon] === nothing - fc = get_fillcolor(series, clims) - gr_set_fill(fc) #, series[:fillalpha]) - l, r = xpos-0.07, xpos-0.01 - b, t = ypos-0.4dy, ypos+0.4dy - x = [l, r, r, l, l] - y = [b, b, t, t, b] - gr_set_transparency(fc, get_fillalpha(series)) - gr_polyline(x, y, GR.fillarea) - lc = get_linecolor(series, clims) - gr_set_transparency(lc, get_linealpha(series)) - gr_set_line(get_linewidth(series), get_linestyle(series), lc, sp) - st == :shape && gr_polyline(x, y) - end - - if st in (:path, :straightline) - gr_set_transparency(lc, get_linealpha(series)) - if series[:fillrange] === nothing || series[:ribbon] !== nothing - GR.polyline([xpos - 0.07, xpos - 0.01], [ypos, ypos]) - else - GR.polyline([xpos - 0.07, xpos - 0.01], [ypos+0.4dy, ypos+0.4dy]) - end - end - - if series[:markershape] != :none - ms = first(series[:markersize]) - msw = first(series[:markerstrokewidth]) - s, sw = if ms > 0 - 0.8 * sp[:legendfontsize], 0.8 * sp[:legendfontsize] * msw / ms - else - 0, 0.8 * sp[:legendfontsize] * msw / 8 - end - gr_draw_markers( - series, xpos - 0.035, ypos, clims, s, sw - ) - end - - lab = series[:label] - GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) - gr_set_textcolor(plot_color(sp[:legendfontcolor])) - gr_text(xpos, ypos, string(lab)) - ypos -= dy - end - end - GR.selntran(1) - GR.restorestate() - end - - # add annotations - GR.savestate() - # update the bounding window - if ispolar(sp) - gr_set_viewport_polar(viewport_plotarea) - else - xmin, xmax, ymin, ymax = data_lims - if xmax > xmin && ymax > ymin - GR.setwindow(xmin, xmax, ymin, ymax) - end - end - for ann in sp[:annotations] - x, y, val = locate_annotation(sp, ann...) - x, y = if RecipesPipeline.is3d(sp) - gr_w3tondc(x, y, z) + gr_set_textcolor(plot_color(sp[:legendfontcolor])) + if sp[:yaxis][:mirror] + (_,i) = sp[:xaxis][:flip] ? findmax(x) : findmin(x) + GR.settextalign(GR.TEXT_HALIGN_RIGHT, GR.TEXT_VALIGN_HALF) + offset = -0.01 else - GR.wctondc(x, y) + (_,i) = sp[:xaxis][:flip] ? findmin(x) : findmax(x) + GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) + offset = 0.01 end - gr_set_font(val.font, sp) - gr_text(x, y, val.str) + (x_l,y_l) = GR.wctondc(x[i],y[i]) + gr_text(x_l+offset,y_l,series[:label]) end GR.restorestate() end +function gr_draw_segments(series, x, y, fillrange, clims) + st = series[:seriestype] + if x !== nothing && length(x) > 1 + segments = series_segments(series, st) + # do area fill + if fillrange !== nothing + GR.setfillintstyle(GR.INTSTYLE_SOLID) + fr_from, fr_to = (is_2tuple(fillrange) ? fillrange : (y, fillrange)) + for segment in segments + i, rng = segment.attr_index, segment.range + fc = get_fillcolor(series, clims, i) + gr_set_fillcolor(fc) + fx = _cycle(x, vcat(rng, reverse(rng))) + fy = vcat(_cycle(fr_from, rng), _cycle(fr_to, reverse(rng))) + gr_set_transparency(fc, get_fillalpha(series, i)) + GR.fillarea(fx, fy) + end + end + + # draw the line(s) + if st in (:path, :straightline) + for segment in segments + i, rng = segment.attr_index, segment.range + lc = get_linecolor(series, clims, i) + gr_set_line( + get_linewidth(series, i), get_linestyle(series, i), lc, series + ) + arrowside = isa(series[:arrow], Arrow) ? series[:arrow].side : :none + arrowstyle = isa(series[:arrow], Arrow) ? series[:arrow].style : :simple + gr_set_fillcolor(lc) + gr_set_transparency(lc, get_linealpha(series, i)) + gr_polyline(x[rng], y[rng]; arrowside = arrowside, arrowstyle = arrowstyle) + end + end + end +end + +function gr_draw_segments_3d(series, x, y, z, clims) + if series[:seriestype] === :path3d && length(x) > 1 + lz = series[:line_z] + segments = series_segments(series, :path3d) + for segment in segments + i, rng = segment.attr_index, segment.range + lc = get_linecolor(series, clims, i) + gr_set_line( + get_linewidth(series, i), get_linestyle(series, i), lc, series + ) + gr_set_transparency(lc, get_linealpha(series, i)) + GR.polyline3d(x[rng], y[rng], z[rng]) + end + end +end + +function gr_draw_markers( + series::Series, + x, + y, + clims, + msize = series[:markersize], + strokewidth = series[:markerstrokewidth], +) + + isempty(x) && return + GR.setfillintstyle(GR.INTSTYLE_SOLID) + + shapes = series[:markershape] + if shapes != :none + for segment in series_segments(series, :scatter) + i = segment.attr_index + rng = intersect(eachindex(x), segment.range) + if !isempty(rng) + ms = get_thickness_scaling(series) * _cycle(msize, i) + msw = get_thickness_scaling(series) * _cycle(strokewidth, i) + shape = _cycle(shapes, i) + for j in rng + gr_draw_marker(series, _cycle(x, j), _cycle(y, j), clims, i, ms, msw, shape) + end + end + end + end +end + +function gr_draw_shapes(series, clims) + x, y = shape_data(series) + for segment in series_segments(series, :shape) + i, rng = segment.attr_index, segment.range + if length(rng) > 1 + # connect to the beginning + rng = vcat(rng, rng[1]) + + # get the segments + xseg, yseg = x[rng], y[rng] + + # draw the interior + fc = get_fillcolor(series, clims, i) + gr_set_fill(fc) + gr_set_transparency(fc, get_fillalpha(series, i)) + GR.fillarea(xseg, yseg) + + # draw the shapes + lc = get_linecolor(series, clims, i) + gr_set_line(get_linewidth(series, i), get_linestyle(series, i), lc, series) + gr_set_transparency(lc, get_linealpha(series, i)) + GR.polyline(xseg, yseg) + end + end +end + +function gr_draw_contour(series, x, y, z, clims) + GR.setspace(clims[1], clims[2], 0, 90) + gr_set_line(get_linewidth(series), get_linestyle(series), get_linecolor(series), series) + is_lc_black = let black=plot_color(:black) + plot_color(series[:linecolor]) in (black,[black]) + end + h = gr_contour_levels(series, clims) + if series[:fillrange] !== nothing + if series[:fillcolor] != series[:linecolor] && !is_lc_black + @warn("GR: filled contour only supported with black contour lines") + end + GR.contourf(x, y, h, z, series[:contour_labels] == true ? 1 : 0) + else + coff = is_lc_black ? 0 : 1000 + GR.contour(x, y, h, z, coff + (series[:contour_labels] == true ? 1 : 0)) + end +end + +function gr_draw_surface(series, x, y, z, clims) + + if series[:seriestype] === :surface + fillalpha = get_fillalpha(series) + fillcolor = get_fillcolor(series) + if length(x) == length(y) == length(z) + x, y, z = GR.gridit(x, y, z, 200, 200) + end + if (!isnothing(fillalpha) && fillalpha < 1) || alpha(first(fillcolor)) < 1 + gr_set_transparency(fillcolor, fillalpha) + GR.surface(x, y, z, GR.OPTION_COLORED_MESH) + else + GR.gr3.surface(x, y, z, GR.OPTION_COLORED_MESH) + end + else # wireframe + GR.setfillcolorind(0) + GR.surface(x, y, z, GR.OPTION_FILLED_MESH) + end +end + +function gr_draw_heatmap(series, x, y, z, clims) + fillgrad = _as_gradient(series[:fillcolor]) + GR.setspace(clims..., 0, 90) + w, h = length(x) - 1, length(y) - 1 + if !ispolar(series) && is_uniformly_spaced(x) && is_uniformly_spaced(y) + # For uniformly spaced data use GR.drawimage, which can be + # much faster than GR.nonuniformcellarray, especially for + # pdf output, and also supports alpha values. + # Note that drawimage draws uniformly spaced data correctly + # even on log scales, where it is visually non-uniform. + colors = plot_color.(get(fillgrad, z, clims), series[:fillalpha]) + rgba = gr_color.(colors) + GR.drawimage(first(x), last(x), last(y), first(y), w, h, rgba) + else + if something(series[:fillalpha], 1) < 1 + @warn "GR: transparency not supported in non-uniform heatmaps. Alpha values ignored." + end + z_normalized = get_z_normalized.(z, clims...) + rgba = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] + if !ispolar(series) + GR.nonuniformcellarray(x, y, w, h, rgba) + else + if y[1] < 0 + @warn "'y[1] < 0' (rmin) is not yet supported." + end + xmin, xmax, ymin, ymax = gr_xy_axislims(series[:subplot]) + GR.setwindow(-ymax, ymax, -ymax, ymax) + GR.nonuniformpolarcellarray(rad2deg.(x), y, w, h, rgba) + end + end +end + +function gr_draw_image(series, x, y, z, clims) + w, h = size(z) + xmin, xmax = ignorenan_extrema(x) + ymin, ymax = ignorenan_extrema(y) + rgba = gr_color.(z) + GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba) +end + # ---------------------------------------------------------------- diff --git a/src/backends/hdf5.jl b/src/backends/hdf5.jl index 1dceedc0..050992aa 100644 --- a/src/backends/hdf5.jl +++ b/src/backends/hdf5.jl @@ -27,33 +27,74 @@ Read from .hdf5 file using: - Should have some form of backward compatibility. - Should be reliable for archival purposes. 5. Fix construction of plot object with hdf5plot_read. + - Layout doesn't seem to get transferred well (ex: `Plots._examples[40]`). - Not building object correctly when backends do not natively support a certain feature (ex: :steppre) + - No support for CategoricalArrays.* structures. But they appear to be + brought into `Plots._examples[25,30]` through DataFrames.jl - so we can't + really reference them in this code. ==# +""" + _hdf5_implementation -import FixedPointNumbers: N0f8 #In core Julia +Create module (namespace) for implementing HDF5 "plots". +(Avoid name collisions, while keeping names short) +""" +module _hdf5_implementation #Tools required to implements HDF5 "plots" + +import Dates + +#Plots.jl imports HDF5 to main: +import ..HDF5 +import ..HDF5: Group, Dataset + +import ..Colors, ..Colorant +import ..PlotUtils.ColorSchemes.ColorScheme + +import ..HDF5Backend +import ..HDF5PLOT_MAP_STR2TELEM, ..HDF5PLOT_MAP_TELEM2STR +import ..HDF5Plot_PlotRef, ..HDF5PLOT_PLOTREF +import ..BoundingBox, ..Extrema, ..Length +import ..RecipesPipeline.datetimeformatter +import ..PlotUtils.ColorPalette, ..PlotUtils.CategoricalColorGradient, ..PlotUtils.ContinuousColorGradient +import ..Surface, ..Shape, ..Arrow +import ..GridLayout, ..RootLayout +import ..Font, ..PlotText, ..SeriesAnnotations +import ..Axis, ..Subplot, ..Plot +import ..AKW, ..KW, ..DefaultsDict +import .._axis_defaults +import ..plot, ..plot! + +#Types that already have built-in HDF5 support (just write out natively): +const HDF5_SupportedTypes = Union{Number, String} +#TODO: Types_HDF5Support #Dispatch types: -struct HDF5PlotNative; end #Indentifies a data element that can natively be handled by HDF5 -struct HDF5CTuple; end #Identifies a "complex" tuple structure +struct CplxTuple; end #Identifies a "complex" tuple structure (not merely numbers) +#HDF5 reader will auto-detect type correctly: +struct HDF5_AutoDetect; end #See HDF5_SupportedTypes + #== ===============================================================================# -is_marker_supported(::HDF5Backend, shape::Shape) = true if length(HDF5PLOT_MAP_TELEM2STR) < 1 #Possible element types of high-level data types: - telem2str = Dict{String, Type}( - "NATIVE" => HDF5PlotNative, - "VOID" => Nothing, - "BOOL" => Bool, + #(Used to add type information as an HDF5 string attribute) + #(Also used to dispatch appropriate read function through _read_typed()) + _telem2str = Dict{String, Type}( + "NOTHING" => Nothing, "SYMBOL" => Symbol, + "RGBA" => Colorant, #Write out any Colorant to an #RRGGBBAA string "TUPLE" => Tuple, - "CTUPLE" => HDF5CTuple, #Tuple of complex structures - "RGBA" => ARGB{N0f8}, + "CTUPLE" => CplxTuple, #Tuple of complex structures + "EXTREMA" => Extrema, "LENGTH" => Length, - "ARRAY" => Array, #Dict won't allow Array to be key in HDF5PLOT_MAP_TELEM2STR + "ARRAY" => Array, #Array{Any} (because Array{T<:Union{Number, String}} natively supported by HDF5) + + #Sub-structure types: + "T_DATETIMEFORMATTER" => typeof(datetimeformatter), #Sub-structure types: "DEFAULTSDICT" => DefaultsDict, @@ -64,13 +105,16 @@ if length(HDF5PLOT_MAP_TELEM2STR) < 1 "SERIESANNOTATIONS" => SeriesAnnotations, "PLOTTEXT" => PlotText, "SHAPE" => Shape, - "COLORGRADIENT" => ColorGradient, + "ARROW" => Arrow, + "COLORSCHEME" => ColorScheme, + "COLORPALETTE" => ColorPalette, + "CONT_COLORGRADIENT" => ContinuousColorGradient, + "CAT_COLORGRADIENT" => CategoricalColorGradient, "AXIS" => Axis, "SURFACE" => Surface, "SUBPLOT" => Subplot, - "NULLABLE" => Union{Nothing, T} where T, ) - merge!(HDF5PLOT_MAP_STR2TELEM, telem2str) + merge!(HDF5PLOT_MAP_STR2TELEM, _telem2str) #Faster to create than push!()?? merge!(HDF5PLOT_MAP_TELEM2STR, Dict{Type, String}(v=>k for (k,v) in HDF5PLOT_MAP_STR2TELEM)) end @@ -78,10 +122,22 @@ end #==Helper functions ===============================================================================# -_hdf5_plotelempath(subpath::String) = "plot/$subpath" -_hdf5_datapath(subpath::String) = "data/$subpath" -_hdf5_map_str2telem(k::String) = HDF5PLOT_MAP_STR2TELEM[k] -_hdf5_map_str2telem(v::Vector) = HDF5PLOT_MAP_STR2TELEM[v[1]] +h5plotpath(plotname::String) = "plots/$plotname" + +#Version info +#NOTE: could cache output, but we seem to not want const declarations in backend files. +function _get_Plots_versionstr() + #Adds to load up time... Maybe a more efficient way?? + try + deps = Pkg.dependencies() + uuid = Base.UUID("91a5bcdd-55d7-5caf-9e0b-520d859cae80") #Plots.jl + vinfo = deps[uuid].version + return "Source: Plots.jl v$vinfo" + catch + now = string(Dates.now()) #Use time in case it can help recover plot + return "Source: Plots.jl v? - $now" + end +end function _hdf5_merge!(dest::AKW, src::AKW) for (k, v) in src @@ -94,10 +150,423 @@ function _hdf5_merge!(dest::AKW, src::AKW) return end +#_type_for_map returns the type to use with HDF5PLOT_MAP_TELEM2STR[], in case it is not concrete: +_type_for_map(::Type{T}) where T = T #Catch-all +_type_for_map(::Type{T}) where T<:BoundingBox = BoundingBox +_type_for_map(::Type{T}) where T<:ColorScheme = ColorScheme +_type_for_map(::Type{T}) where T<:Surface = Surface -#== + +#==Read/write things like type name in attributes +===============================================================================# +function _write_datatype_attr(ds::Union{Group, Dataset}, ::Type{T}) where T + typestr = HDF5PLOT_MAP_TELEM2STR[T] + HDF5.attributes(ds)["TYPE"] = typestr +end +function _read_datatype_attr(ds::Union{Group, Dataset}) + if !Base.haskey(HDF5.attributes(ds), "TYPE") + return HDF5_AutoDetect + end + + typestr = HDF5.read(HDF5.attributes(ds)["TYPE"]) + return HDF5PLOT_MAP_STR2TELEM[typestr] +end + +#Type parameter attributes: +function _write_typeparam_attr(ds::Dataset, v::Length{T}) where T + HDF5.attributes(ds)["TYPEPARAM"] = string(T) #Need to add units for Length +end +_read_typeparam_attr(ds::Dataset) = HDF5.read(HDF5.attributes(ds)["TYPEPARAM"]) + +function _write_length_attr(grp::Group, v::Vector) #of a vector + HDF5.attributes(grp)["LENGTH"] = length(v) +end +_read_length_attr(::Type{Vector}, grp::Group) = HDF5.read(HDF5.attributes(grp)["LENGTH"]) + +function _write_size_attr(grp::Group, v::Array) #of an array + HDF5.attributes(grp)["SIZE"] = [size(v)...] +end +_read_size_attr(::Type{Array}, grp::Group) = tuple(HDF5.read(HDF5.attributes(grp)["SIZE"])...) + + +#==_write_typed(): Simple (leaf) datatypes. (Labels with type name.) +===============================================================================# +#= No: write out struct instead! +function _write_typed(grp::Group, name::String, v::T) where T + tstr = string(T) + path = HDF5.name(grp) * "/" * name + @info("Type not supported: $tstr\npath: $path") + return +end +=# +#Default behaviour: Assumes value is supported by HDF5 format +function _write_typed(grp::Group, name::String, v::HDF5_SupportedTypes) + grp[name] = v + return #No need to _write_datatype_attr +end +function _write_typed(grp::Group, name::String, v::Nothing) + grp[name] = "nothing" #Redundancy check/easier to read HDF5 file + _write_datatype_attr(grp[name], Nothing) +end +function _write_typed(grp::Group, name::String, v::Symbol) + grp[name] = String(v) + _write_datatype_attr(grp[name], Symbol) +end +function _write_typed(grp::Group, name::String, v::Colorant) + vstr = "#" * Colors.hex(v, :RRGGBBAA) + grp[name] = vstr + _write_datatype_attr(grp[name], Colorant) +end +function _write_typed(grp::Group, name::String, v::Extrema) + grp[name] = [v.emin, v.emax] #More compact than writing struct + _write_datatype_attr(grp[name], Extrema) +end +function _write_typed(grp::Group, name::String, v::Length) + grp[name] = v.value + _write_datatype_attr(grp[name], Length) + _write_typeparam_attr(grp[name], v) +end +function _write_typed(grp::Group, name::String, v::typeof(datetimeformatter)) + grp[name] = string(v) #Just write something that helps reader + _write_datatype_attr(grp[name], typeof(datetimeformatter)) +end +function _write_typed(grp::Group, name::String, v::Array{T}) where T<:Number #Default for arrays + grp[name] = v + return #No need to _write_datatype_attr +end +function _write_typed(grp::Group, name::String, v::AbstractRange) + _write_typed(grp, name, collect(v)) #For now +end + + + +#== Helper functions for writing complex data structures ===============================================================================# +#Write an array using HDF5 hierarchy (when not using simple numeric eltype): +function _write_harray(grp::Group, name::String, v::Array) + sgrp = HDF5.create_group(grp, name) + sz = size(v) + lidx = LinearIndices(sz) + + for iter in eachindex(v) + coord = lidx[iter] + elem = v[iter] + idxstr = join(coord, "_") + _write_typed(sgrp, "v$idxstr", elem) + end + + _write_size_attr(sgrp, v) +end + +#Write Dict without tagging with type: +function _write(grp::Group, name::String, d::AbstractDict) + sgrp = HDF5.create_group(grp, name) + for (k, v) in d + kstr = string(k) + _write_typed(sgrp, kstr, v) + end + return +end + +#Write out arbitrary `struct`s: +function _writestructgeneric(grp::Group, obj::T) where T + for fname in fieldnames(T) + v = getfield(obj, fname) + _write_typed(grp, String(fname), v) + end + return +end + + +#==_write_typed(): More complex structures. (Labels with type name.) +===============================================================================# + +#Catch-all (default behaviour for `struct`s): +function _write_typed(grp::Group, name::String, v::T) where T + #NOTE: need "name" parameter so that call signature is same with built-ins + MT = _type_for_map(T) + try #Check to see if type is supported + typestr = HDF5PLOT_MAP_TELEM2STR[MT] + catch + @warn("HDF5Plots does not yet support structs of type `$MT`\n\n$grp") + return + end + + #If attribute is supported and no writer is defined, then this should work: + objgrp = HDF5.create_group(grp, name) + _write_datatype_attr(objgrp, MT) + _writestructgeneric(objgrp, v) +end + +function _write_typed(grp::Group, name::String, v::Array{T}) where T + _write_harray(grp, name, v) + _write_datatype_attr(grp[name], Array) #{Any} +end + +function _write_typed(grp::Group, name::String, v::Tuple, ::Type{ELT}) where ELT<: Number #Basic Tuple + _write_typed(grp, name, [v...]) + _write_datatype_attr(grp[name], Tuple) +end +function _write_typed(grp::Group, name::String, v::Tuple, ::Type) #CplxTuple + _write_harray(grp, name, [v...]) + _write_datatype_attr(grp[name], CplxTuple) +end +_write_typed(grp::Group, name::String, v::Tuple) = _write_typed(grp, name, v, eltype(v)) + +function _write_typed(grp::Group, name::String, v::Dict) +#= + tstr = string(Dict) + path = HDF5.name(grp) * "/" * name + @info("Type not supported: $tstr\npath: $path") + return +=# + #No support for structures with Dicts yet +end +function _write_typed(grp::Group, name::String, d::DefaultsDict) #Typically for plot attributes + _write(grp, name, d) + _write_datatype_attr(grp[name], DefaultsDict) +end + +function _write_typed(grp::Group, name::String, v::Axis) + sgrp = HDF5.create_group(grp, name) + #Ignore: sps::Vector{Subplot} + _write_typed(sgrp, "plotattributes", v.plotattributes) + _write_datatype_attr(sgrp, Axis) +end + +function _write_typed(grp::Group, name::String, v::Subplot) + #Not for use in main "Plot.subplots[]" hierarchy. Just establishes reference with subplot_index. + sgrp = HDF5.create_group(grp, name) + _write_typed(sgrp, "index", v[:subplot_index]) + _write_datatype_attr(sgrp, Subplot) + return +end + +function _write_typed(grp::Group, name::String, v::Plot) + #Don't write plot references +end + + +#==_write(): Write out more complex structures +NOTE: No need to write out type information (inferred from hierarchy) +===============================================================================# + +function _write(grp::Group, sp::Subplot{HDF5Backend}) + _write_typed(grp, "attr", sp.attr) + + listgrp = HDF5.create_group(grp, "series_list") + _write_length_attr(listgrp, sp.series_list) + for (i, series) in enumerate(sp.series_list) + #Just write .plotattributes part: + _write(listgrp, "$i", series.plotattributes) + end + + return +end + +function _write(grp::Group, plt::Plot{HDF5Backend}) + _write_typed(grp, "attr", plt.attr) + + listgrp = HDF5.create_group(grp, "subplots") + _write_length_attr(listgrp, plt.subplots) + for (i, sp) in enumerate(plt.subplots) + sgrp = HDF5.create_group(listgrp, "$i") + _write(sgrp, sp) + end + + return +end + +function hdf5plot_write(plt::Plot{HDF5Backend}, path::AbstractString; name::String="_unnamed") + HDF5.h5open(path, "w") do file + HDF5.write_dataset(file, "VERSION_INFO", _get_Plots_versionstr()) + grp = HDF5.create_group(file, h5plotpath(name)) + _write(grp, plt) + end +end + + +#== _read(): Read data, but not type information. +===============================================================================# + +#Types with built-in HDF5 support: +_read(::Type{HDF5_AutoDetect}, ds::Dataset) = HDF5.read(ds) + +function _read(::Type{Nothing}, ds::Dataset) + nstr = "nothing" + v = HDF5.read(ds) + if nstr != v + path = HDF5.name(ds) + throw(Meta.ParseError("_read(::Nothing, ::Group): Read $v != $nstr:\n$path")) + end + return nothing +end +_read(::Type{Symbol}, ds::Dataset) = Symbol(HDF5.read(ds)) +_read(::Type{Colorant}, ds::Dataset) = parse(Colorant, HDF5.read(ds)) +_read(::Type{Tuple}, ds::Dataset) = tuple(HDF5.read(ds)...) +function _read(::Type{Extrema}, ds::Dataset) + v = HDF5.read(ds) + return Extrema(v[1], v[2]) +end +function _read(::Type{Length}, ds::Dataset) + TUNIT = Symbol(_read_typeparam_attr(ds)) + v = HDF5.read(ds) + T = typeof(v) + return Length{TUNIT,T}(v) +end +_read(::Type{typeof(datetimeformatter)}, ds::Dataset) = datetimeformatter + + +#== Helper functions for reading in complex data structures +===============================================================================# + +#When type is unknown, _read_typed() figures it out: +function _read_typed(grp::Group, name::String) + ds = grp[name] + t = _read_datatype_attr(ds) + return _read(t, ds) +end + +#_readstructgeneric: Needs object values to be written out with _write_typed(): +function _readstructgeneric(::Type{T}, grp::Group) where T + vlist = Array{Any}(nothing, fieldcount(T)) + for (i, fname) in enumerate(fieldnames(T)) + vlist[i] = _read_typed(grp, String(fname)) + end + return T(vlist...) +end + +#Read KW from group: +function _read(::Type{KW}, grp::Group) + d = KW() + gkeys = keys(grp) + for k in gkeys + try + v = _read_typed(grp, k) + d[Symbol(k)] = v + catch e + @show e + @show grp + @warn("Could not read field $k") + end + end + return d +end + + +#== _read(): More complex structures. +===============================================================================# + +#Catch-all (default behaviour for `struct`s): +_read(T::Type, grp::Group) = _readstructgeneric(T, grp) + +function _read(::Type{Array}, grp::Group) #Array{Any} + sz = _read_size_attr(Array, grp) + if tuple(0) == sz; return []; end + result = Array{Any}(undef, sz) + lidx = LinearIndices(sz) + + for iter in eachindex(result) + coord = lidx[iter] + idxstr = join(coord, "_") + result[iter] = _read_typed(grp, "v$idxstr") + end + + #Hack: Implicitly make Julia detect element type. + # (Should probably write it explicitly to file) + result = [elem for elem in result] #Potentially make more specific + return reshape(result, sz) +end + +_read(::Type{CplxTuple}, grp::Group) = tuple(_read(Array, grp)...) + +function _read(::Type{GridLayout}, grp::Group) + #parent = _read_typed(grp, "parent") #Can't use generic reader + parent = RootLayout() #TODO: support parent??? + minpad = _read_typed(grp, "minpad") + bbox = _read_typed(grp, "bbox") + grid = _read_typed(grp, "grid") + widths = _read_typed(grp, "widths") + heights = _read_typed(grp, "heights") + attr = KW() #TODO support attr: _read_typed(grp, "attr") + + return GridLayout(parent, minpad, bbox, grid, widths, heights, attr) +end +#Defaults depends on context. So: user must constructs with defaults, then read. +function _read(::Type{DefaultsDict}, grp::Group) + #User should set DefaultsDict.defaults to one of: + # _plot_defaults, _subplot_defaults, _axis_defaults, _series_defaults + path = HDF5.name(ds) + @warn("Cannot yet read DefaultsDict using _read_typed():\n $path\nCannot fully reconstruct plot.") +end +function _read(::Type{Axis}, grp::Group) + #1st arg appears to be ref to subplots. Seems to work without it. + return Axis([], DefaultsDict(_read(KW, grp["plotattributes"]), _axis_defaults)) +end +function _read(::Type{Subplot}, grp::Group) + #Not for use in main "Plot.subplots[]" hierarchy. Just establishes reference with subplot_index. + idx = _read_typed(grp, "index") + return HDF5PLOT_PLOTREF.ref.subplots[idx] +end + + +#== _read(): Main plot structures +===============================================================================# + +function _read(grp::Group, sp::Subplot) + listgrp = HDF5.open_group(grp, "series_list") + nseries = _read_length_attr(Vector, listgrp) + + for i in 1:nseries + sgrp = HDF5.open_group(listgrp, "$i") + seriesinfo = _read(KW, sgrp) + + plot!(sp, seriesinfo[:x], seriesinfo[:y]) #Add data & create data structures + _hdf5_merge!(sp.series_list[end].plotattributes, seriesinfo) + end + + #Perform after adding series... otherwise values get overwritten: + agrp = HDF5.open_group(grp, "attr") + _hdf5_merge!(sp.attr, _read(KW, agrp)) + + return sp +end + +function _read_plot(grp::Group) + listgrp = HDF5.open_group(grp, "subplots") + n = _read_length_attr(Vector, listgrp) + + #Construct new plot, +allocate subplots: + plt = plot(layout=n) + HDF5PLOT_PLOTREF.ref = plt #Used when reading "layout" + + agrp = HDF5.open_group(grp, "attr") + _hdf5_merge!(plt.attr, _read(KW, agrp)) + + for (i, sp) in enumerate(plt.subplots) + sgrp = HDF5.open_group(listgrp, "$i") + _read(sgrp, sp) + end + + return plt +end + +function hdf5plot_read(path::AbstractString; name::String="_unnamed") + HDF5.h5open(path, "r") do file + grp = HDF5.open_group(file, h5plotpath("_unnamed")) + return _read_plot(grp) + end +end + + +end #module _hdf5_implementation + + +#==Implement Plots.jl backend interface for HDF5Backend +===============================================================================# + +is_marker_supported(::HDF5Backend, shape::Shape) = true # Create the window/figure for this backend. function _create_backend_figure(plt::Plot{HDF5Backend}) @@ -168,473 +637,11 @@ function _display(plt::Plot{HDF5Backend}) return end - -#==HDF5 write functions +#==Interface actually required to use HDF5Backend ===============================================================================# -function _hdf5plot_writetype(grp, k::String, tstr::Array{String}) - d = HDF5.d_open(grp, k) - HDF5.a_write(d, "TYPE", tstr) -end -function _hdf5plot_writetype(grp, k::String, T::Type) - tstr = HDF5PLOT_MAP_TELEM2STR[T] - d = HDF5.d_open(grp, k) - HDF5.a_write(d, "TYPE", tstr) -end -function _hdf5plot_overwritetype(grp, k::String, T::Type) - tstr = HDF5PLOT_MAP_TELEM2STR[T] - d = HDF5.d_open(grp, k) - HDF5.a_delete(d, "TYPE") - HDF5.a_write(d, "TYPE", tstr) -end -function _hdf5plot_writetype(grp, T::Type) #Write directly to group - tstr = HDF5PLOT_MAP_TELEM2STR[T] - HDF5.a_write(grp, "TYPE", tstr) -end -function _hdf5plot_overwritetype(grp, T::Type) #Write directly to group - tstr = HDF5PLOT_MAP_TELEM2STR[T] - HDF5.a_delete(grp, "TYPE") - HDF5.a_write(grp, "TYPE", tstr) -end -function _hdf5plot_writetype(grp, ::Type{Array{T}}) where T<:Any - tstr = HDF5PLOT_MAP_TELEM2STR[Array] #ANY - HDF5.a_write(grp, "TYPE", tstr) -end -function _hdf5plot_writetype(grp, ::Type{T}) where T<:BoundingBox - tstr = HDF5PLOT_MAP_TELEM2STR[BoundingBox] - HDF5.a_write(grp, "TYPE", tstr) -end -function _hdf5plot_writecount(grp, n::Int) #Write directly to group - HDF5.a_write(grp, "COUNT", n) -end -function _hdf5plot_gwritefields(grp, k::String, v) - grp = HDF5.g_create(grp, k) - for _k in fieldnames(typeof(v)) - _v = getfield(v, _k) - kstr = string(_k) - _hdf5plot_gwrite(grp, kstr, _v) - end - _hdf5plot_writetype(grp, typeof(v)) - return -end - -# Write data -# ---------------------------------------------------------------- - -function _hdf5plot_gwrite(grp, k::String, v) #Default - T = typeof(v) - if !(T <: Number || T <: String) - tstr = string(T) - path = HDF5.name(grp) * "/" * k - @info("Type not supported: $tstr\npath: $path") -# @show v - return - end - grp[k] = v - _hdf5plot_writetype(grp, k, HDF5PlotNative) -end -function _hdf5plot_gwrite(grp, k::String, v::Array{T}) where T<:Number #Default for arrays - grp[k] = v - _hdf5plot_writetype(grp, k, HDF5PlotNative) -end -function _hdf5plot_gwrite(grp, k::String, v::Dict) -#= - tstr = string(Dict) - path = HDF5.name(grp) * "/" * k - @info("Type not supported: $tstr\npath: $path") -=# - #No support for structures with Dicts different than KW (plotattributes) -end - -#= -function _hdf5plot_gwrite(grp, k::String, v::Array{Any}) -# @show grp, k - @warn("Cannot write Array: $k=$v") -end -=# -function _hdf5plot_gwrite(grp, k::String, v::Nothing) - grp[k] = 0 - _hdf5plot_writetype(grp, k, Nothing) -end -function _hdf5plot_gwrite(grp, k::String, v::Bool) - grp[k] = Int(v) - _hdf5plot_writetype(grp, k, Bool) -end -function _hdf5plot_gwrite(grp, k::String, v::Symbol) - grp[k] = string(v) - _hdf5plot_writetype(grp, k, Symbol) -end -function _hdf5plot_gwrite(grp, k::String, v::Tuple) - varr = [v...] - elt = eltype(varr) -# if isleaftype(elt) - - _hdf5plot_gwrite(grp, k, varr) - if elt <: Number - #We just wrote a simple dataset - _hdf5plot_overwritetype(grp, k, Tuple) - else #Used a more complex scheme (using subgroups): - _hdf5plot_overwritetype(grp[k], HDF5CTuple) - end - #NOTE: _hdf5plot_overwritetype overwrites "Array" type with "Tuple". -end -function _hdf5plot_gwrite(grp, k::String, plotattributes::KW) - #NOTE: Can only write directly to group, not a subi-item -# @warn("Cannot write dict: $k=$plotattributes") -end -function _hdf5plot_gwrite(grp, k::String, v::AbstractRange) - _hdf5plot_gwrite(grp, k, collect(v)) #For now -end -function _hdf5plot_gwrite(grp, k::String, v::ARGB{N0f8}) - grp[k] = [v.r.i, v.g.i, v.b.i, v.alpha.i] - _hdf5plot_writetype(grp, k, ARGB{N0f8}) -end -function _hdf5plot_gwrite(grp, k::String, v::Colorant) - _hdf5plot_gwrite(grp, k, ARGB{N0f8}(v)) -end -#Custom vector (when not using simple numeric type): -function _hdf5plot_gwritearray(grp, k::String, v::Array{T}) where T - vgrp = HDF5.g_create(grp, k) - _hdf5plot_writetype(vgrp, Array) #ANY - sz = size(v) - lidx = LinearIndices(sz) - - for iter in eachindex(v) - coord = lidx[iter] - elem = v[iter] - idxstr = join(coord, "_") - _hdf5plot_gwrite(vgrp, "v$idxstr", elem) - end - - _hdf5plot_gwrite(vgrp, "dim", [sz...]) - return -end -_hdf5plot_gwrite(grp, k::String, v::Array) = - _hdf5plot_gwritearray(grp, k, v) -function _hdf5plot_gwrite(grp, k::String, v::Extrema) - grp[k] = [v.emin, v.emax] - _hdf5plot_writetype(grp, k, Extrema) -end -function _hdf5plot_gwrite(grp, k::String, v::Length{T}) where T - grp[k] = v.value - _hdf5plot_writetype(grp, k, [HDF5PLOT_MAP_TELEM2STR[Length], string(T)]) -end - -# Write more complex structures: -# ---------------------------------------------------------------- - -function _hdf5plot_gwrite(grp, k::String, v::Plot) - #Don't write plot references -end -function _hdf5plot_gwrite(grp, k::String, v::HDF5PLOT_SIMPLESUBSTRUCT) - _hdf5plot_gwritefields(grp, k, v) - return -end -function _hdf5plot_gwrite(grp, k::String, v::Axis) - grp = HDF5.g_create(grp, k) - for (_k, _v) in v.plotattributes - kstr = string(_k) - _hdf5plot_gwrite(grp, kstr, _v) - end - _hdf5plot_writetype(grp, Axis) - return -end -function _hdf5plot_gwrite(grp, k::String, v::Surface) - grp = HDF5.g_create(grp, k) - _hdf5plot_gwrite(grp, "data2d", v.surf) - _hdf5plot_writetype(grp, Surface) -end -# #TODO: "Properly" support Nullable using _hdf5plot_writetype? -# function _hdf5plot_gwrite(grp, k::String, v::Union{Nothing, T} where T) -# if isnull(v) -# _hdf5plot_gwrite(grp, k, nothing) -# else -# _hdf5plot_gwrite(grp, k, v.value) -# end -# return -# end - -function _hdf5plot_gwrite(grp, k::String, v::Subplot) - grp = HDF5.g_create(grp, k) - _hdf5plot_gwrite(grp, "index", v[:subplot_index]) - _hdf5plot_writetype(grp, Subplot) - return -end - -function _hdf5plot_write(grp, plotattributes::KW) - for (k, v) in plotattributes - kstr = string(k) - _hdf5plot_gwrite(grp, kstr, v) - end - return -end -function _hdf5plot_write(grp, plotattributes::DefaultsDict) - for (k, v) in plotattributes - kstr = string(k) - _hdf5plot_gwrite(grp, kstr, v) - end - _hdf5plot_writetype(grp, DefaultsDict) -end - - -# Write main plot structures: -# ---------------------------------------------------------------- - -function _hdf5plot_write(sp::Subplot{HDF5Backend}, subpath::String, f) - f = f::HDF5.HDF5File #Assert - grp = HDF5.g_create(f, _hdf5_plotelempath("$subpath/attr")) - _hdf5plot_write(grp, sp.attr) - grp = HDF5.g_create(f, _hdf5_plotelempath("$subpath/series_list")) - _hdf5plot_writecount(grp, length(sp.series_list)) - for (i, series) in enumerate(sp.series_list) - grp = HDF5.g_create(f, _hdf5_plotelempath("$subpath/series_list/series$i")) - _hdf5plot_write(grp, series.plotattributes) - end - - return -end - -function _hdf5plot_write(plt::Plot{HDF5Backend}, f) - f = f::HDF5.HDF5File #Assert - - grp = HDF5.g_create(f, _hdf5_plotelempath("attr")) - _hdf5plot_write(grp, plt.attr) - - grp = HDF5.g_create(f, _hdf5_plotelempath("subplots")) - _hdf5plot_writecount(grp, length(plt.subplots)) - - for (i, sp) in enumerate(plt.subplots) - _hdf5plot_write(sp, "subplots/subplot$i", f) - end - - return -end -function hdf5plot_write(plt::Plot{HDF5Backend}, path::AbstractString) - HDF5.h5open(path, "w") do file - _hdf5plot_write(plt, file) - end -end -hdf5plot_write(path::AbstractString) = hdf5plot_write(current(), path) - - -#==HDF5 playback (read) functions -===============================================================================# - -function _hdf5plot_readcount(grp) #Read directly from group - return HDF5.a_read(grp, "COUNT") -end - -_hdf5plot_convert(T::Type{HDF5PlotNative}, v) = v -_hdf5plot_convert(T::Type{Nothing}, v) = nothing -_hdf5plot_convert(T::Type{Bool}, v) = (v!=0) -_hdf5plot_convert(T::Type{Symbol}, v) = Symbol(v) -_hdf5plot_convert(T::Type{Tuple}, v) = tuple(v...) #With Vector{T<:Number} -function _hdf5plot_convert(T::Type{ARGB{N0f8}}, v) - r, g, b, a = reinterpret(N0f8, v) - return Colors.ARGB{N0f8}(r, g, b, a) -end -_hdf5plot_convert(T::Type{Extrema}, v) = Extrema(v[1], v[2]) - -# Read data structures: -# ---------------------------------------------------------------- - -function _hdf5plot_read(grp, k::String, T::Type) - v = HDF5.d_read(grp, k) - return _hdf5plot_convert(T, v) -end - -# Read more complex data structures: -# ---------------------------------------------------------------- -function _hdf5plot_read(grp, k::String, T::Type{Font}) - grp = HDF5.g_open(grp, k) - - family = _hdf5plot_read(grp, "family") - pointsize = _hdf5plot_read(grp, "pointsize") - halign = _hdf5plot_read(grp, "halign") - valign = _hdf5plot_read(grp, "valign") - rotation = _hdf5plot_read(grp, "rotation") - color = _hdf5plot_read(grp, "color") - return Font(family, pointsize, halign, valign, rotation, color) -end -function _hdf5plot_read(grp, k::String, T::Type{Array}) #ANY - grp = HDF5.g_open(grp, k) - sz = _hdf5plot_read(grp, "dim") - if [0] == sz; return []; end - sz = tuple(sz...) - result = Array{Any}(undef, sz) - lidx = LinearIndices(sz) - - for iter in eachindex(result) - coord = lidx[iter] - idxstr = join(coord, "_") - result[iter] = _hdf5plot_read(grp, "v$idxstr") - end - - #Hack: Implicitly make Julia detect element type. - # (Should probably write it explicitly to file) - result = [result[iter] for iter in eachindex(result)] #Potentially make more specific - return reshape(result, sz) -end -function _hdf5plot_read(grp, k::String, T::Type{HDF5CTuple}) - v = _hdf5plot_read(grp, k, Array) - return tuple(v...) -end -function _hdf5plot_read(grp, k::String, T::Type{PlotText}) - grp = HDF5.g_open(grp, k) - - str = _hdf5plot_read(grp, "str") - font = _hdf5plot_read(grp, "font") - return PlotText(str, font) -end -function _hdf5plot_read(grp, k::String, T::Type{SeriesAnnotations}) - grp = HDF5.g_open(grp, k) - - strs = _hdf5plot_read(grp, "strs") - font = _hdf5plot_read(grp, "font") - baseshape = _hdf5plot_read(grp, "baseshape") - scalefactor = _hdf5plot_read(grp, "scalefactor") - return SeriesAnnotations(strs, font, baseshape, scalefactor) -end -function _hdf5plot_read(grp, k::String, T::Type{Shape}) - grp = HDF5.g_open(grp, k) - - x = _hdf5plot_read(grp, "x") - y = _hdf5plot_read(grp, "y") - return Shape(x, y) -end -function _hdf5plot_read(grp, k::String, T::Type{ColorGradient}) - grp = HDF5.g_open(grp, k) - - colors = _hdf5plot_read(grp, "colors") - values = _hdf5plot_read(grp, "values") - return ColorGradient(colors, values) -end -function _hdf5plot_read(grp, k::String, T::Type{BoundingBox}) - grp = HDF5.g_open(grp, k) - - x0 = _hdf5plot_read(grp, "x0") - a = _hdf5plot_read(grp, "a") - return BoundingBox(x0, a) -end -_hdf5plot_read(grp, k::String, T::Type{RootLayout}) = RootLayout() -function _hdf5plot_read(grp, k::String, T::Type{GridLayout}) - grp = HDF5.g_open(grp, k) - -# parent = _hdf5plot_read(grp, "parent") -parent = RootLayout() - minpad = _hdf5plot_read(grp, "minpad") - bbox = _hdf5plot_read(grp, "bbox") - grid = _hdf5plot_read(grp, "grid") - widths = _hdf5plot_read(grp, "widths") - heights = _hdf5plot_read(grp, "heights") - attr = KW() #TODO support attr: _hdf5plot_read(grp, "attr") - - return GridLayout(parent, minpad, bbox, grid, widths, heights, attr) -end -function _hdf5plot_read(grp, T::Type{DefaultsDict}) - attr = DefaultsDict(KW(), _plot_defaults) - v = _hdf5plot_read(grp, attr) - return attr -end -function _hdf5plot_read(grp, k::String, T::Type{Axis}) - grp = HDF5.g_open(grp, k) - plotattributes = DefaultsDict(KW(), _plot_defaults) - _hdf5plot_read(grp, plotattributes) - return Axis([], plotattributes) -end -function _hdf5plot_read(grp, k::String, T::Type{Surface}) - grp = HDF5.g_open(grp, k) - data2d = _hdf5plot_read(grp, "data2d") - return Surface(data2d) -end -function _hdf5plot_read(grp, k::String, T::Type{Subplot}) - grp = HDF5.g_open(grp, k) - idx = _hdf5plot_read(grp, "index") - return HDF5PLOT_PLOTREF.ref.subplots[idx] -end - -#Most types don't need dtid for read!!: -_hdf5plot_read(grp, k::String, T::Type, dtid) = _hdf5plot_read(grp, k, T) -function _hdf5plot_read(grp, k::String, T::Type{Length}, dtid::Vector) - v = HDF5.d_read(grp, k) - TU = Symbol(dtid[2]) - T = typeof(v) - return Length{TU,T}(v) -end -function _hdf5plot_read(grp, k::String) - dtid = HDF5.a_read(grp[k], "TYPE") - T = _hdf5_map_str2telem(dtid) #expect exception - return _hdf5plot_read(grp, k, T, dtid) -end - -#Read in values in group to populate plotattributes: -function _hdf5plot_readattr(grp, plotattributes::AbstractDict) - gnames = names(grp) - for k in gnames - try - v = _hdf5plot_read(grp, k) - plotattributes[Symbol(k)] = v - catch e - @show e - @show grp - @warn("Could not read field $k") - end - end - return -end -_hdf5plot_read(grp, plotattributes::KW) = _hdf5plot_readattr(grp, plotattributes) -_hdf5plot_read(grp, plotattributes::DefaultsDict) = _hdf5plot_readattr(grp, plotattributes) - -# Read main plot structures: -# ---------------------------------------------------------------- - -function _hdf5plot_read(sp::Subplot, subpath::String, f) - f = f::HDF5.HDF5File #Assert - - grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list")) - nseries = _hdf5plot_readcount(grp) - - for i in 1:nseries - grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list/series$i")) - seriesinfo = DefaultsDict(KW(), _plot_defaults) - _hdf5plot_read(grp, seriesinfo) - plot!(sp, seriesinfo[:x], seriesinfo[:y]) #Add data & create data structures - _hdf5_merge!(sp.series_list[end].plotattributes, seriesinfo) - end - - #Perform after adding series... otherwise values get overwritten: - grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/attr")) - attr = DefaultsDict(KW(), _plot_defaults) - _hdf5plot_read(grp, attr) - _hdf5_merge!(sp.attr, attr) - - return -end - -function _hdf5plot_read(plt::Plot, f) - f = f::HDF5.HDF5File #Assert - #Assumpltion: subplots are already allocated (plt.subplots) - - HDF5PLOT_PLOTREF.ref = plt #Used when reading "layout" - grp = HDF5.g_open(f, _hdf5_plotelempath("attr")) - _hdf5plot_read(grp, plt.attr) - - for (i, sp) in enumerate(plt.subplots) - _hdf5plot_read(sp, "subplots/subplot$i", f) - end - - return -end - -function hdf5plot_read(path::AbstractString) - plt = nothing - HDF5.h5open(path, "r") do file - grp = HDF5.g_open(file, _hdf5_plotelempath("subplots")) - n = _hdf5plot_readcount(grp) - plt = plot(layout=n) #Get reference to a new plot - _hdf5plot_read(plt, file) - end - return plt -end +hdf5plot_write(plt::Plot{HDF5Backend}, path::AbstractString) = _hdf5_implementation.hdf5plot_write(plt, path) +hdf5plot_write(path::AbstractString) = _hdf5_implementation.hdf5plot_write(current(), path) +hdf5plot_read(path::AbstractString) = _hdf5_implementation.hdf5plot_read(path) #Last line diff --git a/src/backends/inspectdr.jl b/src/backends/inspectdr.jl index e86fb995..ac362838 100644 --- a/src/backends/inspectdr.jl +++ b/src/backends/inspectdr.jl @@ -17,9 +17,6 @@ Add in functionality to Plots.jl: is_marker_supported(::InspectDRBackend, shape::Shape) = true -_inspectdr_to_pixels(bb::BoundingBox) = - InspectDR.BoundingBox(to_pixels(left(bb)), to_pixels(right(bb)), to_pixels(top(bb)), to_pixels(bottom(bb))) - #Do we avoid Map to avoid possible pre-comile issues? function _inspectdr_mapglyph(s::Symbol) s == :rect && return :square @@ -79,6 +76,58 @@ end # --------------------------------------------------------------------------- +function _inspectdr_getaxisticks(ticks, gridlines, xfrm) + TickCustom = InspectDR.TickCustom + _xfrm(coord) = InspectDR.axis2aloc(Float64(coord), xfrm.spec) #Ensure Float64 - in case + + ttype = ticksType(ticks) + if ticks == :native + #keep current + elseif ttype == :ticks_and_labels + pos = ticks[1]; labels = ticks[2]; nticks = length(ticks[1]) + newticks = TickCustom[TickCustom(_xfrm(pos[i]), labels[i]) for i in 1:nticks] + gridlines = InspectDR.GridLinesCustom(gridlines) + gridlines.major = newticks + gridlines.minor = [] + gridlines.displayminor = false + elseif ttype == :ticks + nticks = length(ticks) + gridlines.major = Float64[_xfrm(t) for t in ticks] + gridlines.minor = [] + gridlines.displayminor = false + elseif isnothing(ticks) + gridlines.major = [] + gridlines.minor = [] + else #Assume ticks == :native + #keep current + end + + return gridlines #keep current +end + +function _inspectdr_setticks(sp::Subplot, plot, strip, xaxis, yaxis) + InputXfrm1D = InspectDR.InputXfrm1D + _get_ticks(axis) = :native == axis[:ticks] ? (:native) : get_ticks(sp, axis) + wantnative(ticks) = (:native == ticks) + + xticks = _get_ticks(xaxis) + yticks = _get_ticks(yaxis) + + if wantnative(xticks) && wantnative(yticks) + #Don't "eval" tick values + return + end + + #TODO: Allow InspectDR to independently "eval" x or y ticks + ext = InspectDR.getextents_aloc(plot, 1) + grid = InspectDR._eval(strip.grid, plot.xscale, strip.yscale, ext) + grid.xlines = _inspectdr_getaxisticks(xticks, grid.xlines, InputXfrm1D(plot.xscale)) + grid.ylines = _inspectdr_getaxisticks(yticks, grid.ylines, InputXfrm1D(strip.yscale)) + strip.grid = grid +end + +# --------------------------------------------------------------------------- + function _inspectdr_getscale(s::Symbol, yaxis::Bool) #TODO: Support :asinh, :sqrt kwargs = yaxis ? (:tgtmajor=>8, :tgtminor=>2) : () #More grid lines on y-axis @@ -167,6 +216,7 @@ function _series_added(plt::Plot{InspectDRBackend}, series::Series) st = series[:seriestype] sp = series[:subplot] plot = sp.o + clims = get_clims(sp, series) #Don't do anything without a "subplot" object: Will process later. if nothing == plot; return; end @@ -237,7 +287,7 @@ For st in :shape: color = linecolor, fillcolor = fillcolor ) end - elseif st in (:path, :scatter, :straightline) #, :steppre, :steppost) + elseif st in (:path, :scatter, :straightline) #, :steppre, :stepmid, :steppost) #NOTE: In Plots.jl, :scatter plots have 0-linewidths (I think). linewidth = series[:linewidth] #More efficient & allows some support for markerstrokewidth: @@ -256,8 +306,8 @@ For st in :shape: wfrm.glyph = InspectDR.glyph( shape = _inspectdr_mapglyph(series[:markershape]), size = _inspectdr_mapglyphsize(series[:markersize]), - color = _inspectdr_mapcolor(plot_color(series[:markerstrokecolor], series[:markerstrokealpha])), - fillcolor = _inspectdr_mapcolor(plot_color(series[:markercolor], series[:markeralpha])), + color = _inspectdr_mapcolor(plot_color(get_markerstrokecolor(series), get_markerstrokealpha(series))), + fillcolor = _inspectdr_mapcolor(plot_color(get_markercolor(series, clims), get_markeralpha(series))), ) end @@ -288,8 +338,8 @@ function _inspectdr_setupsubplot(sp::Subplot{InspectDRBackend}) ygrid_show = yaxis[:grid] strip.grid = InspectDR.GridRect( - vmajor=xgrid_show, # vminor=xgrid_show, - hmajor=ygrid_show, # hminor=ygrid_show, + vmajor=xgrid_show, # vminor=xgrid_show, + hmajor=ygrid_show, # hminor=ygrid_show, ) plot.xscale = _inspectdr_getscale(xaxis[:scale], false) @@ -302,38 +352,48 @@ function _inspectdr_setupsubplot(sp::Subplot{InspectDRBackend}) xmin, xmax = -rmax, rmax ymin, ymax = -rmax, rmax end - plot.xext = InspectDR.PExtents1D() #reset - strip.yext = InspectDR.PExtents1D() #reset plot.xext_full = InspectDR.PExtents1D(xmin, xmax) strip.yext_full = InspectDR.PExtents1D(ymin, ymax) + #Set current extents = full extents (needed for _eval(strip.grid,...)) + plot.xext = plot.xext_full + strip.yext = strip.yext_full + _inspectdr_setticks(sp, plot, strip, xaxis, yaxis) + a = plot.annotation a.title = sp[:title] a.xlabel = xaxis[:guide]; a.ylabels = [yaxis[:guide]] - l = plot.layout - l[:frame_canvas].fillcolor = _inspectdr_mapcolor(sp[:background_color_subplot]) - l[:frame_data].fillcolor = _inspectdr_mapcolor(sp[:background_color_inside]) - l[:frame_data].line.color = _inspectdr_mapcolor(xaxis[:foreground_color_axis]) - l[:font_title] = InspectDR.Font(sp[:titlefontfamily], + #Modify base layout of new object: + l = plot.layout.defaults = deepcopy(InspectDR.defaults.plotlayout) + #IMPORTANT: Must deepcopy to ensure we don't change layouts of other plots. + #Works because plot uses defaults (not user-overwritten `layout.values`) + l.frame_canvas.fillcolor = _inspectdr_mapcolor(sp[:background_color_subplot]) + l.frame_data.fillcolor = _inspectdr_mapcolor(sp[:background_color_inside]) + l.frame_data.line.color = _inspectdr_mapcolor(xaxis[:foreground_color_axis]) + l.font_title = InspectDR.Font(sp[:titlefontfamily], _inspectdr_mapptsize(sp[:titlefontsize]), color = _inspectdr_mapcolor(sp[:titlefontcolor]) ) #Cannot independently control fonts of axes with InspectDR: - l[:font_axislabel] = InspectDR.Font(xaxis[:guidefontfamily], + l.font_axislabel = InspectDR.Font(xaxis[:guidefontfamily], _inspectdr_mapptsize(xaxis[:guidefontsize]), color = _inspectdr_mapcolor(xaxis[:guidefontcolor]) ) - l[:font_ticklabel] = InspectDR.Font(xaxis[:tickfontfamily], + l.font_ticklabel = InspectDR.Font(xaxis[:tickfontfamily], _inspectdr_mapptsize(xaxis[:tickfontsize]), color = _inspectdr_mapcolor(xaxis[:tickfontcolor]) ) - l[:enable_legend] = (sp[:legend] != :none) - #l[:halloc_legend] = 150 #TODO: compute??? - l[:font_legend] = InspectDR.Font(sp[:legendfontfamily], + l.enable_legend = (sp[:legend] != :none) + #l.halloc_legend = 150 #TODO: compute??? + l.font_legend = InspectDR.Font(sp[:legendfontfamily], _inspectdr_mapptsize(sp[:legendfontsize]), color = _inspectdr_mapcolor(sp[:legendfontcolor]) ) - l[:frame_legend].fillcolor = _inspectdr_mapcolor(sp[:background_color_legend]) + l.frame_legend.fillcolor = _inspectdr_mapcolor(sp[:background_color_legend]) + + #_round!() ensures values use integer spacings (looks better on screen): + InspectDR._round!(InspectDR.autofit2font!(l, legend_width=10.0)) #10 "em"s wide + return end # called just before updating layout bounding boxes... in case you need to prep @@ -347,8 +407,9 @@ function _before_layout_calcs(plt::Plot{InspectDRBackend}) #Don't use window_title... probably not what you want. #mplot.title = plt[:window_title] end - mplot.layout[:frame].fillcolor = _inspectdr_mapcolor(plt[:background_color_outside]) + mplot.layout[:frame].fillcolor = _inspectdr_mapcolor(plt[:background_color_outside]) + mplot.layout[:frame] = mplot.layout[:frame] #register changes resize!(mplot.subplots, length(plt.subplots)) nsubplots = length(plt.subplots) for (i, sp) in enumerate(plt.subplots) @@ -412,11 +473,16 @@ end function _update_plot_object(plt::Plot{InspectDRBackend}) mplot = _inspectdr_getmplot(plt.o) if nothing == mplot; return; end + mplot.bblist = InspectDR.BoundingBox[] for (i, sp) in enumerate(plt.subplots) - graphbb = _inspectdr_to_pixels(plotarea(sp)) - plot = mplot.subplots[i] - plot.plotbb = InspectDR.plotbounds(plot.layout.values, graphbb) + figw, figh = sp.plt[:size] + pcts = bbox_to_pcts(sp.bbox, figw*px, figh*px) + _left, _bottom, _width, _height = pcts + ymax = 1.0-_bottom + ymin = ymax - _height + bb = InspectDR.BoundingBox(_left, _left+_width, ymin, ymax) + push!(mplot.bblist, bb) end gplot = _inspectdr_getgui(plt.o) diff --git a/src/backends/pgfplotsx.jl b/src/backends/pgfplotsx.jl index 4486330c..438c007e 100644 --- a/src/backends/pgfplotsx.jl +++ b/src/backends/pgfplotsx.jl @@ -1,5 +1,6 @@ using Contour: Contour using UUIDs +using Latexify Base.@kwdef mutable struct PGFPlotsXPlot is_created::Bool = false was_shown::Bool = false @@ -72,28 +73,32 @@ function surface_to_vecs(x::AVec, y::AVec, s::Union{AMat,Surface}) yn = Vector{eltype(y)}(undef, length(a)) zn = Vector{eltype(s)}(undef, length(a)) for (n, (i, j)) in enumerate(Tuple.(CartesianIndices(a))) + if length(x) == size(s)[1] + i, j = j, i + end xn[n] = x[j] yn[n] = y[i] zn[n] = a[i, j] end return xn, yn, zn end +surface_to_vecs(x::AVec, y::AVec, z::AVec) = x, y, z function Base.push!(pgfx_plot::PGFPlotsXPlot, item) push!(pgfx_plot.the_plot, item) end +function pgfx_split_extra_opts(extra) + return (get(extra, :add, nothing), filter( x-> first(x) != :add, extra)) +end function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) if !pgfx_plot.is_created || pgfx_plot.was_shown pgfx_sanitize_plot!(plt) # extract extra kwargs - extra_plot_opt = plt[:extra_plot_kwargs] - extra_plot = nothing - if haskey(extra_plot_opt, :add) - extra_plot = wraptuple(pop!(extra_plot_opt,:add)) - end + extra_plot, extra_plot_opt = pgfx_split_extra_opts(plt[:extra_plot_kwargs]) the_plot = PGFPlotsX.TikzPicture(PGFPlotsX.Options(extra_plot_opt...)) if extra_plot !== nothing + extra_plot = wraptuple(extra_plot) push!(the_plot, extra_plot...) end bgc = plt.attr[:background_color_outside] == :match ? @@ -127,26 +132,17 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) axis_height = sp_height - (tpad + bpad) axis_width = sp_width - (rpad + lpad) - cstr = plot_color(sp[:background_color_legend]) - a = alpha(cstr) - fg_alpha = alpha(plot_color(sp[:foreground_color_legend])) title_cstr = plot_color(sp[:titlefontcolor]) title_a = alpha(title_cstr) title_loc = sp[:titlelocation] bgc_inside = plot_color(sp[:background_color_inside]) bgc_inside_a = alpha(bgc_inside) axis_opt = PGFPlotsX.Options( + "point meta max" => get_clims(sp)[2], + "point meta min" => get_clims(sp)[1], "title" => sp[:title], "title style" => PGFPlotsX.Options( - "at" => if title_loc == :left - "{(0,1)}" - elseif title_loc == :right - "{(1,1)}" - elseif title_loc isa Tuple - "{$(string(title_loc))}" - else - "{(0.5,1)}" - end, + pgfx_get_title_pos(title_loc)..., "font" => pgfx_font( sp[:titlefontsize], pgfx_thickness_scaling(sp), @@ -155,21 +151,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) "draw opacity" => title_a, "rotate" => sp[:titlefontrotation], ), - "legend style" => PGFPlotsX.Options( - pgfx_linestyle( - pgfx_thickness_scaling(sp), - sp[:foreground_color_legend], - fg_alpha, - "solid", - ) => nothing, - "fill" => cstr, - "fill opacity" => a, - "text opacity" => alpha(plot_color(sp[:legendfontcolor])), - "font" => pgfx_font( - sp[:legendfontsize], - pgfx_thickness_scaling(sp), - ), - ), + "legend style" => pgfx_get_legend_style(sp), "axis background/.style" => PGFPlotsX.Options( "fill" => bgc_inside, "opacity" => bgc_inside_a, @@ -183,13 +165,6 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) nothing sp_height > 0 * mm ? push!(axis_opt, "height" => string(axis_height)) : nothing - # legend position - if sp[:legend] isa Tuple - x, y = sp[:legend] - push!(axis_opt["legend style"], "at={($x, $y)}") - else - push!(axis_opt["legend style"], pgfx_get_legend_pos(sp[:legend])...) - end for letter in (:x, :y, :z) if letter != :z || RecipesPipeline.is3d(sp) pgfx_axis!(axis_opt, sp, letter) @@ -206,41 +181,56 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) # As it is likely that all series within the same axis use the same # colormap this should not cause any problem. for series in series_list(sp) - if hascolorbar(series) - cg = get_colorgradient(series) - cm = pgfx_colormap(get_colorgradient(series)) - PGFPlotsX.push_preamble!( - pgfx_plot.the_plot, - """\\pgfplotsset{ - colormap={plots$(sp.attr[:subplot_index])}{$cm}, - }""", - ) + if hascolorbar(series) + cg = get_colorgradient(series) + cm = pgfx_colormap(get_colorgradient(series)) + PGFPlotsX.push_preamble!( + pgfx_plot.the_plot, + """\\pgfplotsset{ + colormap={plots$(sp.attr[:subplot_index])}{$cm}, + }""", + ) + push!(axis_opt, "colormap name" => "plots$(sp.attr[:subplot_index])") + if cg isa PlotUtils.CategoricalColorGradient push!( axis_opt, - "colorbar" => nothing, - "colormap name" => "plots$(sp.attr[:subplot_index])", + "colormap access" => "piecewise const", + "colorbar sampled" => nothing, ) - if cg isa PlotUtils.CategoricalColorGradient - push!( - axis_opt, - "colormap access" => "piecewise const", - "colorbar sampled" => nothing, - ) - end - # goto is needed to break out of col and series for - @goto colorbar_end end + # goto is needed to break out of col and series for + @goto colorbar_end + end end @label colorbar_end - push!( - axis_opt, - "colorbar style" => PGFPlotsX.Options( + if hascolorbar(sp) + cticks = get_colorbar_ticks(sp)[2] + colorbar_style = PGFPlotsX.Options( "title" => sp[:colorbar_title], - ), - "point meta max" => get_clims(sp)[2], - "point meta min" => get_clims(sp)[1], - ) + ) + if sp[:colorbar] === :top + push!(colorbar_style, + "at" => string((0.5, 1.05)), + "anchor" => "south", + "xtick" => string("{", join(cticks, ","), "}"), + "xticklabel pos" => "upper", + "xticklabel style" => pgfx_get_colorbar_ticklabel_style(sp), + ) + else + push!(colorbar_style, + "ytick" => string("{", join(cticks, ","), "}"), + "yticklabel style" => pgfx_get_colorbar_ticklabel_style(sp), + ) + end + push!( + axis_opt, + string("colorbar", pgfx_get_colorbar_pos(sp[:colorbar])) => nothing, + "colorbar style" => colorbar_style, + ) + else + push!(axis_opt, "colorbar" => "false") + end if RecipesPipeline.is3d(sp) azim, elev = sp[:camera] push!(axis_opt, "view" => (azim, elev)) @@ -252,13 +242,10 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) else PGFPlotsX.Axis end - extra_sp_opt = sp[:extra_kwargs] - extra_sp = nothing - if haskey(extra_sp_opt, :add) - extra_sp = wraptuple(pop!(extra_sp_opt,:add)) - end + extra_sp, extra_sp_opt = pgfx_split_extra_opts(sp[:extra_kwargs]) axis = axisf(merge(axis_opt, PGFPlotsX.Options(extra_sp_opt...))) if extra_sp !== nothing + extra_sp = wraptuple(extra_sp) push!(axis, extra_sp...) end if sp[:legendtitle] !== nothing @@ -282,13 +269,9 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) "color" => single_color(opt[:linecolor]), "name path" => string(series_id), ) - extra_series_opt = series[:extra_kwargs] - extra_series = nothing - if haskey(extra_series_opt, :add) - extra_series = wraptuple(pop!(extra_series_opt,:add)) - end + extra_series, extra_series_opt = pgfx_split_extra_opts(series[:extra_kwargs]) series_opt = merge(series_opt, PGFPlotsX.Options(extra_series_opt...)) - if RecipesPipeline.is3d(series) || st == :heatmap + if RecipesPipeline.is3d(series) || st in (:heatmap, :contour) || (st == :quiver && opt[:z] !== nothing) series_func = PGFPlotsX.Plot3 else series_func = PGFPlotsX.Plot @@ -297,131 +280,11 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) !isfilledcontour(series) && series[:ribbon] === nothing push!(series_opt, "area legend" => nothing) end - if st == :heatmap - push!(axis.options, "view" => "{0}{90}") + pgfx_add_series!(Val(st), axis, series_opt, series, series_func, opt) + if extra_series !== nothing + extra_series = wraptuple(extra_series) + push!(axis.contents[end], extra_series...) end - # treat segments - segments = - if st in (:wireframe, :heatmap, :contour, :surface, :contour3d) - iter_segments(surface_to_vecs( - series[:x], - series[:y], - series[:z], - )...) - else - iter_segments(series) - end - for (i, rng) in enumerate(segments) - segment_opt = PGFPlotsX.Options() - segment_opt = merge(segment_opt, pgfx_linestyle(opt, i)) - if opt[:markershape] != :none - marker = opt[:markershape] - if marker isa Shape - x = marker.x - y = marker.y - scale_factor = 0.00125 - mark_size = opt[:markersize] * scale_factor - path = join( - [ - "($(x[i] * mark_size), $(y[i] * mark_size))" - for i in eachindex(x) - ], - " -- ", - ) - c = get_markercolor(series, i) - a = get_markeralpha(series, i) - PGFPlotsX.push_preamble!( - pgfx_plot.the_plot, - """ - \\pgfdeclareplotmark{PlotsShape$(series_index)}{ - \\filldraw - $path; - } - """, - ) - end - segment_opt = merge(segment_opt, pgfx_marker(opt, i)) - end - if st == :shape || isfilledcontour(series) - segment_opt = merge(segment_opt, pgfx_fillstyle(opt, i)) - end - # add fillrange - if sf !== nothing && - !isfilledcontour(series) && series[:ribbon] === nothing - if sf isa Number || sf isa AVec - pgfx_fillrange_series!( - axis, - series, - series_func, - i, - _cycle(sf, rng), - rng, - ) - end - if i == 1 && - sp[:legend] != :none && pgfx_should_add_to_legend(series) - pgfx_filllegend!(series_opt, opt) - end - end - coordinates = - pgfx_series_coordinates!(sp, series, segment_opt, opt, rng) - segment_plot = - series_func(merge(series_opt, segment_opt), coordinates) - if extra_series !== nothing - push!(segment_plot, extra_series...) - end - push!(axis, segment_plot) - # fill between functions - if sf isa Tuple && series[:ribbon] === nothing - sf1, sf2 = sf - @assert sf1 == series_index "First index of the tuple has to match the current series index." - push!( - axis, - series_func( - merge( - pgfx_fillstyle(opt, series_index), - PGFPlotsX.Options("forget plot" => nothing), - ), - "fill between [of=$series_id and $(_pgfplotsx_series_ids[Symbol(string(sf2))])]", - ), - ) - end - # add ribbons? - ribbon = series[:ribbon] - if ribbon !== nothing - pgfx_add_ribbons!( - axis, - series, - segment_plot, - series_func, - series_index, - ) - end - # add to legend? - if sp[:legend] != :none - leg_entry = if opt[:label] isa AVec - get(opt[:label], i, "") - elseif opt[:label] isa AbstractString - if i == 1 - get(opt, :label, "") - else - "" - end - else - throw(ArgumentError("Malformed label. label = $(opt[:label])")) - end - if leg_entry == "" || !pgfx_should_add_to_legend(series) - push!(axis.contents[end].options, "forget plot" => nothing) - else - leg_opt = PGFPlotsX.Options() - if ribbon !== nothing - pgfx_filllegend!(axis.contents[end - 3].options, opt) - end - legend = PGFPlotsX.LegendEntry(leg_opt, leg_entry, false) - push!(axis, legend) - end - end - end # for segments # add series annotations anns = series[:series_annotations] for (xi, yi, str, fnt) in EachAnn(anns, series[:x], series[:y]) @@ -455,11 +318,353 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) return pgfx_plot end ## seriestype specifics -@inline function pgfx_series_coordinates!(sp, series, segment_opt, opt, rng) +function pgfx_add_series!(axis, series_opt, series, series_func, opt) + args = pgfx_series_arguments(series, opt) + series_plot = series_func(series_opt, PGFPlotsX.Table(args...)) + push!(axis, series_plot) + pgfx_add_legend!(axis, series, opt) +end + +function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, opt) + # treat segments + segments = collect(series_segments(series, series[:seriestype])) + sf = opt[:fillrange] + for (k, segment) in enumerate(segments) + i, rng = segment.attr_index, segment.range + segment_opt = PGFPlotsX.Options() + segment_opt = merge(segment_opt, pgfx_linestyle(opt, i)) + if opt[:markershape] != :none + marker = _cycle(opt[:markershape], i) + if marker isa Shape + x = marker.x + y = marker.y + scale_factor = 0.00125 + mark_size = opt[:markersize] * scale_factor + path = join( + [ + "($(x[i] * mark_size), $(y[i] * mark_size))" + for i in eachindex(x) + ], + " -- ", + ) + c = get_markercolor(series, i) + a = get_markeralpha(series, i) + PGFPlotsX.push_preamble!( + series[:plot_object].o.the_plot, + """ + \\pgfdeclareplotmark{PlotsShape$(series[:series_plotindex])}{ + \\filldraw + $path; + } + """, + ) + end + segment_opt = merge(segment_opt, pgfx_marker(opt, i)) + end + # add fillrange + if sf !== nothing && + !isfilledcontour(series) + if sf isa Number || sf isa AVec + pgfx_fillrange_series!( + axis, + series, + series_func, + i, + _cycle(sf, rng), + rng, + ) + elseif sf isa Tuple && series[:ribbon] !== nothing + for sfi in sf + pgfx_fillrange_series!( + axis, + series, + series_func, + i, + _cycle(sfi, rng), + rng, + ) + end + end + if k == 1 && + series[:subplot][:legend] != :none && pgfx_should_add_to_legend(series) + pgfx_filllegend!(series_opt, opt) + end + end + # handle arrows + arrow = opt[:arrow] + if arrow isa Arrow + arrow_opt = merge( + segment_opt, + PGFPlotsX.Options("quiver" => PGFPlotsX.Options( + "u" => "\\thisrow{u}", + "v" => "\\thisrow{v}", + pgfx_arrow(arrow, :head) => nothing, + ) + ) + ) + if arrow.side == :head + x_arrow = opt[:x][rng][end-1:end] + y_arrow = opt[:y][rng][end-1:end] + x_path = opt[:x][rng][1:end-1] + y_path = opt[:y][rng][1:end-1] + elseif arrow.side == :tail + x_arrow = opt[:x][rng][2:-1:1] + y_arrow = opt[:y][rng][2:-1:1] + x_path = opt[:x][rng][2:end] + y_path = opt[:y][rng][2:end] + elseif arrow.side == :both + x_arrow = opt[:x][rng][[2,1,end-1,end]] + y_arrow = opt[:y][rng][[2,1,end-1,end]] + x_path = opt[:x][rng][2:end-1] + y_path = opt[:y][rng][2:end-1] + end + coordinates = PGFPlotsX.Table([ + :x => x_arrow[1:2:end-1], + :y => y_arrow[1:2:end-1], + :u => [x_arrow[i] - x_arrow[i-1] for i in 2:2:lastindex(x_arrow)], + :v => [y_arrow[i] - y_arrow[i-1] for i in 2:2:lastindex(y_arrow)], + ]) + arrow_plot = + series_func(merge(series_opt, arrow_opt), coordinates) + push!(axis, arrow_plot) + coordinates = PGFPlotsX.Table(x_path, y_path) + segment_plot = + series_func(merge(series_opt, segment_opt), coordinates) + push!(axis, segment_plot) + else + coordinates = PGFPlotsX.Table(pgfx_series_arguments(series, opt, rng)...) + segment_plot = + series_func(merge(series_opt, segment_opt), coordinates) + push!(axis, segment_plot) + end + # fill between functions + if sf isa Tuple && series[:ribbon] === nothing + sf1, sf2 = sf + @assert sf1 == series_index "First index of the tuple has to match the current series index." + push!( + axis, + series_func( + merge( + pgfx_fillstyle(opt, series_index), + PGFPlotsX.Options("forget plot" => nothing), + ), + "fill between [of=$series_id and $(_pgfplotsx_series_ids[Symbol(string(sf2))])]", + ), + ) + end + pgfx_add_legend!(axis, series, opt, k) + end # for segments + # get that last marker + if !isnothing(opt[:y]) && !any(isnan, opt[:y]) && opt[:markershape] isa AVec + additional_plot = PGFPlotsX.PlotInc(pgfx_marker(opt, length(segments) + 1), PGFPlotsX.Coordinates(tuple((last(opt[:x]), last(opt[:y]))))) + push!(axis, additional_plot) + end +end + +function pgfx_add_series!(::Val{:scatter}, axis, series_opt, series, series_func, opt) + push!(series_opt, "only marks" => nothing) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:straightline}, axis, series_opt, series, series_func, opt) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:path3d}, axis, series_opt, series, series_func, opt) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:scatter3d}, axis, series_opt, series, series_func, opt) + push!(series_opt, "only marks" => nothing) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:surface}, axis, series_opt, series, series_func, opt) + alpha = get_fillalpha(series) + push!( + series_opt, + "surf" => nothing, + "mesh/rows" => length(unique(opt[:x])), # unique if its all vectors + "mesh/cols" => length(unique(opt[:y])), + "z buffer" => "sort", + "opacity" => alpha === nothing ? 1.0 : alpha, + ) + pgfx_add_series!(axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:wireframe}, axis, series_opt, series, series_func, opt) + push!(series_opt, "mesh" => nothing, + "mesh/rows" => length(opt[:x]) + ) + pgfx_add_series!(axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:heatmap}, axis, series_opt, series, series_func, opt) + push!(axis.options, "view" => "{0}{90}") + push!( + series_opt, + "matrix plot*" => nothing, + "mesh/rows" => length(opt[:x]), + "mesh/cols" => length(opt[:y]), + "point meta" => "\\thisrow{meta}", + ) + args = pgfx_series_arguments(series, opt) + meta = [any(!isfinite, r) ? NaN : r[3] for r in zip(args...)] + for arg in args + arg[(!isfinite).(arg)] .= 0 + end + t = PGFPlotsX.Table(["x" => args[1], "y" => args[2], "z" => args[3], "meta" => meta]) + series_plot = series_func(series_opt, t) + push!(axis, series_plot) + pgfx_add_legend!(axis, series, opt) +end + +function pgfx_add_series!(::Val{:mesh3d}, axis, series_opt, series, series_func, opt) + ptable = join([ string(i, " ", j, " ", k, "\\\\") for (i, j, k) in zip(opt[:connections]...) ], "\n ") + push!( + series_opt, + "patch" => nothing, + "table/row sep" => "\\\\", + "patch table" => ptable + ) + pgfx_add_series!(axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:contour}, axis, series_opt, series, series_func, opt) + push!(axis.options, "view" => "{0}{90}") + if isfilledcontour(series) + pgfx_add_series!(Val(:filledcontour), axis, series_opt, series, series_func, opt) + return nothing + end + pgfx_add_series!(Val(:contour3d), axis, series_opt, series, series_func, opt) + return nothing +end + +function pgfx_add_series!(::Val{:filledcontour}, axis, series_opt, series, series_func, opt) + push!( + series_opt, + "contour filled" => PGFPlotsX.Options(), # labels not supported + "patch type" => "bilinear", + "shader" => "flat", + ) + if opt[:levels] isa Number + push!(series_opt["contour filled"], "number" => opt[:levels]) + elseif opt[:levels] isa AVec + push!(series_opt["contour filled"], "levels" => opt[:levels]) + end + pgfx_add_series!(axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:contour3d}, axis, series_opt, series, series_func, opt) + push!( + series_opt, + "contour prepared" => PGFPlotsX.Options("labels" => opt[:contour_labels]), + ) + series_opt = merge( series_opt, pgfx_linestyle(opt) ) + args = pgfx_series_arguments(series, opt) + series_plot = series_func(series_opt, PGFPlotsX.Table(Contour.contours(args..., opt[:levels]))) + push!(axis, series_plot) + pgfx_add_legend!(axis, series, opt) +end + +function pgfx_add_series!(::Val{:quiver}, axis, series_opt, series, series_func, opt) + if opt[:quiver] !== nothing + push!( + series_opt, + "quiver" => PGFPlotsX.Options( + "u" => "\\thisrow{u}", + "v" => "\\thisrow{v}", + pgfx_arrow(opt[:arrow]) => nothing, + ), + ) + x = opt[:x] + y = opt[:y] + z = opt[:z] + if z !== nothing + push!(series_opt["quiver"], "w" => "\\thisrow{w}") + pgfx_axis!(axis.options, series[:subplot], :z) + table = PGFPlotsX.Table([ + :x => x, + :y => y, + :z => z, + :u => opt[:quiver][1], + :v => opt[:quiver][2], + :w => opt[:quiver][3], + ]) + else + table = PGFPlotsX.Table([ + :x => x, + :y => y, + :u => opt[:quiver][1], + :v => opt[:quiver][2], + ]) + end + end + series_plot = series_func(series_opt, table) + push!(axis, series_plot) + pgfx_add_legend!(axis, series, opt) +end + +function pgfx_add_series!(::Val{:shape}, axis, series_opt, series, series_func, opt) + push!(series_opt, "area legend" => nothing) + series_opt = merge(series_opt, pgfx_fillstyle(opt)) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:steppre}, axis, series_opt, series, series_func, opt) + push!(series_opt, "const plot mark right" => nothing) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:stepmid}, axis, series_opt, series, series_func, opt) + push!(series_opt, "const plot mark mid" => nothing) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:steppost}, axis, series_opt, series, series_func, opt) + push!(series_opt, "const plot" => nothing) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:ysticks}, axis, series_opt, series, series_func, opt) + push!(series_opt, "ycomb" => nothing) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_series!(::Val{:xsticks}, axis, series_opt, series, series_func, opt) + push!(series_opt, "xcomb" => nothing) + pgfx_add_series!(Val(:path), axis, series_opt, series, series_func, opt) +end + +function pgfx_add_legend!(axis, series, opt, i = 1) + if series[:subplot][:legend] != :none + leg_entry = if opt[:label] isa AVec + get(opt[:label], i, "") + elseif opt[:label] isa AbstractString + if i == 1 + get(opt, :label, "") + else + "" + end + else + throw(ArgumentError("Malformed label. label = $(opt[:label])")) + end + if leg_entry == "" || !pgfx_should_add_to_legend(series) + push!(axis.contents[end].options, "forget plot" => nothing) + else + leg_opt = PGFPlotsX.Options() + legend = PGFPlotsX.LegendEntry(leg_opt, leg_entry, false) + push!(axis, legend) + end + end + return nothing +end + +pgfx_series_arguments(series, opt, range) = (arg[range] for arg in pgfx_series_arguments(series, opt)) +function pgfx_series_arguments(series, opt) st = series[:seriestype] - # function args - args = if st in (:contour, :contour3d) - opt[:x], opt[:y], Array(opt[:z])' + return if st in (:contour, :contour3d) + opt[:x], opt[:y], handle_surface(opt[:z]) elseif st in (:heatmap, :surface, :wireframe) surface_to_vecs(opt[:x], opt[:y], opt[:z]) elseif RecipesPipeline.is3d(st) @@ -468,155 +673,12 @@ end straightline_data(series) elseif st == :shape shape_data(series) - elseif ispolar(sp) + elseif ispolar(series) theta, r = opt[:x], opt[:y] rad2deg.(theta), r else opt[:x], opt[:y] end - seg_args = if st in (:contour, :contour3d) - args - else - (arg[rng] for arg in args) - end - if opt[:quiver] !== nothing - push!( - segment_opt, - "quiver" => PGFPlotsX.Options( - "u" => "\\thisrow{u}", - "v" => "\\thisrow{v}", - pgfx_arrow(opt[:arrow]) => nothing, - ), - ) - x, y = collect(seg_args) - return PGFPlotsX.Table([ - :x => x, - :y => y, - :u => opt[:quiver][1], - :v => opt[:quiver][2], - ]) - else - if isfilledcontour(series) - st = :filledcontour - end - pgfx_series_coordinates!(Val(st), segment_opt, opt, seg_args) - end -end -function pgfx_series_coordinates!( - st_val::Union{Val{:path},Val{:path3d},Val{:straightline}}, - segment_opt, - opt, - args, -) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!( - st_val::Union{Val{:scatter},Val{:scatter3d}}, - segment_opt, - opt, - args, -) - push!(segment_opt, "only marks" => nothing) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!(st_val::Val{:heatmap}, segment_opt, opt, args) - push!( - segment_opt, - "matrix plot*" => nothing, - "mesh/rows" => length(opt[:x]), - "mesh/cols" => length(opt[:y]), - ) - return PGFPlotsX.Table(args...) -end - -function pgfx_series_coordinates!(st_val::Val{:steppre}, segment_opt, opt, args) - push!(segment_opt, "const plot mark right" => nothing) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!(st_val::Val{:stepmid}, segment_opt, opt, args) - push!(segment_opt, "const plot mark mid" => nothing) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!(st_val::Val{:steppost}, segment_opt, opt, args) - push!(segment_opt, "const plot" => nothing) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!( - st_val::Union{Val{:ysticks},Val{:sticks}}, - segment_opt, - opt, - args, -) - push!(segment_opt, "ycomb" => nothing) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!(st_val::Val{:xsticks}, segment_opt, opt, args) - push!(segment_opt, "xcomb" => nothing) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!(st_val::Val{:surface}, segment_opt, opt, args) - push!( - segment_opt, - "surf" => nothing, - "mesh/rows" => length(opt[:x]), - "mesh/cols" => length(opt[:y]), - ) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!(st_val::Val{:volume}, segment_opt, opt, args) - push!(segment_opt, "patch" => nothing) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!(st_val::Val{:wireframe}, segment_opt, opt, args) - push!(segment_opt, "mesh" => nothing, "mesh/rows" => length(opt[:x])) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!(st_val::Val{:shape}, segment_opt, opt, args) - push!(segment_opt, "area legend" => nothing) - return PGFPlotsX.Coordinates(args...) -end -function pgfx_series_coordinates!( - st_val::Union{Val{:contour},Val{:contour3d}}, - segment_opt, - opt, - args, -) - push!( - segment_opt, - "contour prepared" => PGFPlotsX.Options("labels" => opt[:contour_labels]), - ) - return PGFPlotsX.Table(Contour.contours(args..., opt[:levels])) -end -function pgfx_series_coordinates!( - st_val::Val{:filledcontour}, - segment_opt, - opt, - args, -) - xs, ys, zs = collect(args) - push!( - segment_opt, - "contour filled" => PGFPlotsX.Options("labels" => opt[:contour_labels]), - "point meta" => "explicit", - "shader" => "flat", - ) - if opt[:levels] isa Number - push!(segment_opt["contour filled"], "number" => opt[:levels]) - elseif opt[:levels] isa AVec - push!(segment_opt["contour filled"], "levels" => opt[:levels]) - end - - cs = join( - [ - join(["($x, $y) [$(zs[j, i])]" for (j, x) in enumerate(xs)], " ") for (i, y) in enumerate(ys) - ], - "\n\n", - ) - """ - coordinates { - $cs - }; - """ end ## pgfx_get_linestyle(k) = get( @@ -655,6 +717,26 @@ pgfx_get_marker(k) = get( "*", ) +pgfx_get_xguide_pos(k) = get( + ( + top = "at={(0.5,1)},above,", + right = "at={(ticklabel* cs:1.02)}, anchor=west,", + left = "at={(ticklabel* cs:-0.02)}, anchor=east,", + ), + k, + "at={(ticklabel cs:0.5)}, anchor=near ticklabel" +) + +pgfx_get_yguide_pos(k) = get( + ( + top = "at={(ticklabel* cs:1.02)}, anchor=south", + right = "at={(1,0.5)},below,", + bottom = "at={(ticklabel* cs:-0.02)}, anchor=north,", + ), + k, + "at={(ticklabel cs:0.5)}, anchor=near ticklabel" +) + pgfx_get_legend_pos(k) = get( ( top = ("at" => string((0.5, 0.98)), "anchor" => "north"), @@ -677,11 +759,89 @@ pgfx_get_legend_pos(k) = get( Symbol(k), ("at" => string((1.02, 1)), "anchor" => "north west"), ) +pgfx_get_legend_pos(t::Tuple{S,T}) where {S<:Real,T<:Real} = ("at" => "{$(string(t))}", "anchor" => "north west") +pgfx_get_legend_pos(nt::NamedTuple) = ("at" => "{$(string(nt.at))}", "anchor" => string(nt.anchor)) +pgfx_get_legend_pos(theta::Real) = pgfx_get_legend_pos((theta,:inner)) +function pgfx_get_legend_pos(v::Tuple{S,Symbol}) where S <: Real + (s,c) = sincosd(v[1]) + anchors = [ + "south west" "south" "south east"; + "west" "center" "east"; + "north west" "north" "north east"; + ] + if v[2] === :inner + rect = (0.07,0.5,1.0,0.07,0.52,1.0) + anchor = anchors[legend_anchor_index(s),legend_anchor_index(c)] + else + rect = (-0.15,0.5,1.05,-0.15,0.52,1.1) + anchor = anchors[4-legend_anchor_index(s),4-legend_anchor_index(c)] + end + return ("at"=>"$(string(legend_pos_from_angle(v[1],rect...)))", "anchor"=>anchor) +end + +function pgfx_get_legend_style(sp) + cstr = plot_color(sp[:background_color_legend]) + a = alpha(cstr) + fg_alpha = alpha(plot_color(sp[:foreground_color_legend])) + legfont = legendfont(sp) + PGFPlotsX.Options( + pgfx_linestyle( + pgfx_thickness_scaling(sp), + sp[:foreground_color_legend], + fg_alpha, + "solid", + ) => nothing, + "fill" => cstr, + "fill opacity" => a, + "text opacity" => alpha(plot_color(sp[:legendfontcolor])), + "font" => pgfx_font( + sp[:legendfontsize], + pgfx_thickness_scaling(sp), + ), + "text" => plot_color(sp[:legendfontcolor]), + "cells" => PGFPlotsX.Options("anchor" => get((left = "west", right = "east", hcenter = "center"), legfont.halign, "west")), + pgfx_get_legend_pos(sp[:legend])..., + ) +end + +pgfx_get_colorbar_pos(s) = + get((left = " left", bottom = " horizontal", top = " horizontal"), s, "") +pgfx_get_colorbar_pos(b::Bool) = "" + +pgfx_get_title_pos(s) = + get(( + left = ("at" => "{(0,1)}", "anchor" => "south west"), + right = ("at" => "{(1,1)}", "anchor" => "south east"), + ), s, + ("at" => "{(0.5,1)}", "anchor" => "south")) +pgfx_get_title_pos(t::Tuple) = ("at" => "{$(string(t))}", "anchor" => "south") +pgfx_get_title_pos(nt::NamedTuple) = ("at" => "{$(string(nt.at))}", "anchor" => string(nt.anchor)) + +function pgfx_get_ticklabel_style(sp, axis) + cstr = plot_color(axis[:tickfontcolor]) + return PGFPlotsX.Options( + "font" => pgfx_font( + axis[:tickfontsize], pgfx_thickness_scaling(sp) + ), "color" => cstr, + "draw opacity" => alpha(cstr), + "rotate" => axis[:tickfontrotation], + ) +end + +function pgfx_get_colorbar_ticklabel_style(sp) + cstr = plot_color(sp[:colorbar_tickfontcolor]) + return PGFPlotsX.Options( + "font" => pgfx_font( + sp[:colorbar_tickfontsize], pgfx_thickness_scaling(sp) + ), "color" => cstr, + "draw opacity" => alpha(cstr), + "rotate" => sp[:colorbar_tickfontrotation], + ) +end ## -------------------------------------------------------------------------------------- -# Generates a colormap for pgfplots based on a ColorGradient pgfx_arrow(::Nothing) = "every arrow/.append style={-}" -function pgfx_arrow(arr::Arrow) +function pgfx_arrow(arr::Arrow, side = arr.side) components = String[] head = String[] push!(head, "{stealth[length = $(arr.headlength)pt, width = $(arr.headwidth)pt") @@ -690,11 +850,11 @@ function pgfx_arrow(arr::Arrow) end push!(head, "]}") head = join(head, "") - if arr.side == :both || arr.side == :tail + if side == :both || side == :tail push!(components, head) end push!(components, "-") - if arr.side == :both || arr.side == :head + if side == :both || side == :head push!(components, head) end components = join(components, "") @@ -710,6 +870,7 @@ function pgfx_filllegend!(series_opt, opt) }""") end +# Generates a colormap for pgfplots based on a ColorGradient pgfx_colormap(cl::PlotUtils.AbstractColorList) = pgfx_colormap(color_list(cl)) function pgfx_colormap(v::Vector{<:Colorant}) join(map(v) do c @@ -718,7 +879,7 @@ function pgfx_colormap(v::Vector{<:Colorant}) end function pgfx_colormap(cg::ColorGradient) join(map(1:length(cg)) do i - @sprintf("rgb(%.8fcm)=(%.8f,%.8f,%.8f)", cg.values[i], red(cg.colors[i]), green(cg.colors[i]), blue(cg.colors[i])) + @sprintf("rgb(%.8f)=(%.8f,%.8f,%.8f)", cg.values[i], red(cg.colors[i]), green(cg.colors[i]), blue(cg.colors[i])) end, "\n") end @@ -770,6 +931,13 @@ function pgfx_font(fontsize, thickness_scaling = 1, font = "\\selectfont") return string("{\\fontsize{", fs, " pt}{", 1.3fs, " pt}", font, "}") end +# If a particular fontsize parameter is `nothing`, produce a figure that doesn't specify the +# font size, and therefore uses whatever fontsize is utilised by the doc in which the +# figure is located. +function pgfx_font(fontsize::Nothing, thickness_scaling = 1, font = "\\selectfont") + return string("{", font, "}") +end + function pgfx_should_add_to_legend(series::Series) series.plotattributes[:primary] && !( @@ -802,9 +970,12 @@ function pgfx_marker(plotattributes, i = 1) pgfx_thickness_scaling(plotattributes) * 0.75 * _cycle(plotattributes[:markersize], i) + mark_freq = !any(isnan, plotattributes[:y]) && plotattributes[:markershape] isa AVec ? + length(plotattributes[:markershape]) : 1 return PGFPlotsX.Options( "mark" => shape isa Shape ? "PlotsShape$i" : pgfx_get_marker(shape), "mark size" => "$mark_size pt", + "mark repeat" => mark_freq, "mark options" => PGFPlotsX.Options( "color" => cstr_stroke, "draw opacity" => a_stroke, @@ -910,7 +1081,7 @@ end function pgfx_fillrange_series!(axis, series, series_func, i, fillrange, rng) fillrange_opt = PGFPlotsX.Options("line width" => "0", "draw opacity" => "0") fillrange_opt = merge(fillrange_opt, pgfx_fillstyle(series, i)) - fillrange_opt = merge(fillrange_opt, pgfx_marker(series, i)) + push!(fillrange_opt, "mark" => "none") # no markers on fillranges push!(fillrange_opt, "forget plot" => nothing) opt = series.plotattributes args = RecipesPipeline.is3d(series) ? (opt[:x][rng], opt[:y][rng], opt[:z][rng]) : @@ -934,7 +1105,7 @@ function pgfx_fillrange_args(fillrange, x, y, z) x_fill = [x; x[n:-1:1]; x[1]] y_fill = [y; y[n:-1:1]; x[1]] z_fill = [z; _cycle(fillrange, n:-1:1); z[1]] - return PGFPlotsX.Coordiantes(x_fill, y_fill, z_fill) + return PGFPlotsX.Coordinates(x_fill, y_fill, z_fill) end function pgfx_sanitize_string(p::PlotText) @@ -947,12 +1118,16 @@ function pgfx_sanitize_string(s::AbstractString) s = replace(s, r"\\?\&" => "\\&") s = replace(s, r"\\?\{" => "\\{") s = replace(s, r"\\?\}" => "\\}") + s = map(split(s, "")) do s + isascii(s) ? s : latexify(s) + end |> join end @require LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" begin using .LaTeXStrings function pgfx_sanitize_string(s::LaTeXString) s = replace(s, r"\\?\#" => "\\#") s = replace(s, r"\\?\%" => "\\%") + return LaTeXString(s) end end function pgfx_sanitize_plot!(plt) @@ -1018,10 +1193,10 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter) # axis label position labelpos = "" - if letter == :x && axis[:guide_position] == :top - labelpos = "at={(0.5,1)},above," - elseif letter == :y && axis[:guide_position] == :right - labelpos = "at={(1,0.5)},below," + if letter == :x + labelpos = pgfx_get_xguide_pos(axis[:guide_position]) + elseif letter == :y + labelpos = pgfx_get_yguide_pos(axis[:guide_position]) end # Add label font @@ -1109,22 +1284,20 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter) else push!(opt, string(letter, "ticklabels") => "{}") end + if axis[:tick_direction] === :none + push!( + opt, + string(letter, "tick style") => "draw=none", + ) + else + push!( + opt, + string(letter, "tick align") => + (axis[:tick_direction] == :out ? "outside" : "inside"), + ) + end push!( - opt, - string(letter, "tick align") => - (axis[:tick_direction] == :out ? "outside" : "inside"), - ) - cstr = plot_color(axis[:tickfontcolor]) - α = alpha(cstr) - push!( - opt, - string(letter, "ticklabel style") => PGFPlotsX.Options( - "font" => - pgfx_font(axis[:tickfontsize], pgfx_thickness_scaling(sp)), - "color" => cstr, - "draw opacity" => α, - "rotate" => axis[:tickfontrotation], - ), + opt, string(letter, "ticklabel style") => pgfx_get_ticklabel_style(sp, axis) ) push!( opt, @@ -1212,6 +1385,7 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter) ) end end + # -------------------------------------------------------------------------------------- # display calls this and then _display, its called 3 times for plot(1:5) # Set the (left, top, right, bottom) minimum padding around the plot area @@ -1237,7 +1411,7 @@ function _update_plot_object(plt::Plot{PGFPlotsXBackend}) plt.o(plt) end -for mime in ("application/pdf", "image/png", "image/svg+xml") +for mime in ("application/pdf", "image/svg+xml") @eval function _show( io::IO, mime::MIME{Symbol($mime)}, @@ -1248,6 +1422,18 @@ for mime in ("application/pdf", "image/png", "image/svg+xml") end end + function _show( + io::IO, + mime::MIME{Symbol("image/png")}, + plt::Plot{PGFPlotsXBackend}, + ) + plt.o.was_shown = true + plt_file = tempname() * ".png" + PGFPlotsX.pgfsave(plt_file, plt.o.the_plot; dpi=plt[:dpi]) + write(io, read(plt_file)) + rm(plt_file; force = true) + end + function _show( io::IO, mime::MIME{Symbol("application/x-tex")}, diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index 6839dc4e..457db10b 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -1,4 +1,3 @@ - # https://plot.ly/javascript/getting-started is_subplot_supported(::PlotlyBackend) = true @@ -8,7 +7,7 @@ function _plotly_framestyle(style::Symbol) return style else default_style = get((semi = :box, origin = :zerolines), style, :axes) - @warn("Framestyle :$style is not supported by Plotly and PlotlyJS. :$default_style was cosen instead.") + @warn("Framestyle :$style is not supported by Plotly and PlotlyJS. :$default_style was chosen instead.") default_style end end @@ -20,21 +19,15 @@ using UUIDs # ---------------------------------------------------------------- -plotly_legend_pos(pos::Symbol) = get( - ( - right = [1.0, 0.5], - left = [0.0, 0.5], - top = [0.5, 1.0], - bottom = [0.5, 0.0], - bottomleft = [0.0, 0.0], - bottomright = [1.0, 0.0], - topright = [1.0, 1.0], - topleft = [0.0, 1.0], - ), - pos, - [1.0, 1.0], -) -plotly_legend_pos(v::Tuple{S,T}) where {S<:Real, T<:Real} = v +function labelfunc(scale::Symbol, backend::PlotlyBackend) + texfunc = labelfunc_tex(scale) + function (x) + tex_x = texfunc(x) + sup_x = replace( tex_x, r"\^{(.*)}"=>s"\1" ) + # replace dash with \minus (U+2212) + replace(sup_x, "-" => "−") + end +end function plotly_font(font::Font, color = font.color) KW( @@ -127,16 +120,17 @@ end # this method gets the start/end in percentage of the canvas for this axis direction -function plotly_domain(sp::Subplot, letter) +function plotly_domain(sp::Subplot) figw, figh = sp.plt[:size] pcts = bbox_to_pcts(sp.plotarea, figw*px, figh*px) pcts = plotly_apply_aspect_ratio(sp, sp.plotarea, pcts) - i1,i2 = (letter == :x ? (1,3) : (2,4)) - [pcts[i1], pcts[i1]+pcts[i2]] + x_domain = [pcts[1], pcts[1] + pcts[3]] + y_domain = [pcts[2], pcts[2] + pcts[4]] + return x_domain, y_domain end -function plotly_axis(plt::Plot, axis::Axis, sp::Subplot) +function plotly_axis(axis, sp, anchor = nothing, domain = nothing) letter = axis[:letter] framestyle = sp[:framestyle] ax = KW( @@ -149,20 +143,16 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot) :zerolinecolor => rgba_string(axis[:foreground_color_axis]), :showline => framestyle in (:box, :axes) && axis[:showaxis], :linecolor => rgba_string(plot_color(axis[:foreground_color_axis])), - :ticks => axis[:tick_direction] == :out ? "outside" : "inside", + :ticks => axis[:tick_direction] === :out ? "outside" : + axis[:tick_direction] === :in ? "inside" : "", :mirror => framestyle == :box, :showticklabels => axis[:showaxis], ) - - if letter in (:x,:y) - ax[:domain] = plotly_domain(sp, letter) - if RecipesPipeline.is3d(sp) - # don't link 3d axes for synchronized interactivity - x_idx = y_idx = sp[:subplot_index] - else - x_idx, y_idx = plotly_link_indicies(plt, sp) - end - ax[:anchor] = "$(letter==:x ? "y$(y_idx)" : "x$(x_idx)")" + if anchor !== nothing + ax[:anchor] = anchor + end + if domain !== nothing + ax[:domain] = domain end ax[:tickangle] = -axis[:rotation] @@ -257,46 +247,42 @@ function plotly_layout(plt::Plot) # set to supported framestyle sp[:framestyle] = _plotly_framestyle(sp[:framestyle]) - # if any(RecipesPipeline.is3d, seriesargs) - if RecipesPipeline.is3d(sp) - azim = sp[:camera][1] - 90 #convert azimuthal to match GR behaviour - theta = 90 - sp[:camera][2] #spherical coordinate angle from z axis - plotattributes_out[:scene] = KW( - Symbol("xaxis$(spidx)") => plotly_axis(plt, sp[:xaxis], sp), - Symbol("yaxis$(spidx)") => plotly_axis(plt, sp[:yaxis], sp), - Symbol("zaxis$(spidx)") => plotly_axis(plt, sp[:zaxis], sp), - - #2.6 multiplier set camera eye such that whole plot can be seen - :camera => KW( - :eye => KW( - :x => cosd(azim)*sind(theta)*2.6, - :y => sind(azim)*sind(theta)*2.6, - :z => cosd(theta)*2.6, - ), - ), - ) - elseif ispolar(sp) + if ispolar(sp) plotattributes_out[Symbol("angularaxis$(spidx)")] = plotly_polaraxis(sp, sp[:xaxis]) plotattributes_out[Symbol("radialaxis$(spidx)")] = plotly_polaraxis(sp, sp[:yaxis]) else - plotattributes_out[Symbol("xaxis$(x_idx)")] = plotly_axis(plt, sp[:xaxis], sp) - # don't allow yaxis to be reupdated/reanchored in a linked subplot - spidx == y_idx ? plotattributes_out[Symbol("yaxis$(y_idx)")] = plotly_axis(plt, sp[:yaxis], sp) : nothing + x_domain, y_domain = plotly_domain(sp) + if RecipesPipeline.is3d(sp) + azim = sp[:camera][1] - 90 #convert azimuthal to match GR behaviour + theta = 90 - sp[:camera][2] #spherical coordinate angle from z axis + plotattributes_out[Symbol(:scene, spidx)] = KW( + :domain => KW(:x => x_domain, :y => y_domain), + Symbol("xaxis$(spidx)") => plotly_axis(sp[:xaxis], sp), + Symbol("yaxis$(spidx)") => plotly_axis(sp[:yaxis], sp), + Symbol("zaxis$(spidx)") => plotly_axis(sp[:zaxis], sp), + + #2.6 multiplier set camera eye such that whole plot can be seen + :camera => KW( + :eye => KW( + :x => cosd(azim)*sind(theta)*2.6, + :y => sind(azim)*sind(theta)*2.6, + :z => cosd(theta)*2.6, + ), + ), + ) + else + plotattributes_out[Symbol("xaxis$(x_idx)")] = + plotly_axis(sp[:xaxis], sp, string("y", y_idx) , x_domain) + # don't allow yaxis to be reupdated/reanchored in a linked subplot + if spidx == y_idx + plotattributes_out[Symbol("yaxis$(y_idx)")] = + plotly_axis(sp[:yaxis], sp, string("x", x_idx), y_domain) + end + end end # legend - plotattributes_out[:showlegend] = sp[:legend] != :none - xpos,ypos = plotly_legend_pos(sp[:legend]) - if sp[:legend] != :none - plotattributes_out[:legend] = KW( - :bgcolor => rgba_string(sp[:background_color_legend]), - :bordercolor => rgba_string(sp[:foreground_color_legend]), - :font => plotly_font(legendfont(sp)), - :tracegroupgap => 0, - :x => xpos, - :y => ypos - ) - end + plotly_add_legend!(plotattributes_out, sp) # annotations for ann in sp[:annotations] @@ -336,9 +322,89 @@ function plotly_layout(plt::Plot) plotattributes_out[:hovermode] = "none" end - plotattributes_out + plotattributes_out = recursive_merge(plotattributes_out, plt.attr[:extra_plot_kwargs]) end + +function plotly_add_legend!(plotattributes_out::KW, sp::Subplot) + plotattributes_out[:showlegend] = sp[:legend] != :none + legend_position = plotly_legend_pos(sp[:legend]) + if sp[:legend] != :none + plotattributes_out[:legend] = KW( + :bgcolor => rgba_string(sp[:background_color_legend]), + :bordercolor => rgba_string(sp[:foreground_color_legend]), + :borderwidth => 1, + :traceorder => "normal", + :xanchor => legend_position.xanchor, + :yanchor => legend_position.yanchor, + :font => plotly_font(legendfont(sp)), + :tracegroupgap => 0, + :x => legend_position.coords[1], + :y => legend_position.coords[2], + :title => KW( + :text => sp[:legendtitle] === nothing ? "" : string(sp[:legendtitle]), + :font => plotly_font(legendtitlefont(sp)), + ), + ) + end +end + +function plotly_legend_pos(pos::Symbol) + xleft = 0.07 + ybot = 0.07 + ytop = 1.0 + xcenter = 0.55 + ycenter = 0.52 + center = 0.5 + youtertop = 1.1 + youterbot = -0.15 + xouterright = 1.05 + xouterleft = -0.15 + plotly_legend_position_mapping = ( + right = (coords = [1.0, ycenter], xanchor = "right", yanchor = "middle"), + left = (coords = [xleft, ycenter], xanchor = "left", yanchor = "middle"), + top = (coords = [xcenter, ytop], xanchor = "center", yanchor = "top"), + bottom = (coords = [xcenter, ybot], xanchor = "center", yanchor = "bottom"), + bottomleft = (coords = [xleft, ybot], xanchor = "left", yanchor = "bottom"), + bottomright = (coords = [1.0, ybot], xanchor = "right", yanchor = "bottom"), + topright = (coords = [1.0, 1.0], xanchor = "right", yanchor = "top"), + topleft = (coords = [xleft, 1.0], xanchor = "left", yanchor = "top"), + outertop =(coords = [center, youtertop ], xanchor = "upper", yanchor = "middle"), + outerbottom =(coords = [center, youterbot], xanchor = "lower", yanchor = "middle"), + outerleft =(coords = [xouterleft, center], xanchor = "left", yanchor = "top"), + outerright =(coords = [xouterright, center], xanchor = "right", yanchor = "top"), + outertopleft =(coords = [xouterleft, ytop], xanchor = "upper", yanchor = "left"), + outertopright = (coords = [xouterright, ytop], xanchor = "upper", yanchor = "right"), + outerbottomleft =(coords = [xouterleft, ybot], xanchor = "lower", yanchor = "left"), + outerbottomright =(coords = [xouterright, ybot], xanchor = "lower", yanchor = "right"), + default = (coords = [1.0, 1.0], xanchor = "auto", yanchor = "auto") + ) + + legend_position = get(plotly_legend_position_mapping, pos, plotly_legend_position_mapping.default) +end + +plotly_legend_pos(v::Tuple{S,T}) where {S<:Real, T<:Real} = (coords=v, xanchor="left", yanchor="top") + +plotly_legend_pos(theta::Real) = plotly_legend_pos((theta, :inner)) + +function plotly_legend_pos(v::Tuple{S,Symbol}) where S<:Real + (s,c) = sincosd(v[1]) + xanchors = ["left", "center", "right"] + yanchors = ["bottom", "middle", "top"] + + if v[2] === :inner + rect = (0.07,0.5,1.0,0.07,0.52,1.0) + xanchor = xanchors[legend_anchor_index(c)] + yanchor = yanchors[legend_anchor_index(s)] + else + rect = (-0.15,0.5,1.05,-0.15,0.52,1.1) + xanchor = xanchors[4-legend_anchor_index(c)] + yanchor = yanchors[4-legend_anchor_index(s)] + end + return (coords=legend_pos_from_angle(v[1],rect...), xanchor=xanchor, yanchor=yanchor) +end + + function plotly_layout_json(plt::Plot) JSON.json(plotly_layout(plt), 4) end @@ -416,8 +482,8 @@ function plotly_data(series::Series, letter::Symbol, data) data end - if series[:seriestype] in (:heatmap, :contour, :surface, :wireframe) - plotly_surface_data(series, data) + if series[:seriestype] in (:heatmap, :contour, :surface, :wireframe, :mesh3d) + handle_surface(data) else plotly_data(data) end @@ -427,10 +493,6 @@ plotly_data(v::AbstractArray) = v plotly_data(surf::Surface) = surf.surf plotly_data(v::AbstractArray{R}) where {R<:Rational} = float(v) -plotly_surface_data(series::Series, a::AbstractVector) = a -plotly_surface_data(series::Series, a::AbstractMatrix) = transpose_z(series, a, false) -plotly_surface_data(series::Series, a::Surface) = plotly_surface_data(series, a.surf) - function plotly_native_data(axis::Axis, data::AbstractArray) if !isempty(axis[:discrete_values]) construct_categorical_data(data, axis) @@ -471,9 +533,17 @@ function plotly_series(plt::Plot, series::Series) plotattributes_out = KW() # these are the axes that the series should be mapped to - x_idx, y_idx = plotly_link_indicies(plt, sp) - plotattributes_out[:xaxis] = "x$(x_idx)" - plotattributes_out[:yaxis] = "y$(y_idx)" + if RecipesPipeline.is3d(sp) + spidx = length(plt.subplots) > 1 ? sp[:subplot_index] : "" + plotattributes_out[:xaxis] = "x$spidx" + plotattributes_out[:yaxis] = "y$spidx" + plotattributes_out[:zaxis] = "z$spidx" + plotattributes_out[:scene] = "scene$spidx" + else + x_idx, y_idx = length(plt.subplots) > 1 ? plotly_link_indicies(plt, sp) : ("", "") + plotattributes_out[:xaxis] = "x$(x_idx)" + plotattributes_out[:yaxis] = "y$(y_idx)" + end plotattributes_out[:showlegend] = should_add_to_legend(series) if st == :straightline @@ -523,7 +593,7 @@ function plotly_series(plt::Plot, series::Series) plotattributes_out[:showscale] = hascolorbar(sp) && hascolorbar(series) elseif st in (:surface, :wireframe) - plotattributes_out[:type] = "surface" + plotattributes_out[:type] = "surface" plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z if st == :wireframe plotattributes_out[:hidesurface] = true @@ -538,11 +608,34 @@ function plotly_series(plt::Plot, series::Series) plotattributes_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) plotattributes_out[:opacity] = series[:fillalpha] if series[:fill_z] !== nothing - plotattributes_out[:surfacecolor] = plotly_surface_data(series, series[:fill_z]) + plotattributes_out[:surfacecolor] = handle_surface(series[:fill_z]) end plotattributes_out[:showscale] = hascolorbar(sp) end + elseif st == :mesh3d + plotattributes_out[:type] = "mesh3d" + plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z + if series[:connections] !== nothing + if typeof(series[:connections]) <: Tuple{Array,Array,Array} + i,j,k = series[:connections] + if !(length(i) == length(j) == length(k)) + throw(ArgumentError("Argument connections must consist of equally sized arrays.")) + end + plotattributes_out[:i] = i + plotattributes_out[:j] = j + plotattributes_out[:k] = k + else + throw(ArgumentError("Argument connections has to be a tuple of three arrays.")) + end + end + plotattributes_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) + plotattributes_out[:color] = rgba_string(plot_color(series[:fillcolor], series[:fillalpha])) + plotattributes_out[:opacity] = series[:fillalpha] + if series[:fill_z] !== nothing + plotattributes_out[:surfacecolor] = handle_surface(series[:fill_z]) + end + plotattributes_out[:showscale] = hascolorbar(sp) else @warn("Plotly: seriestype $st isn't supported.") return KW() @@ -570,7 +663,7 @@ function plotly_series(plt::Plot, series::Series) end function plotly_series_shapes(plt::Plot, series::Series, clims) - segments = iter_segments(series) + segments = series_segments(series) plotattributes_outs = Vector{KW}(undef, length(segments)) # TODO: create a plotattributes_out for each polygon @@ -589,7 +682,8 @@ function plotly_series_shapes(plt::Plot, series::Series, clims) for (letter, data) in zip((:x, :y), shape_data(series, 100)) ) - for (i,rng) in enumerate(segments) + for (k, segment) in enumerate(segments) + i, rng = segment.attr_index, segment.range length(rng) < 2 && continue # to draw polygons, we actually draw lines with fill @@ -608,10 +702,10 @@ function plotly_series_shapes(plt::Plot, series::Series, clims) :dash => string(get_linestyle(series, i)), ) end - plotattributes_out[:showlegend] = i==1 ? should_add_to_legend(series) : false + plotattributes_out[:showlegend] = k==1 ? should_add_to_legend(series) : false plotly_polar!(plotattributes_out, series) plotly_hover!(plotattributes_out, _cycle(series[:hover], i)) - plotattributes_outs[i] = plotattributes_out + plotattributes_outs[k] = plotattributes_out end if series[:fill_z] !== nothing push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :fill)) @@ -632,12 +726,16 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z hasfillrange = st in (:path, :scatter, :scattergl, :straightline) && (isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple)) - segments = iter_segments(series) + segments = collect(series_segments(series, st)) plotattributes_outs = fill(KW(), (hasfillrange ? 2 : 1 ) * length(segments)) - for (i,rng) in enumerate(segments) + needs_scatter_fix = !isscatter && hasmarker && !any(isnan,y) && length(segments) > 1 + + for (k, segment) in enumerate(segments) + i, rng = segment.attr_index, segment.range + plotattributes_out = deepcopy(plotattributes_base) - plotattributes_out[:showlegend] = i==1 ? should_add_to_legend(series) : false + plotattributes_out[:showlegend] = k==1 ? should_add_to_legend(series) : false plotattributes_out[:legendgroup] = series[:label] # set the type @@ -671,13 +769,15 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z # add "marker" if hasmarker + mcolor = rgba_string(plot_color(get_markercolor(series, clims, i), get_markeralpha(series, i))) + lcolor = rgba_string(plot_color(get_markerstrokecolor(series, i), get_markerstrokealpha(series, i))) plotattributes_out[:marker] = KW( :symbol => get_plotly_marker(_cycle(series[:markershape], i), string(_cycle(series[:markershape], i))), - # :opacity => series[:markeralpha], + # :opacity => needs_scatter_fix ? [1, 0] : 1, :size => 2 * _cycle(series[:markersize], i), - :color => rgba_string(plot_color(get_markercolor(series, clims, i), get_markeralpha(series, i))), + :color => needs_scatter_fix ? [mcolor, "rgba(0, 0, 0, 0.000)"] : mcolor, :line => KW( - :color => rgba_string(plot_color(get_markerstrokecolor(series, i), get_markerstrokealpha(series, i))), + :color => needs_scatter_fix ? [lcolor, "rgba(0, 0, 0, 0.000)"] : lcolor, :width => _cycle(series[:markerstrokewidth], i), ), ) @@ -690,6 +790,8 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z :width => get_linewidth(series, i), :shape => if st == :steppre "vh" + elseif st == :stepmid + "hvh" elseif st == :steppost "hv" else @@ -722,16 +824,15 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z else # if fillrange is a tuple with upper and lower limit, plotattributes_out_fillrange # is the series that will do the filling - fillrng = Tuple(series[:fillrange][i][rng] for i in 1:2) - plotattributes_out_fillrange[:x], plotattributes_out_fillrange[:y] = concatenate_fillrange(x[rng], fillrng) + plotattributes_out_fillrange[:x], plotattributes_out_fillrange[:y] = concatenate_fillrange(x[rng], series[:fillrange]) plotattributes_out_fillrange[:line][:width] = 0 delete!(plotattributes_out, :fill) delete!(plotattributes_out, :fillcolor) end - plotattributes_outs[(2 * i - 1):(2 * i)] = [plotattributes_out_fillrange, plotattributes_out] + plotattributes_outs[(2k-1):(2k)] = [plotattributes_out_fillrange, plotattributes_out] else - plotattributes_outs[i] = plotattributes_out + plotattributes_outs[k] = plotattributes_out end end @@ -759,8 +860,8 @@ function plotly_colorbar_hack(series::Series, plotattributes_base::KW, sym::Symb end # zrange = zmax == zmin ? 1 : zmax - zmin # if all marker_z values are the same, plot all markers same color (avoids division by zero in next line) plotattributes_out[:marker] = KW( - :size => 0, - :opacity => 0, + :size => 1e-10, + :opacity => 1e-10, :color => [0.5], :cmin => cmin, :cmax => cmax, @@ -807,26 +908,19 @@ plotly_series_json(plt::Plot) = JSON.json(plotly_series(plt), 4) html_head(plt::Plot{PlotlyBackend}) = plotly_html_head(plt) html_body(plt::Plot{PlotlyBackend}) = plotly_html_body(plt) -const ijulia_initialized = Ref(false) - function plotly_html_head(plt::Plot) - local_file = ("file://" * plotly_local_file_path) plotly = - use_local_dependencies[] ? local_file : "https://cdn.plot.ly/plotly-latest.min.js" - if isijulia() && !ijulia_initialized[] - # using requirejs seems to be key to load a js depency in IJulia! - # https://requirejs.org/docs/start.html - # https://github.com/JuliaLang/IJulia.jl/issues/345 - display("text/html", """ - - """) - ijulia_initialized[] = true + use_local_dependencies[] ? ("file:///" * plotly_local_file_path[]) : "https://cdn.plot.ly/$(_plotly_min_js_filename)" + + include_mathjax = get(plt[:extra_plot_kwargs], :include_mathjax, "") + mathjax_file = include_mathjax != "cdn" ? ("file://" * include_mathjax) : "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML" + mathjax_head = include_mathjax == "" ? "" : "\n\t\t" + + if isijulia() + mathjax_head + else + "$mathjax_head" end - return "" end function plotly_html_body(plt, style = nothing) @@ -834,12 +928,33 @@ function plotly_html_body(plt, style = nothing) w, h = plt[:size] style = "width:$(w)px;height:$(h)px;" end + + requirejs_prefix = "" + requirejs_suffix = "" + if isijulia() + # require.js adds .js automatically + plotly_no_ext = + use_local_dependencies[] ? ("file:///" * plotly_local_file_path[]) : "https://cdn.plot.ly/$(_plotly_min_js_filename)" + plotly_no_ext = plotly_no_ext[1:end-3] + + requirejs_prefix = """ + requirejs.config({ + paths: { + Plotly: '$(plotly_no_ext)' + } + }); + require(['Plotly'], function (Plotly) { + """ + requirejs_suffix = "});" + end + uuid = UUIDs.uuid4() html = """
""" html @@ -847,7 +962,7 @@ end function js_body(plt::Plot, uuid) js = """ - PLOT = document.getElementById('$(uuid)'); + var PLOT = document.getElementById('$(uuid)'); Plotly.plot(PLOT, $(plotly_series_json(plt)), $(plotly_layout_json(plt))); """ end @@ -871,7 +986,7 @@ end function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyBackend}) - write(io, standalone_html(plt)) + write(io, embeddable_html(plt)) end diff --git a/src/backends/orca.jl b/src/backends/plotlybase.jl similarity index 54% rename from src/backends/orca.jl rename to src/backends/plotlybase.jl index 733e25a9..d0e34f2b 100644 --- a/src/backends/orca.jl +++ b/src/backends/plotlybase.jl @@ -1,14 +1,14 @@ function plotlybase_syncplot(plt::Plot) - plt.o = ORCA.PlotlyBase.Plot() - traces = ORCA.PlotlyBase.GenericTrace[] + plt.o = PlotlyBase.Plot() + traces = PlotlyBase.GenericTrace[] for series_dict in plotly_series(plt) plotly_type = pop!(series_dict, :type) - push!(traces, ORCA.PlotlyBase.GenericTrace(plotly_type; series_dict...)) + push!(traces, PlotlyBase.GenericTrace(plotly_type; series_dict...)) end - ORCA.PlotlyBase.addtraces!(plt.o, traces...) + PlotlyBase.addtraces!(plt.o, traces...) layout = plotly_layout(plt) w, h = plt[:size] - ORCA.PlotlyBase.relayout!(plt.o, layout, width = w, height = h) + PlotlyBase.relayout!(plt.o, layout, width = w, height = h) return plt.o end @@ -19,5 +19,5 @@ for (mime, fmt) in ( "image/eps" => "eps", ) @eval _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{PlotlyBackend}) = - ORCA.PlotlyBase.savefig(io, plotlybase_syncplot(plt), format = $fmt) + PlotlyBase.savefig(io, plotlybase_syncplot(plt), format = $fmt) end diff --git a/src/backends/plotlyjs.jl b/src/backends/plotlyjs.jl index f6f07e80..374bc58f 100644 --- a/src/backends/plotlyjs.jl +++ b/src/backends/plotlyjs.jl @@ -34,13 +34,12 @@ _show(io::IO, mime::MIME"application/vnd.plotly.v1+json", plt::Plot{PlotlyJSBack html_head(plt::Plot{PlotlyJSBackend}) = plotly_html_head(plt) html_body(plt::Plot{PlotlyJSBackend}) = plotly_html_body(plt) -_show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) = write(io, standalone_html(plt)) +_show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) = write(io, embeddable_html(plt)) _display(plt::Plot{PlotlyJSBackend}) = display(plotlyjs_syncplot(plt)) function PlotlyJS.WebIO.render(plt::Plot{PlotlyJSBackend}) - plt_html = sprint(show, MIME("text/html"), plt) - return PlotlyJS.WebIO.render(PlotlyJS.WebIO.dom"div"(innerHTML=plt_html)) + return PlotlyJS.WebIO.render(plotlyjs_syncplot(plt)) end function closeall(::PlotlyJSBackend) diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 97cfbef5..5e9da1e4 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -13,6 +13,7 @@ append!(Base.Multimedia.displays, otherdisplays) pycolors = PyPlot.pyimport("matplotlib.colors") pypath = PyPlot.pyimport("matplotlib.path") mplot3d = PyPlot.pyimport("mpl_toolkits.mplot3d") +axes_grid1 = PyPlot.pyimport("mpl_toolkits.axes_grid1") pypatches = PyPlot.pyimport("matplotlib.patches") pyfont = PyPlot.pyimport("matplotlib.font_manager") pyticker = PyPlot.pyimport("matplotlib.ticker") @@ -22,6 +23,8 @@ pynp."seterr"(invalid="ignore") pytransforms = PyPlot.pyimport("matplotlib.transforms") pycollections = PyPlot.pyimport("matplotlib.collections") pyart3d = PyPlot.art3D +pyrcparams = PyPlot.PyDict(PyPlot.matplotlib."rcParams") + # "support" matplotlib v1.5 set_facecolor_sym = if PyPlot.version < v"2" @@ -58,6 +61,9 @@ end # # anything else just gets a bluesred gradient # py_colormap(c, α=nothing) = py_colormap(default_gradient(), α) +py_handle_surface(v) = v +py_handle_surface(z::Surface) = z.surf + py_color(s) = py_color(parse(Colorant, string(s))) py_color(c::Colorant) = (red(c), green(c), blue(c), alpha(c)) py_color(cs::AVec) = map(py_color, cs) @@ -146,12 +152,14 @@ end function py_stepstyle(seriestype::Symbol) seriestype == :steppost && return "steps-post" + seriestype == :stepmid && return "steps-mid" seriestype == :steppre && return "steps-pre" return "default" end function py_fillstepstyle(seriestype::Symbol) seriestype == :steppost && return "post" + seriestype == :stepmid && return "mid" seriestype == :steppre && return "pre" return nothing end @@ -174,15 +182,7 @@ function add_pyfixedformatter(cbar, vals::AVec) end function labelfunc(scale::Symbol, backend::PyPlotBackend) - if scale == :log10 - x -> PyPlot.LaTeXStrings.latexstring("10^{$x}") - elseif scale == :log2 - x -> PyPlot.LaTeXStrings.latexstring("2^{$x}") - elseif scale == :ln - x -> PyPlot.LaTeXStrings.latexstring("e^{$x}") - else - string - end + PyPlot.LaTeXStrings.latexstring ∘ labelfunc_tex(scale) end function py_mask_nans(z) @@ -207,9 +207,15 @@ function fix_xy_lengths!(plt::Plot{PyPlotBackend}, series::Series) end end -py_linecolormap(series::Series) = py_colormap(series[:linecolor]) -py_markercolormap(series::Series) = py_colormap(series[:markercolor]) -py_fillcolormap(series::Series) = py_colormap(series[:fillcolor]) +function py_linecolormap(series::Series) + py_colormap(cgrad(series[:linecolor], alpha=get_linealpha(series))) +end +function py_markercolormap(series::Series) + py_colormap(cgrad(series[:markercolor], alpha=get_markeralpha(series))) +end +function py_fillcolormap(series::Series) + py_colormap(cgrad(series[:fillcolor], alpha=get_fillalpha(series))) +end # --------------------------------------------------------------------------- @@ -275,7 +281,6 @@ end function py_bbox_axis(ax, letter) ticks = py_bbox_ticks(ax, letter) labels = py_bbox_axislabel(ax, letter) - # letter == "x" && @show ticks labels ticks+labels ticks + labels end @@ -350,7 +355,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) fix_xy_lengths!(plt, series) # ax = getAxis(plt, series) - x, y, z = series[:x], series[:y], series[:z] + x, y, z = (py_handle_surface(series[letter]) for letter in (:x, :y, :z)) if st == :straightline x, y = straightline_data(series) elseif st == :shape @@ -375,8 +380,10 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) vmin, vmax = clims = get_clims(sp, series) # Dict to store extra kwargs - if st == :wireframe - extrakw = KW() # vmin, vmax cause an error for wireframe plot + if st == :wireframe || st == :hexbin + # vmin, vmax cause an error for wireframe plot + # We are not supporting clims for hexbin as calculation of bins is not trivial + extrakw = KW() else extrakw = KW(:vmin => vmin, :vmax => vmax) end @@ -403,9 +410,8 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) # for each plotting command, optionally build and add a series handle to the list # line plot - if st in (:path, :path3d, :steppre, :steppost, :straightline) + if st in (:path, :path3d, :steppre, :stepmid, :steppost, :straightline) if maximum(series[:linewidth]) > 0 - segments = iter_segments(series) # TODO: check LineCollection alternative for speed # if length(segments) > 1 && (any(typeof(series[attr]) <: AbstractVector for attr in (:fillcolor, :fillalpha)) || series[:fill_z] !== nothing) && !(typeof(series[:linestyle]) <: AbstractVector) # # multicolored line segments @@ -436,18 +442,20 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) # end # push!(handles, handle) # else - for (i, rng) in enumerate(iter_segments(series)) - handle = ax."plot"((arg[rng] for arg in xyargs)...; - label = i == 1 ? series[:label] : "", - zorder = series[:series_plotindex], - color = py_color(single_color(get_linecolor(series, clims, i)), get_linealpha(series, i)), - linewidth = py_thickness_scale(plt, get_linewidth(series, i)), - linestyle = py_linestyle(st, get_linestyle(series, i)), - solid_capstyle = "round", - drawstyle = py_stepstyle(st) - )[1] - push!(handles, handle) - end + for (k, segment) in enumerate(series_segments(series, st)) + i, rng = segment.attr_index, segment.range + handle = ax."plot"((arg[rng] for arg in xyargs)...; + label = k == 1 ? series[:label] : "", + zorder = series[:series_plotindex], + color = py_color(single_color(get_linecolor(series, clims, i)), get_linealpha(series, i)), + linewidth = py_thickness_scale(plt, get_linewidth(series, i)), + linestyle = py_linestyle(st, get_linestyle(series, i)), + solid_capstyle = "butt", + dash_capstyle = "butt", + drawstyle = py_stepstyle(st) + )[1] + push!(handles, handle) + end # end a = series[:arrow] @@ -478,124 +486,33 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) end # add markers? - if series[:markershape] != :none && st in (:path, :scatter, :path3d, - :scatter3d, :steppre, :steppost, - :bar) - markercolor = if any(typeof(series[arg]) <: AVec for arg in (:markercolor, :markeralpha)) || series[:marker_z] !== nothing - # py_color(plot_color.(get_markercolor.(series, clims, eachindex(x)), get_markeralpha.(series, eachindex(x)))) - [py_color(plot_color(get_markercolor(series, clims, i), get_markeralpha(series, i))) for i in eachindex(x)] - else - py_color(plot_color(series[:markercolor], series[:markeralpha])) - end - extrakw[:c] = if markercolor isa Array - permutedims(hcat([[m...] for m in markercolor]...),[2,1]) - elseif markercolor isa Tuple - reshape([markercolor...], 1, length(markercolor)) - else - error("This case is not handled. Please file an issue.") - end - xyargs = if st == :bar && !isvertical(series) - (y, x) - else - xyargs - end - - if isa(series[:markershape], AbstractVector{Shape}) - # this section will create one scatter per data point to accommodate the - # vector of shapes - handle = [] - x,y = xyargs - shapes = series[:markershape] - msc = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)) - lw = py_thickness_scale(plt, series[:markerstrokewidth]) - for i=eachindex(y) - if series[:marker_z] !== nothing - extrakw[:c] = [py_color(get_markercolor(series, i), get_markercoloralpha(series, i))] + if series[:markershape] != :none && st in ( + :path, :scatter, :path3d, :scatter3d, :steppre, :stepmid, :steppost, :bar + ) + for segment in series_segments(series, :scatter) + i, rng = segment.attr_index, segment.range + xyargs = if st == :bar && !isvertical(series) + if RecipesPipeline.is3d(sp) + y[rng], x[rng], z[rng] + else + y[rng], x[rng] end - - push!(handle, ax."scatter"(_cycle(x,i), _cycle(y,i); - label = series[:label], - zorder = series[:series_plotindex] + 0.5, - marker = py_marker(_cycle(shapes,i)), - s = py_thickness_scale(plt, _cycle(series[:markersize],i)).^ 2, - facecolors = py_color(get_markercolor(series, i), get_markercoloralpha(series, i)), - edgecolors = msc, - linewidths = lw, - extrakw... - )) - end - push!(handles, handle) - elseif isa(series[:markershape], AbstractVector{Symbol}) - handle = [] - x,y = xyargs - shapes = series[:markershape] - - prev_marker = py_marker(_cycle(shapes,1)) - - cur_x_list = [] - cur_y_list = [] - - cur_color_list = [] - cur_scale_list = [] - - delete!(extrakw, :c) - - for i=eachindex(y) - cur_marker = py_marker(_cycle(shapes,i)) - - if ( cur_marker == prev_marker ) - push!(cur_x_list, _cycle(x,i)) - push!(cur_y_list, _cycle(y,i)) - - push!(cur_color_list, _cycle(markercolor, i)) - push!(cur_scale_list, py_thickness_scale(plt, _cycle(series[:markersize],i)).^ 2) - - continue + else + if RecipesPipeline.is3d(sp) + x[rng], y[rng], z[rng] + else + x[rng], y[rng] end - - push!(handle, ax."scatter"(cur_x_list, cur_y_list; - label = series[:label], - zorder = series[:series_plotindex] + 0.5, - marker = prev_marker, - s = cur_scale_list, - edgecolors = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)), - linewidths = py_thickness_scale(plt, series[:markerstrokewidth]), - facecolors = cur_color_list, - extrakw... - )) - - cur_x_list = [_cycle(x,i)] - cur_y_list = [_cycle(y,i)] - - cur_color_list = [_cycle(markercolor, i)] - cur_scale_list = [py_thickness_scale(plt, _cycle(series[:markersize],i)) .^ 2] - - prev_marker = cur_marker end - if !isempty(cur_color_list) - push!(handle, ax."scatter"(cur_x_list, cur_y_list; - label = series[:label], - zorder = series[:series_plotindex] + 0.5, - marker = prev_marker, - s = cur_scale_list, - edgecolors = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)), - linewidths = py_thickness_scale(plt, series[:markerstrokewidth]), - facecolors = cur_color_list, - extrakw... - )) - end - - push!(handles, handle) - else - # do a normal scatter plot handle = ax."scatter"(xyargs...; label = series[:label], zorder = series[:series_plotindex] + 0.5, - marker = py_marker(series[:markershape]), - s = py_thickness_scale(plt, series[:markersize]) .^2, - edgecolors = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)), - linewidths = py_thickness_scale(plt, series[:markerstrokewidth]), + marker = py_marker(_cycle(series[:markershape], i)), + s = py_thickness_scale(plt, _cycle(series[:markersize], i)).^ 2, + facecolors = py_color(get_markercolor(series, i), get_markeralpha(series, i)), + edgecolors = py_color(get_markerstrokecolor(series, i), get_markerstrokealpha(series, i)), + linewidths = py_thickness_scale(plt, get_markerstrokewidth(series, i)), extrakw... ) push!(handles, handle) @@ -603,31 +520,27 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) end if st == :hexbin - handle = ax."hexbin"(x, y, + extrakw[:mincnt] = get(series[:extra_kwargs], :mincnt, nothing) + extrakw[:edgecolors] = get(series[:extra_kwargs], :edgecolors, py_color(get_linecolor(series))) + handle = ax."hexbin"(x, y; label = series[:label], C = series[:weights], gridsize = series[:bins]==:auto ? 100 : series[:bins], # 100 is the default value linewidths = py_thickness_scale(plt, series[:linewidth]), - edgecolors = py_color(get_linecolor(series)), alpha = series[:fillalpha], cmap = py_fillcolormap(series), # applies to the pcolorfast object zorder = series[:series_plotindex], - # extrakw... # for some reason vmin and vmax are NaN??? + extrakw... ) push!(handles, handle) end if st in (:contour, :contour3d) - z = transpose_z(series, z.surf) - if typeof(x)<:Plots.Surface - x = Plots.transpose_z(series, x.surf) - end - if typeof(y)<:Plots.Surface - y = Plots.transpose_z(series, y.surf) - end - if st == :contour3d extrakw[:extend3d] = true + if !ismatrix(x) || !ismatrix(y) + x, y = repeat(x', length(y), 1), repeat(y, 1, length(x)) + end end if typeof(series[:linecolor]) <: AbstractArray @@ -654,6 +567,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) handle = ax."contourf"(x, y, z, levelargs...; label = series[:label], zorder = series[:series_plotindex] + 0.5, + alpha = series[:fillalpha], extrakw... ) push!(handles, handle) @@ -661,17 +575,17 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) end if st in (:surface, :wireframe) - if typeof(z) <: AbstractMatrix || typeof(z) <: Surface - x, y, z = map(Array, (x,y,z)) + if z isa AbstractMatrix if !ismatrix(x) || !ismatrix(y) - x = repeat(x', length(y), 1) - y = repeat(y, 1, length(series[:x])) + x, y = repeat(x', length(y), 1), repeat(y, 1, length(x)) end - z = transpose_z(series, z) if st == :surface if series[:fill_z] !== nothing # the surface colors are different than z-value - extrakw[:facecolors] = py_shading(series[:fillcolor], transpose_z(series, series[:fill_z].surf)) + extrakw[:facecolors] = py_shading( + series[:fillcolor], + py_handle_surface(series[:fill_z]), + ) extrakw[:shade] = false else extrakw[:cmap] = py_fillcolormap(series) @@ -719,22 +633,24 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) end if st == :image - # @show typeof(z) - xmin, xmax = ignorenan_extrema(series[:x]); ymin, ymax = ignorenan_extrema(series[:y]) - img = Array(transpose_z(series, z.surf)) - z = if eltype(img) <: Colors.AbstractGray - float(img) - elseif eltype(img) <: Colorant - map(c -> Float64[red(c),green(c),blue(c),alpha(c)], img) + xmin, xmax = ignorenan_extrema(series[:x]) + ymin, ymax = ignorenan_extrema(series[:y]) + dx = (xmax - xmin) / (length(series[:x]) - 1) / 2 + dy = (ymax - ymin) / (length(series[:y]) - 1) / 2 + z = if eltype(z) <: Colors.AbstractGray + float(z) + elseif eltype(z) <: Colorant + map(c -> Float64[red(c),green(c),blue(c),alpha(c)], z) else z # hopefully it's in a data format that will "just work" with imshow end - handle = ax."imshow"(z; + handle = ax."imshow"( + z; zorder = series[:series_plotindex], cmap = py_colormap(cgrad(plot_color([:black, :white]))), vmin = 0.0, vmax = 1.0, - extent = (xmin-0.5, xmax+0.5, ymax+0.5, ymin-0.5) + extent = (xmin - dx, xmax + dx, ymax + dy, ymin - dy) ) push!(handles, handle) @@ -745,7 +661,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) end if st == :heatmap - x, y, z = heatmap_edges(x, sp[:xaxis][:scale]), heatmap_edges(y, sp[:yaxis][:scale]), transpose_z(series, z.surf) + x, y = heatmap_edges(x, sp[:xaxis][:scale], y, sp[:yaxis][:scale], size(z)) expand_extrema!(sp[:xaxis], x) expand_extrema!(sp[:yaxis], y) @@ -767,7 +683,8 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) if st == :shape handle = [] - for (i, rng) in enumerate(iter_segments(series)) + for segment in series_segments(series) + i, rng = segment.attr_index, segment.range if length(rng) > 1 path = pypath."Path"(hcat(x[rng], y[rng])) patches = pypatches."PathPatch"( @@ -794,7 +711,8 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) # handle area filling fillrange = series[:fillrange] if fillrange !== nothing && st != :contour - for (i, rng) in enumerate(iter_segments(series)) + for segment in series_segments(series) + i, rng = segment.attr_index, segment.range f, dim1, dim2 = if isvertical(series) :fill_between, x[rng], y[rng] else @@ -831,7 +749,17 @@ function py_set_lims(ax, sp::Subplot, axis::Axis) getproperty(ax, Symbol("set_", letter, "lim"))(lfrom, lto) end -function py_set_ticks(ax, ticks, letter) +function py_surround_latextext(latexstring, env) + if !isempty(latexstring) && latexstring[1] == '$' && latexstring[end] == '$' + unenclosed = latexstring[2:end-1] + else + unenclosed = latexstring + end + PyPlot.LaTeXStrings.latexstring(env, "{", unenclosed, "}") +end + + +function py_set_ticks(sp, ax, ticks, letter, env) ticks == :auto && return axis = getproperty(ax, Symbol(letter,"axis")) if ticks == :none || ticks === nothing || ticks == false @@ -848,7 +776,14 @@ function py_set_ticks(ax, ticks, letter) axis."set_ticks"(ticks) elseif ttype == :ticks_and_labels axis."set_ticks"(ticks[1]) - axis."set_ticklabels"(ticks[2]) + + if get(sp[:extra_kwargs], :rawticklabels, false) + tick_labels = ticks[2] + else + tick_labels = [py_surround_latextext(ticklabel, env) for ticklabel in ticks[2]] + end + + axis."set_ticklabels"(tick_labels) else error("Invalid input for $(letter)ticks: $ticks") end @@ -874,33 +809,52 @@ function py_compute_axis_minval(sp::Subplot, axis::Axis) minval end -function py_set_scale(ax, sp::Subplot, axis::Axis) - scale = axis[:scale] - letter = axis[:letter] +function py_set_scale(ax, sp::Subplot, scale::Symbol, letter::Symbol) scale in supported_scales() || return @warn("Unhandled scale value in pyplot: $scale") func = getproperty(ax, Symbol("set_", letter, "scale")) + if PyPlot.version ≥ v"3.3" # https://matplotlib.org/3.3.0/api/api_changes.html + pyletter = Symbol("") + else + pyletter = letter + end kw = KW() arg = if scale == :identity "linear" else - kw[Symbol(:base,letter)] = if scale == :ln + kw[Symbol(:base, pyletter)] = if scale == :ln ℯ elseif scale == :log2 2 elseif scale == :log10 10 end - kw[Symbol(:linthresh,letter)] = NaNMath.max(1e-16, py_compute_axis_minval(sp, axis)) + axis = sp[Symbol(letter, :axis)] + kw[Symbol(:linthresh, pyletter)] = NaNMath.max(1e-16, py_compute_axis_minval(sp, axis)) "symlog" end func(arg; kw...) end +function py_set_scale(ax, sp::Subplot, axis::Axis) + scale = axis[:scale] + letter = axis[:letter] + py_set_scale(ax, sp, scale, letter) +end + +function py_set_spine_color(spines, color) + for loc in spines + spines[loc]."set_color"(color) + end +end + +function py_set_spine_color(spines::Dict, color) + for (loc, spine) in spines + spine."set_color"(color) + end +end function py_set_axis_colors(sp, ax, a::Axis) - for (loc, spine) in ax.spines - spine."set_color"(py_color(a[:foreground_color_border])) - end + py_set_spine_color(ax.spines, py_color(a[:foreground_color_border])) axissym = Symbol(a[:letter], :axis) if PyPlot.PyCall.hasproperty(ax, axissym) tickcolor = sp[:framestyle] in (:zerolines, :grid) ? py_color(plot_color(a[:foreground_color_grid], a[:gridalpha])) : py_color(a[:foreground_color_axis]) @@ -997,47 +951,142 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) end kw[:spacing] = "proportional" - # create and store the colorbar object (handle) and the axis that it is drawn on. - # note: the colorbar axis is positioned independently from the subplot axis - fig = plt.o - cbax = fig."add_axes"([0.8,0.1,0.03,0.8], label = string(gensym())) - cb = fig."colorbar"(handle; cax = cbax, kw...) - cb."set_label"(sp[:colorbar_title],size=py_thickness_scale(plt, sp[:yaxis][:guidefontsize]),family=sp[:yaxis][:guidefontfamily], color = py_color(sp[:yaxis][:guidefontcolor])) - for lab in cb."ax"."yaxis"."get_ticklabels"() - lab."set_fontsize"(py_thickness_scale(plt, sp[:yaxis][:tickfontsize])) - lab."set_family"(sp[:yaxis][:tickfontfamily]) - lab."set_color"(py_color(sp[:yaxis][:tickfontcolor])) + if RecipesPipeline.is3d(sp) || ispolar(sp) + cbax = fig."add_axes"([0.9, 0.1, 0.03, 0.8], label=string("cbar", sp[:subplot_index])) + cb = fig."colorbar"(handle; cax=cbax, kw...) + else + # divider approach works only with 2d plots + divider = axes_grid1.make_axes_locatable(ax) + # width = axes_grid1.axes_size.AxesY(ax, aspect=1.0 / 3.5) + # pad = axes_grid1.axes_size.Fraction(0.5, width) # Colorbar is spaced 0.5 of its size away from the ax + # cbax = divider.append_axes("right", size=width, pad=pad) # This approach does not work well in subplots + colorbar_position = "right" + colorbar_pad = "2.5%" + colorbar_orientation="vertical" + + if sp[:colorbar] == :left + colorbar_position = string(sp[:colorbar]) + colorbar_pad = "5%" + elseif sp[:colorbar] == :top + colorbar_position = string(sp[:colorbar]) + colorbar_pad = "2.5%" + colorbar_orientation="horizontal" + elseif sp[:colorbar] == :bottom + colorbar_position = string(sp[:colorbar]) + colorbar_pad = "5%" + colorbar_orientation="horizontal" + end + + cbax = divider.append_axes(colorbar_position, size="5%", pad=colorbar_pad, label=string("cbar", sp[:subplot_index])) # Reasonable value works most of the usecases + cb = fig."colorbar"(handle; cax=cbax, orientation = colorbar_orientation, kw...) + + if sp[:colorbar] == :left + cbax.yaxis.set_ticks_position("left") + elseif sp[:colorbar] == :top + cbax.xaxis.set_ticks_position("top") + elseif sp[:colorbar] == :bottom + cbax.xaxis.set_ticks_position("bottom") + end + end + + cb."set_label"(sp[:colorbar_title],size=py_thickness_scale(plt, sp[:colorbar_titlefontsize]),family=sp[:colorbar_titlefontfamily], color = py_color(sp[:colorbar_titlefontcolor])) + + # cb."formatter".set_useOffset(false) # This for some reason does not work, must be a pyplot bug, instead this is a workaround: + cb."formatter".set_powerlimits((-Inf, Inf)) + cb."update_ticks"() + + env = "\\mathregular" # matches the outer fonts https://matplotlib.org/tutorials/text/mathtext.html + ticks = get_colorbar_ticks(sp) + + if sp[:colorbar] in (:top, :bottom) + axis = sp[:xaxis] # colorbar inherits from x axis + cbar_axis = cb."ax"."xaxis" + ticks_letter=:x + else + axis = sp[:yaxis] # colorbar inherits from y axis + cbar_axis = cb."ax"."yaxis" + ticks_letter=:y + end + py_set_scale(cb.ax, sp, sp[:colorbar_scale], ticks_letter) + sp[:colorbar_ticks] == :native ? nothing : py_set_ticks(sp, cb.ax, ticks, ticks_letter, env) + + for lab in cbar_axis."get_ticklabels"() + lab."set_fontsize"(py_thickness_scale(plt, sp[:colorbar_tickfontsize])) + lab."set_family"(sp[:colorbar_tickfontfamily]) + lab."set_color"(py_color(sp[:colorbar_tickfontcolor])) + end + + # Adjust thickness of the cbar ticks + intensity = 0.5 + cbar_axis."set_tick_params"( + direction = axis[:tick_direction] == :out ? "out" : "in", + width=py_thickness_scale(plt, intensity), + length = axis[:tick_direction] == :none ? 0 : 5 * py_thickness_scale(plt, intensity) + ) + + + + cb.outline."set_linewidth"(py_thickness_scale(plt, 1)) + sp.attr[:cbar_handle] = cb sp.attr[:cbar_ax] = cbax end + # framestyle if !ispolar(sp) && !RecipesPipeline.is3d(sp) - ax.spines["left"]."set_linewidth"(py_thickness_scale(plt, 1)) - ax.spines["bottom"]."set_linewidth"(py_thickness_scale(plt, 1)) + for pos in ("left", "right", "top", "bottom") + # Scale all axes by default first + ax.spines[pos]."set_linewidth"(py_thickness_scale(plt, 1)) + end + + # Then set visible some of them if sp[:framestyle] == :semi intensity = 0.5 - ax.spines["right"]."set_alpha"(intensity) - ax.spines["top"]."set_alpha"(intensity) - ax.spines["right"]."set_linewidth"(py_thickness_scale(plt, intensity)) - ax.spines["top"]."set_linewidth"(py_thickness_scale(plt, intensity)) + + spine = sp[:yaxis][:mirror] ? "left" : "right" + ax.spines[spine]."set_alpha"(intensity) + ax.spines[spine]."set_linewidth"(py_thickness_scale(plt, intensity)) + + spine = sp[:xaxis][:mirror] ? "bottom" : "top" + ax.spines[spine]."set_linewidth"(py_thickness_scale(plt, intensity)) + ax.spines[spine]."set_alpha"(intensity) + elseif sp[:framestyle] == :box + ax.tick_params(top=true) # Add ticks too + ax.tick_params(right=true) # Add ticks too elseif sp[:framestyle] in (:axes, :origin) - ax.spines["right"]."set_visible"(false) - ax.spines["top"]."set_visible"(false) + sp[:xaxis][:mirror] ? ax.spines["bottom"]."set_visible"(false) : ax.spines["top"]."set_visible"(false) + sp[:yaxis][:mirror] ? ax.spines["left"]."set_visible"(false) : ax.spines["right"]."set_visible"(false) if sp[:framestyle] == :origin ax.spines["bottom"]."set_position"("zero") ax.spines["left"]."set_position"("zero") end elseif sp[:framestyle] in (:grid, :none, :zerolines) - for (loc, spine) in ax.spines - spine."set_visible"(false) + if PyPlot.version >= v"3.4.1" # that is one where it worked, the API change may have some other value + for spine in ax.spines + ax.spines[string(spine)]."set_visible"(false) + end + else + for (loc, spine) in ax.spines + spine."set_visible"(false) + end end if sp[:framestyle] == :zerolines ax."axhline"(y = 0, color = py_color(sp[:xaxis][:foreground_color_axis]), lw = py_thickness_scale(plt, 0.75)) ax."axvline"(x = 0, color = py_color(sp[:yaxis][:foreground_color_axis]), lw = py_thickness_scale(plt, 0.75)) end end + + if sp[:xaxis][:mirror] + ax.xaxis."set_label_position"("top") # the guides + sp[:framestyle] == :box ? nothing : ax.xaxis."tick_top"() + end + + if sp[:yaxis][:mirror] + ax.yaxis."set_label_position"("right") # the guides + sp[:framestyle] == :box ? nothing : ax.yaxis."tick_right"() + end end # axis attributes @@ -1046,17 +1095,13 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) PyPlot.PyCall.hasproperty(ax, axissym) || continue axis = sp[axissym] pyaxis = getproperty(ax, axissym) - if axis[:mirror] && letter != :z - pos = letter == :x ? "top" : "right" - pyaxis."set_label_position"(pos) # the guides - pyaxis."set_ticks_position"("both") # the hash marks - getproperty(pyaxis, Symbol(:tick_, pos))() # the tick labels - end + if axis[:guide_position] != :auto && letter != :z pyaxis."set_label_position"(axis[:guide_position]) end + py_set_scale(ax, sp, axis) - axis[:ticks] != :native ? py_set_lims(ax, sp, axis) : nothing + axis[:ticks] == :native ? nothing : py_set_lims(ax, sp, axis) if ispolar(sp) && letter == :y ax."set_rlabel_position"(90) end @@ -1065,10 +1110,41 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) if sp[:framestyle] == :origin && length(ticks) > 1 ticks[2][ticks[1] .== 0] .= "" end - axis[:ticks] != :native ? py_set_ticks(ax, ticks, letter) : nothing + # Set ticks + fontProperties = PyPlot.PyCall.PyDict( + Dict( + "family" => axis[:tickfontfamily], + "size" => py_thickness_scale(plt, axis[:tickfontsize]), + "rotation" => axis[:tickfontrotation], + ) + ) + + positions = getproperty(ax, Symbol("get_",letter,"ticks"))() + pyaxis.set_major_locator(pyticker.FixedLocator(positions)) + if RecipesPipeline.is3d(sp) + getproperty(ax, Symbol("set_",letter,"ticklabels"))( + positions; + (Symbol(k) => v for (k, v) in fontProperties)... + ) + else + getproperty(ax, Symbol("set_",letter,"ticklabels"))( + positions, + fontdict=fontProperties, + ) + end + + # workaround to set mathtext.fontspec per Text element + env = "\\mathregular" # matches the outer fonts https://matplotlib.org/tutorials/text/mathtext.html + + axis[:ticks] == :native ? nothing : py_set_ticks(sp, ax, ticks, letter, env) + # Tick marks intensity = 0.5 # This value corresponds to scaling of other grid elements - pyaxis."set_tick_params"(direction = axis[:tick_direction] == :out ? "out" : "in", width=py_thickness_scale(plt, intensity)) + pyaxis."set_tick_params"( + direction = axis[:tick_direction] == :out ? "out" : "in", + width=py_thickness_scale(plt, intensity), + length = axis[:tick_direction] == :none ? 0 : 5 * py_thickness_scale(plt, intensity) + ) getproperty(ax, Symbol("set_", letter, "label"))(axis[:guide]) if get(axis.plotattributes, :flip, false) @@ -1076,7 +1152,7 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) end pyaxis."label"."set_fontsize"(py_thickness_scale(plt, axis[:guidefontsize])) pyaxis."label"."set_family"(axis[:guidefontfamily]) - + if (RecipesPipeline.is3d(sp)) pyaxis."set_rotate_label"(false) end @@ -1087,11 +1163,7 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) pyaxis."label"."set_rotation"(axis[:guidefontrotation]) end - for lab in getproperty(ax, Symbol("get_", letter, "ticklabels"))() - lab."set_fontsize"(py_thickness_scale(plt, axis[:tickfontsize])) - lab."set_family"(axis[:tickfontfamily]) - lab."set_rotation"(axis[:rotation]) - end + if axis[:grid] && !(ticks in (:none, nothing, false)) fgcolor = py_color(axis[:foreground_color_grid]) pyaxis."grid"(true, @@ -1110,7 +1182,8 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) pyaxis."set_tick_params"( which = "minor", direction = axis[:tick_direction] == :out ? "out" : "in", - width=py_thickness_scale(plt, intensity)) + length = axis[:tick_direction] == :none ? 0 : py_thickness_scale(plt, intensity), + ) end if axis[:minorgrid] @@ -1120,7 +1193,8 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) pyaxis."set_tick_params"( which = "minor", direction = axis[:tick_direction] == :out ? "out" : "in", - width=py_thickness_scale(plt, intensity)) + length = axis[:tick_direction] == :none ? 0 : py_thickness_scale(plt, intensity) + ) pyaxis."grid"(true, which = "minor", @@ -1202,6 +1276,7 @@ function _update_min_padding!(sp::Subplot{PyPlotBackend}) toppad = 0mm rightpad = 0mm bottompad = 0mm + for bb in (py_bbox_axis(ax, "x"), py_bbox_axis(ax, "y"), py_bbox_title(ax), py_bbox_legend(ax)) if ispositive(width(bb)) && ispositive(height(bb)) leftpad = max(leftpad, left(plotbb) - left(bb)) @@ -1211,11 +1286,23 @@ function _update_min_padding!(sp::Subplot{PyPlotBackend}) end end + if haskey(sp.attr, :cbar_ax) # Treat colorbar the same way + ax = sp.attr[:cbar_handle]."ax" + for bb in (py_bbox_axis(ax, "x"), py_bbox_axis(ax, "y"), py_bbox_title(ax), ) + if ispositive(width(bb)) && ispositive(height(bb)) + leftpad = max(leftpad, left(plotbb) - left(bb)) + toppad = max(toppad, top(plotbb) - top(bb)) + rightpad = max(rightpad, right(bb) - right(plotbb)) + bottompad = max(bottompad, bottom(bb) - bottom(plotbb)) + end + end + end + + # optionally add the width of colorbar labels and colorbar to rightpad - if haskey(sp.attr, :cbar_ax) + if RecipesPipeline.is3d(sp) && haskey(sp.attr, :cbar_ax) bb = py_bbox(sp.attr[:cbar_handle]."ax"."get_yticklabels"()) - sp.attr[:cbar_width] = _cbar_width + width(bb) + 2.3mm + (sp[:colorbar_title] == "" ? 0px : 30px) - rightpad = rightpad + sp.attr[:cbar_width] + sp.attr[:cbar_width] = width(bb) + (sp[:colorbar_title] == "" ? 0px : 30px) end # add in the user-specified margin @@ -1255,44 +1342,26 @@ end # ----------------------------------------------------------------- -py_legend_pos(pos::Symbol) = get( - ( - right = "right", - left = "center left", - top = "upper center", - bottom = "lower center", - bottomleft = "lower left", - bottomright = "lower right", - topright = "upper right", - topleft = "upper left", - outerright = "center left", - outerleft = "right", - outertop = "lower center", - outerbottom = "upper center", - outerbottomleft = "lower right", - outerbottomright = "lower left", - outertopright = "upper left", - outertopleft = "upper right", - ), - pos, - "best", -) -py_legend_pos(pos) = "lower left" +py_legend_pos(pos::Tuple{S,T}) where {S<:Real,T<:Real} = "lower left" + +function py_legend_pos(pos::Tuple{<:Real,Symbol}) + (s,c) = sincosd(pos[1]) + if pos[2] === :outer + s = -s + c = -c + end + yanchors = ["lower","center","upper"] + xanchors = ["left","center","right"] + return join([yanchors[legend_anchor_index(s)], xanchors[legend_anchor_index(c)]], ' ') +end + +function py_legend_bbox(pos::Tuple{T,Symbol}) where T<:Real + if pos[2] === :outer + return legend_pos_from_angle(pos[1],-0.15,0.5,1.0,-0.15,0.5,1.0) + end + legend_pos_from_angle(pos[1],0.0,0.5,1.0,0.0,0.5,1.0) +end -py_legend_bbox(pos::Symbol) = get( - ( - outerright = (1.0, 0.5, 0.0, 0.0), - outerleft = (-0.15, 0.5, 0.0, 0.0), - outertop = (0.5, 1.0, 0.0, 0.0), - outerbottom = (0.5, -0.15, 0.0, 0.0), - outerbottomleft = (-0.15, 0.0, 0.0, 0.0), - outerbottomright = (1.0, 0.0, 0.0, 0.0), - outertopright = (1.0, 1.0, 0.0, 0.0), - outertopleft = (-0.15, 1.0, 0.0, 0.0), - ), - pos, - (0.0, 0.0, 1.0, 1.0), -) py_legend_bbox(pos) = pos function py_add_legend(plt::Plot, sp::Subplot, ax) @@ -1311,14 +1380,17 @@ function py_add_legend(plt::Plot, sp::Subplot, ax) edgecolor = py_color(single_color(get_linecolor(series, clims)), get_linealpha(series)), facecolor = py_color(single_color(get_fillcolor(series, clims)), get_fillalpha(series)), linewidth = py_thickness_scale(plt, clamp(get_linewidth(series), 0, 5)), - linestyle = py_linestyle(series[:seriestype], get_linestyle(series)) + linestyle = py_linestyle(series[:seriestype], get_linestyle(series)), + capstyle = "butt" ) - elseif series[:seriestype] in (:path, :straightline, :scatter, :steppre, :steppost) + elseif series[:seriestype] in (:path, :straightline, :scatter, :steppre, :stepmid, :steppost) hasline = get_linewidth(series) > 0 - PyPlot.plt."Line2D"((0,1),(0,0), + PyPlot.plt."Line2D"((0, 1),(0,0), color = py_color(single_color(get_linecolor(series, clims)), get_linealpha(series)), linewidth = py_thickness_scale(plt, hasline * sp[:legendfontsize] / 8), linestyle = py_linestyle(:path, get_linestyle(series)), + solid_capstyle = "butt", solid_joinstyle = "miter", + dash_capstyle = "butt", dash_joinstyle = "miter", marker = py_marker(_cycle(series[:markershape], 1)), markersize = py_thickness_scale(plt, 0.8 * sp[:legendfontsize]), markeredgecolor = py_color(single_color(get_markerstrokecolor(series)), get_markerstrokealpha(series)), @@ -1335,6 +1407,7 @@ function py_add_legend(plt::Plot, sp::Subplot, ax) # if anything was added, call ax.legend and set the colors if !isempty(handles) + leg = legend_angle(leg) leg = ax."legend"(handles, labels, loc = py_legend_pos(leg), @@ -1344,7 +1417,8 @@ function py_add_legend(plt::Plot, sp::Subplot, ax) facecolor = py_color(sp[:background_color_legend]), edgecolor = py_color(sp[:foreground_color_legend]), framealpha = alpha(plot_color(sp[:background_color_legend])), - fancybox = false # makes the legend box square + fancybox = false, # makes the legend box square + borderpad = 0.8 # to match GR legendbox ) frame = leg."get_frame"() frame."set_linewidth"(py_thickness_scale(plt, 1)) @@ -1375,13 +1449,10 @@ function _update_plot_object(plt::Plot{PyPlotBackend}) pcts = bbox_to_pcts(sp.plotarea, figw, figh) ax."set_position"(pcts) - # set the cbar position if there is one - if haskey(sp.attr, :cbar_ax) + if haskey(sp.attr, :cbar_ax) && RecipesPipeline.is3d(sp) # 2D plots are completely handled by axis dividers cbw = sp.attr[:cbar_width] # this is the bounding box of just the colors of the colorbar (not labels) - ex = sp[:zaxis][:extrema] - has_toplabel = !(1e-7 < max(abs(ex.emax), abs(ex.emin)) < 1e7) - cb_bbox = BoundingBox(right(sp.bbox)-cbw+1mm, top(sp.bbox) + (has_toplabel ? 4mm : 2mm), _cbar_width-1mm, height(sp.bbox) - (has_toplabel ? 6mm : 4mm)) + cb_bbox = BoundingBox(right(sp.bbox)-cbw - 2mm, top(sp.bbox) + 2mm, _cbar_width-1mm, height(sp.bbox) - 4mm) pcts = bbox_to_pcts(cb_bbox, figw, figh) sp.attr[:cbar_ax]."set_position"(pcts) end diff --git a/src/backends/web.jl b/src/backends/web.jl index b1663435..876858cf 100644 --- a/src/backends/web.jl +++ b/src/backends/web.jl @@ -19,6 +19,10 @@ function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.attr """ end +function embeddable_html(plt::AbstractPlot) + html_head(plt) * html_body(plt) +end + function open_browser_window(filename::AbstractString) @static if Sys.isapple() return run(`open $(filename)`) @@ -45,7 +49,7 @@ function standalone_html_window(plt::AbstractPlot) old = use_local_dependencies[] # save state to restore afterwards # if we open a browser ourself, we can host local files, so # when we have a local plotly downloaded this is the way to go! - use_local_dependencies[] = isfile(plotly_local_file_path) + use_local_dependencies[] = plotly_local_file_path[] === nothing ? false : isfile(plotly_local_file_path[]) filename = write_temp_html(plt) open_browser_window(filename) # restore for other backends diff --git a/src/colorbars.jl b/src/colorbars.jl new file mode 100644 index 00000000..b0f9996a --- /dev/null +++ b/src/colorbars.jl @@ -0,0 +1,90 @@ +# These functions return an operator for use in `get_clims(::Seres, op)` +process_clims(lims::Tuple{<:Number,<:Number}) = (zlims -> ifelse.(isfinite.(lims), lims, zlims)) ∘ ignorenan_extrema +process_clims(s::Union{Symbol,Nothing,Missing}) = ignorenan_extrema +# don't specialize on ::Function otherwise python functions won't work +process_clims(f) = f + +function get_clims(sp::Subplot, op=process_clims(sp[:clims])) + zmin, zmax = Inf, -Inf + for series in series_list(sp) + if series[:colorbar_entry] + zmin, zmax = _update_clims(zmin, zmax, get_clims(series, op)...) + end + end + return zmin <= zmax ? (zmin, zmax) : (NaN, NaN) +end + +function get_clims(sp::Subplot, series::Series, op=process_clims(sp[:clims])) + zmin, zmax = if series[:colorbar_entry] + get_clims(sp, op) + else + get_clims(series, op) + end + return zmin <= zmax ? (zmin, zmax) : (NaN, NaN) +end + +""" + get_clims(::Series, op=Plots.ignorenan_extrema) + +Finds the limits for the colorbar by taking the "z-values" for the series and passing them into `op`, +which must return the tuple `(zmin, zmax)`. The default op is the extrema of the finite +values of the input. +""" +function get_clims(series::Series, op=ignorenan_extrema) + zmin, zmax = Inf, -Inf + z_colored_series = (:contour, :contour3d, :heatmap, :histogram2d, :surface, :hexbin) + for vals in (series[:seriestype] in z_colored_series ? series[:z] : nothing, series[:line_z], series[:marker_z], series[:fill_z]) + if (typeof(vals) <: AbstractSurface) && (eltype(vals.surf) <: Union{Missing, Real}) + zmin, zmax = _update_clims(zmin, zmax, op(vals.surf)...) + elseif (vals !== nothing) && (eltype(vals) <: Union{Missing, Real}) + zmin, zmax = _update_clims(zmin, zmax, op(vals)...) + end + end + return zmin <= zmax ? (zmin, zmax) : (NaN, NaN) +end + +_update_clims(zmin, zmax, emin, emax) = NaNMath.min(zmin, emin), NaNMath.max(zmax, emax) + +@enum ColorbarStyle cbar_gradient cbar_fill cbar_lines + +function colorbar_style(series::Series) + colorbar_entry = series[:colorbar_entry] + if !(colorbar_entry isa Bool) + @warn "Non-boolean colorbar_entry ignored." + colorbar_entry = true + end + + if !colorbar_entry + nothing + elseif isfilledcontour(series) + cbar_fill + elseif iscontour(series) + cbar_lines + elseif series[:seriestype] ∈ (:heatmap,:surface) || + any(series[z] !== nothing for z ∈ [:marker_z,:line_z,:fill_z]) + cbar_gradient + else + nothing + end +end + +hascolorbar(series::Series) = colorbar_style(series) !== nothing +hascolorbar(sp::Subplot) = sp[:colorbar] != :none && any(hascolorbar(s) for s in series_list(sp)) + +function get_colorbar_ticks(sp::Subplot; update = true) + if update || !haskey(sp.attr, :colorbar_optimized_ticks) + ticks = _transform_ticks(sp[:colorbar_ticks]) + cvals = sp[:colorbar_continuous_values] + dvals = sp[:colorbar_discrete_values] + clims = get_clims(sp) + scale = sp[:colorbar_scale] + formatter = sp[:colorbar_formatter] + sp.attr[:colorbar_optimized_ticks] = + get_ticks(ticks, cvals, dvals, clims, scale, formatter) + end + return sp.attr[:colorbar_optimized_ticks] +end + +function _update_subplot_colorbars(sp::Subplot) + # Dynamic callback from the pipeline if needed +end diff --git a/src/components.jl b/src/components.jl index d1cf1d91..63be7b15 100644 --- a/src/components.jl +++ b/src/components.jl @@ -1,7 +1,5 @@ - - -const P2 = GeometryTypes.Point2{Float64} -const P3 = GeometryTypes.Point3{Float64} +const P2 = GeometryBasics.Point2{Float64} +const P3 = GeometryBasics.Point3{Float64} nanpush!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); push!(a, b)) nanappend!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); append!(a, b)) @@ -11,9 +9,9 @@ compute_angle(v::P2) = (angle = atan(v[2], v[1]); angle < 0 ? 2π - angle : angl # ------------------------------------------------------------- -struct Shape - x::Vector{Float64} - y::Vector{Float64} +struct Shape{X<:Number, Y<:Number} + x::Vector{X} + y::Vector{Y} # function Shape(x::AVec, y::AVec) # # if x[1] != x[end] || y[1] != y[end] # # new(vcat(x, x[1]), vcat(y, y[1])) @@ -44,21 +42,17 @@ function coords(shape::Shape) shape.x, shape.y end -function coords(shapes::AVec{Shape}) - length(shapes) == 0 && return zeros(0), zeros(0) - xs = map(get_xs, shapes) - ys = map(get_ys, shapes) - x, y = map(copy, coords(shapes[1])) - for shape in shapes[2:end] - nanappend!(x, shape.x) - nanappend!(y, shape.y) - end +#coords(shapes::AVec{Shape}) = unzip(map(coords, shapes)) +function coords(shapes::AVec{<:Shape}) + c = map(coords, shapes) + x = [q[1] for q in c] + y = [q[2] for q in c] x, y end "get an array of tuples of points on a circle with radius `r`" function partialcircle(start_θ, end_θ, n = 20, r=1) - Tuple{Float64,Float64}[(r*cos(u),r*sin(u)) for u in range(start_θ, stop=end_θ, length=n)] + [(r*cos(u), r*sin(u)) for u in range(start_θ, stop=end_θ, length=n)] end "interleave 2 vectors into each other (like a zipper's teeth)" @@ -77,7 +71,6 @@ function weave(x,y; ordering = Vector[x,y]) ret end - "create a star by weaving together points from an outer and inner circle. `n` is the number of arms" function makestar(n; offset = -0.5, radius = 1.0) z1 = offset * π @@ -93,7 +86,6 @@ function makeshape(n; offset = -0.5, radius = 1.0) Shape(partialcircle(z, z + 2π, n+1, radius)) end - function makecross(; offset = -0.5, radius = 1.0) z2 = offset * π z1 = z2 - π/8 @@ -103,7 +95,6 @@ function makecross(; offset = -0.5, radius = 1.0) ordering=Vector[outercircle,innercircle,outercircle])) end - from_polar(angle, dist) = P2(dist*cos(angle), dist*sin(angle)) function makearrowhead(angle; h = 2.0, w = 0.4) @@ -112,31 +103,6 @@ function makearrowhead(angle; h = 2.0, w = 0.4) from_polar(angle + 0.5π, w) - tip, (0,0)]) end -const _shape_keys = Symbol[ - :circle, - :rect, - :star5, - :diamond, - :hexagon, - :cross, - :xcross, - :utriangle, - :dtriangle, - :rtriangle, - :ltriangle, - :pentagon, - :heptagon, - :octagon, - :star4, - :star6, - :star7, - :star8, - :vline, - :hline, - :+, - :x, -] - const _shapes = KW( :circle => makeshape(20), :rect => makeshape(4, offset=-0.25), @@ -239,9 +205,12 @@ function rotate!(shape::Shape, Θ::Real, c = center(shape)) end "rotate an object in space" -function rotate(shape::Shape, Θ::Real, c = center(shape)) - shapecopy = deepcopy(shape) - rotate!(shapecopy, Θ, c) +function rotate(shape::Shape, θ::Real, c = center(shape)) + x, y = coords(shape) + cx, cy = c + x_new = rotate_x.(x, y, θ, cx, cy) + y_new = rotate_y.(x, y, θ, cx, cy) + Shape(x_new, y_new) end # ----------------------------------------------------------------------- @@ -319,7 +288,7 @@ function font(args...;kw...) for symbol in keys(kw) if symbol == :family - family = kw[:family] + family = string(kw[:family]) elseif symbol == :pointsize pointsize = kw[:pointsize] elseif symbol == :halign @@ -358,9 +327,15 @@ end Scales all **current** font sizes by `factor`. For example `scalefontsizes(1.1)` increases all current font sizes by 10%. To reset to initial sizes, use `scalefontsizes()` """ function scalefontsizes(factor::Number) - for k in (:titlefontsize, :guidefontsize, :tickfontsize, :legendfontsize) + for k in (:titlefontsize, :legendfontsize, :legendtitlefontsize) scalefontsize(k, factor) end + + for letter in (:x,:y,:z) + for k in (:guidefontsize, :tickfontsize) + scalefontsize(Symbol(letter, k), factor) + end + end end """ @@ -369,15 +344,27 @@ end Resets font sizes to initial default values. """ function scalefontsizes() - for k in (:titlefontsize, :guidefontsize, :tickfontsize, :legendfontsize) - f = default(k) - if k in keys(_initial_fontsizes) - factor = f / _initial_fontsizes[k] - scalefontsize(k, 1.0/factor) - end - end + for k in (:titlefontsize, :legendfontsize, :legendtitlefontsize) + f = default(k) + if k in keys(_initial_fontsizes) + factor = f / _initial_fontsizes[k] + scalefontsize(k, 1.0/factor) + end + end + + for letter in (:x,:y,:z) + for k in (:guidefontsize, :tickfontsize) + if k in keys(_initial_fontsizes) + f = default(Symbol(letter, k)) + factor = f / _initial_fontsizes[k] + scalefontsize(Symbol(letter, k), 1.0/factor) + end + end + end end +resetfontsizes() = scalefontsizes() + "Wrap a string with font info" struct PlotText str::AbstractString @@ -487,6 +474,11 @@ mutable struct SeriesAnnotations baseshape::Union{Shape, AbstractVector{Shape}, Nothing} scalefactor::Tuple end + +series_annotations(scalar) = series_annotations([scalar]) +function series_annotations(anns::AMat) + map(series_annotations, anns) +end function series_annotations(strs::AbstractVector, args...) fnt = font() shp = nothing @@ -583,16 +575,27 @@ end annotations(::Nothing) = [] annotations(anns::AVec) = anns +annotations(anns::AMat) = map(annotations, anns) annotations(anns) = Any[anns] annotations(sa::SeriesAnnotations) = sa # Expand arrays of coordinates, positions and labels into induvidual annotations # and make sure labels are of type PlotText -function process_annotation(sp::Subplot, xs, ys, labs, font = font()) +function process_annotation(sp::Subplot, xs, ys, labs, font = nothing) anns = [] labs = makevec(labs) xlength = length(methods(length, (typeof(xs),))) == 0 ? 1 : length(xs) ylength = length(methods(length, (typeof(ys),))) == 0 ? 1 : length(ys) + if isnothing(font) + font = Plots.font(; + family=sp[:annotationfontfamily], + pointsize=sp[:annotationfontsize], + halign=sp[:annotationhalign], + valign=sp[:annotationvalign], + rotation=sp[:annotationrotation], + color=sp[:annotationcolor], + ) + end for i in 1:max(xlength, ylength, length(labs)) x, y, lab = _cycle(xs, i), _cycle(ys, i), _cycle(labs, i) x = typeof(x) <: TimeType ? Dates.value(x) : x @@ -601,14 +604,24 @@ function process_annotation(sp::Subplot, xs, ys, labs, font = font()) alphabet = "abcdefghijklmnopqrstuvwxyz" push!(anns, (x, y, text(string("(", alphabet[sp[:subplot_index]], ")"), font))) else - push!(anns, (x, y, isa(lab, PlotText) ? lab : isa(lab, Tuple) ? text(lab...) : text(lab, font))) + push!(anns, (x, y, isa(lab, PlotText) ? lab : isa(lab, Tuple) ? text(lab[1], font, lab[2:end]...) : text(lab, font))) end end anns end -function process_annotation(sp::Subplot, positions::Union{AVec{Symbol},Symbol}, labs, font = font()) +function process_annotation(sp::Subplot, positions::Union{AVec{Symbol},Symbol}, labs, font = nothing) anns = [] positions, labs = makevec(positions), makevec(labs) + if isnothing(font) + font = Plots.font(; + family=sp[:annotationfontfamily], + pointsize=sp[:annotationfontsize], + halign=sp[:annotationhalign], + valign=sp[:annotationvalign], + rotation=sp[:annotationrotation], + color=sp[:annotationcolor], + ) + end for i in 1:max(length(positions), length(labs)) pos, lab = _cycle(positions, i), _cycle(labs, i) pos = get(_positionAliases, pos, pos) @@ -616,12 +629,15 @@ function process_annotation(sp::Subplot, positions::Union{AVec{Symbol},Symbol}, alphabet = "abcdefghijklmnopqrstuvwxyz" push!(anns, (pos, text(string("(", alphabet[sp[:subplot_index]], ")"), font))) else - push!(anns, (pos, isa(lab, PlotText) ? lab : isa(lab, Tuple) ? text(lab...) : text(lab, font))) + push!(anns, (pos, isa(lab, PlotText) ? lab : isa(lab, Tuple) ? text(lab[1], font, lab[2:end]...) : text(lab, font))) end end anns end +function process_any_label(lab, font=Font()) + lab isa Tuple ? text(lab...) : text( lab, font ) +end # Give each annotation coordinates based on specified position function locate_annotation(sp::Subplot, pos::Symbol, lab::PlotText) position_multiplier = Dict{Symbol, Tuple{Float64,Float64}}( @@ -638,6 +654,7 @@ function locate_annotation(sp::Subplot, pos::Symbol, lab::PlotText) (x, y, lab) end locate_annotation(sp::Subplot, x, y, label::PlotText) = (x, y, label) +locate_annotation(sp::Subplot, x, y, z, label::PlotText) = (x, y, z, label) # ----------------------------------------------------------------------- "type which represents z-values for colors and sizes (and anything else that might come up)" @@ -737,7 +754,7 @@ end # ----------------------------------------------------------------------- "create a BezierCurve for plotting" -mutable struct BezierCurve{T <: GeometryTypes.Point} +mutable struct BezierCurve{T <: GeometryBasics.Point} control_points::Vector{T} end diff --git a/src/examples.jl b/src/examples.jl index 4b1f7e49..eb99e5fe 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -7,9 +7,9 @@ mutable struct PlotExample exprs::Vector{Expr} end -# the _examples we'll run for each +# the _examples we'll run for each backend const _examples = PlotExample[ - PlotExample( + PlotExample( # 1 "Lines", "A simple line plot of the columns.", [:( @@ -18,7 +18,7 @@ const _examples = PlotExample[ end )], ), - PlotExample( + PlotExample( # 2 "Functions, adding data, and animations", """ Plot multiple functions. You can also put the function first, or use the form `plot(f, @@ -30,16 +30,16 @@ const _examples = PlotExample[ """, [:( begin - p = plot([sin, cos], zeros(0), leg = false) + p = plot([sin, cos], zeros(0), leg = false, xlims = (0, 2π), ylims = (-1, 1)) anim = Animation() - for x in range(0, stop = 10π, length = 100) + for x in range(0, stop = 2π, length = 20) push!(p, x, Float64[sin(x), cos(x)]) frame(anim) end end )], ), - PlotExample( + PlotExample( # 3 "Parametric plots", "Plot function pair (x(u), y(u)).", [ @@ -58,7 +58,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 4 "Colors", """ Access predefined palettes (or build your own with the `colorscheme` method). @@ -89,7 +89,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 5 "Global", """ Change the guides/background/limits/ticks. Convenience args `xaxis` and `yaxis` allow @@ -121,13 +121,7 @@ const _examples = PlotExample[ ], ), - # PlotExample("Two-axis", - # "Use the `axis` arguments.", - # [ - # :(plot(Vector[randn(100), randn(100)*100], axis = [:l :r], ylabel="LEFT", yrightlabel="RIGHT", xlabel="X", title="TITLE")) - # ]), - - PlotExample( + PlotExample( # 6 "Images", "Plot an image. y-axis is set to flipped", [ @@ -142,7 +136,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 7 "Arguments", """ Plot multiple series with different numbers of points. Mix arguments that apply to all @@ -166,7 +160,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 8 "Build plot in pieces", "Start with a base plot...", [:( @@ -175,7 +169,7 @@ const _examples = PlotExample[ end )], ), - PlotExample( + PlotExample( # 9 "", "and add to it later.", [:( @@ -184,7 +178,7 @@ const _examples = PlotExample[ end )], ), - PlotExample( + PlotExample( # 10 "Histogram2D", "", [:( @@ -193,7 +187,7 @@ const _examples = PlotExample[ end )], ), - PlotExample( + PlotExample( # 11 "Line types", "", [ @@ -214,7 +208,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 12 "Line styles", "", [ @@ -237,7 +231,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 13 "Marker types", "", [ @@ -247,14 +241,15 @@ const _examples = PlotExample[ m -> m in Plots.supported_markers(), Plots._shape_keys, ) - markers = reshape(markers, 1, length(markers)) + markers = permutedims(markers) n = length(markers) x = range(0, stop = 10, length = n + 2)[2:(end - 1)] y = repeat(reshape(reverse(x), 1, :), n, 1) scatter( x, y, - m = (8, :auto), + m = markers, + markersize = 8, lab = map(string, markers), bg = :linen, xlim = (0, 10), @@ -264,7 +259,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 14 "Bar", "`x` is the midpoint of the bar. (todo: allow passing of edges instead of midpoints)", [:( @@ -273,7 +268,7 @@ const _examples = PlotExample[ end )], ), - PlotExample( + PlotExample( # 15 "Histogram", "", [ @@ -288,7 +283,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 16 "Subplots", """ Use the `layout` keyword, and optionally the convenient `@layout` macro to generate @@ -310,7 +305,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 17 "Adding to subplots", """ Note here the automatic grid layout, as well as the order in which new series are added @@ -329,14 +324,20 @@ const _examples = PlotExample[ ), ], ), - PlotExample("", "", [:( - begin - using Random - Random.seed!(111) - plot!(Plots.fakedata(100, 10)) - end - )]), - PlotExample( + PlotExample( # 18 + "", + "", + [ + :( + begin + using Random + Random.seed!(111) + plot!(Plots.fakedata(100, 10)) + end + ) + ] + ), + PlotExample( # 19 "Open/High/Low/Close", """ Create an OHLC chart. Pass in a list of (open,high,low,close) tuples as your `y` @@ -365,7 +366,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 20 "Annotations", """ The `annotations` keyword is used for text annotations in data-coordinates. Pass in a @@ -408,7 +409,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 21 "Custom Markers", """A `Plots.Shape` is a light wrapper around vertices of a polygon. For supported backends, pass arbitrary polygons as the marker shapes. Note: The center is (0,0) and @@ -454,7 +455,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 22 "Contours", """ Any value for fill works here. We first build a filled contour from a function, then an @@ -474,7 +475,7 @@ const _examples = PlotExample[ end )], ), - PlotExample( + PlotExample( # 23 "Pie", "", [:( @@ -485,7 +486,7 @@ const _examples = PlotExample[ end )], ), - PlotExample( + PlotExample( # 24 "3D", "", [ @@ -511,7 +512,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 25 "DataFrames", "Plot using DataFrame column symbols.", [ @@ -534,7 +535,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 26 "Groups and Subplots", "", [ @@ -552,7 +553,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 27 "Polar Plots", "", [:( @@ -563,7 +564,7 @@ const _examples = PlotExample[ end )], ), - PlotExample( + PlotExample( # 28 "Heatmap, categorical axes, and aspect_ratio", "", [:( @@ -575,7 +576,7 @@ const _examples = PlotExample[ end )], ), - PlotExample( + PlotExample( # 29 "Layouts, margins, label rotation, title location", "", [ @@ -595,7 +596,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 30 "Boxplot and Violin series recipes", "", [ @@ -620,7 +621,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 31 "Animation with subplots", "The `layout` macro can be used to create an animation with subplots.", [ @@ -628,14 +629,15 @@ const _examples = PlotExample[ begin l = @layout([[a; b] c]) p = plot( - plot([sin, cos], 1, leg = false), - scatter([atan, cos], 1, leg = false), - plot(log, 1, xlims = (1, 10π), ylims = (0, 5), leg = false), + plot([sin, cos], 1, ylims = (-1, 1), leg = false), + scatter([atan, cos], 1, ylims = (-1, 1.5), leg = false), + plot(log, 1, ylims = (0, 2), leg = false), layout = l, + xlims = (1, 2π), ) anim = Animation() - for x in range(1, stop = 10π, length = 100) + for x in range(1, stop = 2π, length = 20) plot(push!( p, x, @@ -647,7 +649,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 32 "Spy", """ For a matrix `mat` with unique nonzeros `spy(mat)` returns a colorless plot. If `mat` has @@ -681,7 +683,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 33 "Magic grid argument", """ The grid lines can be modified individually for each axis with the magic `grid` argument. @@ -711,7 +713,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 34 "Framestyle", """ The style of the frame/axes of a (sub)plot can be changed with the `framestyle` @@ -735,7 +737,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 35 "Lines and markers with varying colors", """ You can use the `line_z` and `marker_z` properties to associate a color with @@ -761,7 +763,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 36 "Portfolio Composition maps", """ see: http://stackoverflow.com/a/37732384/5075246 @@ -787,7 +789,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 37 "Ribbons", """ Ribbons can be added to lines via the `ribbon` keyword; @@ -810,7 +812,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 38 "Histogram2D (complex values)", "", [ @@ -829,7 +831,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 39 "Unconnected lines using `missing` or `NaN`", """ Missing values and non-finite values, including `NaN`, are not plotted. @@ -853,7 +855,7 @@ const _examples = PlotExample[ ), ], ), - PlotExample( + PlotExample( # 40 "Lens", "A lens lets you easily magnify a region of a plot. x and y coordinates refer to the to be magnified region and the via the `inset` keyword the subplot index and the bounding box (in relative coordinates) of the inset plot with the magnified plot can be specified. Additional attributes count for the inset plot.", [ @@ -875,7 +877,7 @@ const _examples = PlotExample[ end, ], ), - PlotExample( + PlotExample( # 41 "Array Types", "Plots supports different `Array` types that follow the `AbstractArray` interface, like `StaticArrays` and `OffsetArrays.`", [ @@ -885,11 +887,12 @@ const _examples = PlotExample[ sv = SVector{10}(rand(10)) ov = OffsetVector(rand(10), -2) plot([sv, ov], label = ["StaticArray" "OffsetArray"]) + plot!(3ov, ribbon=ov, label="OffsetArray ribbon") end end, ], ), - PlotExample( + PlotExample( # 42 "Setting defaults and font arguments", "", [ @@ -919,7 +922,7 @@ const _examples = PlotExample[ end, ], ), - PlotExample( + PlotExample( # 43 "Heatmap with DateTime axis", "", [ @@ -934,7 +937,7 @@ const _examples = PlotExample[ end, ], ), - PlotExample( + PlotExample( # 44 "Linked axes", "", [ @@ -946,7 +949,7 @@ const _examples = PlotExample[ end, ], ), - PlotExample( + PlotExample( # 45 "Error bars and array type recipes", "", [ @@ -973,8 +976,8 @@ const _examples = PlotExample[ surf = Measurement.((1:10) .* (1:10)', rand(10,10)) plot( - scatter(x, [x y], msw = 0), - scatter(x, y, z, msw = 0), + scatter(x, [x y]), + scatter(x, y, z), heatmap(x, y, surf), wireframe(x, y, surf), legend = :topleft @@ -983,28 +986,247 @@ const _examples = PlotExample[ end, ], ), + PlotExample( # 46 + "Tuples and `Point`s as data", + "", + [quote + using GeometryBasics + using Distributions + d = MvNormal([1.0 0.75; 0.75 2.0]) + plot([(1,2),(3,2),(2,1),(2,3)]) + scatter!(Point2.(eachcol(rand(d,1000))), alpha=0.25) + end] + ), + PlotExample( # 47 + "Mesh3d", + """ + Allows to plot arbitrary 3d meshes. If only x,y,z are given the mesh is generated automatically. + You can also specify the connections using the connections keyword. + The connections are specified using a tuple of vectors. Each vector contains the 0-based indices of one point of a triangle, + such that elements at the same position of these vectors form a triangle. + """, + [ + :( + begin + # specify the vertices + x=[0, 1, 2, 0] + y=[0, 0, 1, 2] + z=[0, 2, 0, 1] + + # specify the triangles + # every column is one triangle, + # where the values denote the indices of the vertices of the triangle + i=[0, 0, 0, 1] + j=[1, 2, 3, 2] + k=[2, 3, 1, 3] + + # the four triangles gives above give a tetrahedron + mesh3d(x,y,z;connections=(i,j,k)) + end + ), + ], + ), + PlotExample( # 48 + "Vectors of markershapes and segments", + "", + [quote + using Base.Iterators: cycle, take + + yv = ones(9) + ys = [1; 1; NaN; ones(6)] + y = 5 .- [yv 2ys 3yv 4ys] + + plt_color_rows = plot( + y, + seriestype = [:path :path :scatter :scatter], + markershape = collect(take(cycle((:utriangle, :rect)), 9)), + markersize = 8, + color = collect(take(cycle((:red, :black)), 9)) + ) + + plt_z_cols = plot( + y, + markershape = [:utriangle :x :circle :square], + markersize = [5 10 10 5], + marker_z = [5 4 3 2], + line_z = [1 3 3 1], + linewidth = [1 10 5 1] + ) + + plot(plt_color_rows, plt_z_cols) + end] + ), + PlotExample( # 49 + "Polar heatmaps", + "", + [quote + x = range(0, 2π, length=9) + y = 0:4 + z = (1:4) .+ (1:8)' + heatmap(x, y, z, projection = :polar) + end] + ), + PlotExample( # 50 + "3D surface with axis guides", + "", + [quote + f(x,a) = 1/x + a*x^2 + xs = collect(0.1:0.05:2.0); + as = collect(0.2:0.1:2.0); + + x_grid = [x for x in xs for y in as]; + a_grid = [y for x in xs for y in as]; + + plot(x_grid, a_grid, f.(x_grid,a_grid), + st = :surface, + xlabel = "longer xlabel", + ylabel = "longer ylabel", + zlabel = "longer zlabel", + ) + end] + ), + PlotExample( # 51 + "Images with custom axes", + "", + [quote + using Plots + using TestImages + img = testimage("lighthouse") + + # plot the image reversing the first dimension and setting yflip = false + plot([-π, π], [-1, 1], reverse(img, dims=1), yflip=false, aspect_ratio=:none) + # plot other data + plot!(sin, -π, π, lw=3, color=:red) + end] + ), + PlotExample( + "3d quiver", + "", + [quote + using Plots + + ϕs = range(-π, π, length=50) + θs = range(0, π, length=25) + θqs = range(1, π-1, length=25) + + x = vec([sin(θ) * cos(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θs)]) + y = vec([sin(θ) * sin(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θs)]) + z = vec([cos(θ) for (ϕ, θ) in Iterators.product(ϕs, θs)]) + + u = 0.1 * vec([sin(θ) * cos(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θqs)]) + v = 0.1 * vec([sin(θ) * sin(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θqs)]) + w = 0.1 * vec([cos(θ) for (ϕ, θ) in Iterators.product(ϕs, θqs)]) + + quiver(x,y,z, quiver=(u,v,w)) + end] + ), + PlotExample( # 53 + "Step Types", + "A comparison of the various step-like `seriestype`s", + [ + :( + begin + x = 1:5 + y = [1, 2, 3, 2, 1] + default(shape=:circle) + plot( + plot(x, y, markershape=:circle, seriestype=:steppre, label="steppre"), + plot(x, y, markershape=:circle, seriestype=:stepmid, label="stepmid"), + plot(x, y, markershape=:circle, seriestype=:steppost, label="steppost"), + layout=(3,1) + ) + end + ), + ], + ), + PlotExample( # 54 + "Guide positions and alignment", + "", + [ + :( + begin + plot( + rand(10, 4), + layout=4, + xguide="x guide", + yguide="y guide", + xguidefonthalign=[:left :right :right :left], + yguidefontvalign=[:top :bottom :bottom :top], + xguideposition=:top, + yguideposition=[:right :left :right :left], + ymirror=[false true true false], + xmirror=[false false true true], + legend=false, + seriestype=[:bar :scatter :path :stepmid] + ) + end + ), + ], + ), + PlotExample( # 55 + "3D axis flip / mirror", + "", + [ + :( + begin + using LinearAlgebra + scalefontsizes(.4) + + x, y = collect(-6:0.5:10), collect(-8:0.5:8) + + args = x, y, (x, y) -> sinc(norm([x, y]) / π) + kwargs = Dict(:xlabel=>"x", :ylabel=>"y", :zlabel=>"z", :grid=>true, :minorgrid=>true) + + plots = [wireframe(args..., title = "wire"; kwargs...)] + + for ax ∈ (:x, :y, :z) + push!(plots, wireframe( + args..., + title = "wire-flip-$ax", + xflip = ax == :x, + yflip = ax == :y, + zflip = ax == :z; + kwargs..., + )) + end + + for ax ∈ (:x, :y, :z) + push!(plots, wireframe( + args..., + title = "wire-mirror-$ax", + xmirror = ax == :x, + ymirror = ax == :y, + zmirror = ax == :z; + kwargs..., + )) + end + + plt = plot(plots..., layout=(@layout [_ ° _; ° ° °; ° ° °]), margin=0Plots.px) + + resetfontsizes() + plt + end + ), + ], + ), ] # Some constants for PlotDocs and PlotReferenceImages _animation_examples = [2, 31] _backend_skips = Dict( - :gr => [25, 30], - :pyplot => [2, 25, 30, 31], - :plotlyjs => [2, 21, 24, 25, 30, 31], - :plotly => [2, 21, 24, 25, 30, 31], - :pgfplots => [2, 5, 6, 10, 16, 20, 22, 23, 25, 28, 30, 31, 34, 37, 38, 39], + :gr => [25, 30, 47], + :pyplot => [2, 25, 30, 31, 47, 49, 55], + :plotlyjs => [2, 21, 24, 25, 30, 31, 49, 51, 55], + :plotly => [2, 21, 24, 25, 30, 31, 49, 50, 51, 55], :pgfplotsx => [ 2, # animation 6, # images - 10, # histogram2d 16, # pgfplots thinks the upper panel is too small - 22, # contourf - 25, # @df 30, # @df 31, # animation 32, # spy - 38, # histogram2d - 45, # wireframe + 49, # polar heatmap + 51, # image with custom axes ], ) diff --git a/src/ijulia.jl b/src/ijulia.jl index 1f0ef219..7d2786ee 100644 --- a/src/ijulia.jl +++ b/src/ijulia.jl @@ -4,7 +4,7 @@ const use_local_plotlyjs = Ref(false) function _init_ijulia_plotting() # IJulia is more stable with local file - use_local_plotlyjs[] = isfile(plotly_local_file_path) + use_local_plotlyjs[] = plotly_local_file_path[] === nothing ? false : isfile(plotly_local_file_path[]) ENV["MPLBACKEND"] = "Agg" end @@ -54,9 +54,9 @@ function _ijulia_display_dict(plt::Plot) elseif output_type == :html mime = "text/html" out[mime] = sprint(show, MIME(mime), plt) + _ijulia__extra_mime_info!(plt, out) else error("Unsupported output type $output_type") end - _ijulia__extra_mime_info!(plt, out) out end diff --git a/src/init.jl b/src/init.jl index eb05ec71..22bb4b51 100644 --- a/src/init.jl +++ b/src/init.jl @@ -1,4 +1,7 @@ using REPL +using Scratch + +const plotly_local_file_path = Ref{Union{Nothing, String}}(nothing) function _plots_defaults() @@ -13,9 +16,10 @@ end function __init__() user_defaults = _plots_defaults() if haskey(user_defaults, :theme) - theme(pop!(user_defaults, :theme)) + theme(pop!(user_defaults, :theme); user_defaults...) + else + default(; user_defaults...) end - default(; user_defaults...) insert!(Base.Multimedia.displays, findlast(x -> x isa Base.TextDisplay || x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1, PlotsDisplay()) @@ -29,49 +33,41 @@ function __init__() @require HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" begin fn = joinpath(@__DIR__, "backends", "hdf5.jl") include(fn) - @require Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Revise.track(Plots, fn) end @require InspectDR = "d0351b0e-4b05-5898-87b3-e2a8edfddd1d" begin fn = joinpath(@__DIR__, "backends", "inspectdr.jl") include(fn) - @require Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Revise.track(Plots, fn) end @require PGFPlots = "3b7a836e-365b-5785-a47d-02c71176b4aa" begin - fn = joinpath(@__DIR__, "backends", "pgfplots.jl") + fn = joinpath(@__DIR__, "backends", "deprecated", "pgfplots.jl") include(fn) - @require Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Revise.track(Plots, fn) end - @require ORCA = "47be7bcc-f1a6-5447-8b36-7eeeff7534fd" begin - fn = joinpath(@__DIR__, "backends", "orca.jl") + @require PlotlyBase = "a03496cd-edff-5a9b-9e67-9cda94a718b5" begin + fn = joinpath(@__DIR__, "backends", "plotlybase.jl") include(fn) - @require Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Revise.track(Plots, fn) end @require PGFPlotsX = "8314cec4-20b6-5062-9cdb-752b83310925" begin fn = joinpath(@__DIR__, "backends", "pgfplotsx.jl") include(fn) - @require Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Revise.track(Plots, fn) end @require PlotlyJS = "f0f68f2c-4968-5e81-91da-67840de0976a" begin fn = joinpath(@__DIR__, "backends", "plotlyjs.jl") include(fn) - @require Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Revise.track(Plots, fn) end @require PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee" begin fn = joinpath(@__DIR__, "backends", "pyplot.jl") include(fn) - @require Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Revise.track(Plots, fn) end @require UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" begin fn = joinpath(@__DIR__, "backends", "unicodeplots.jl") include(fn) - @require Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Revise.track(Plots, fn) end @require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin @@ -82,16 +78,17 @@ function __init__() end end - if haskey(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL") - use_local_plotlyjs[] = ENV["PLOTS_HOST_DEPENDENCY_LOCAL"] == "true" - use_local_dependencies[] = isfile(plotly_local_file_path) && use_local_plotlyjs[] - if use_local_plotlyjs[] && !isfile(plotly_local_file_path) - @warn("PLOTS_HOST_DEPENDENCY_LOCAL is set to true, but no local plotly file found. run Pkg.build(\"Plots\") and make sure PLOTS_HOST_DEPENDENCY_LOCAL is set to true") + if get(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL", "false") == "true" + global plotly_local_file_path[] = joinpath(@get_scratch!("plotly"), _plotly_min_js_filename) + if !isfile(plotly_local_file_path[]) + download("https://cdn.plot.ly/$(_plotly_min_js_filename)", plotly_local_file_path[]) end - else - use_local_dependencies[] = use_local_plotlyjs[] + + use_local_plotlyjs[] = true end + use_local_dependencies[] = use_local_plotlyjs[] + @require FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" begin _show(io::IO, mime::MIME"image/png", plt::Plot{<:PDFBackends}) = _show_pdfbackends(io, mime, plt) diff --git a/src/layouts.jl b/src/layouts.jl index 941663ca..a34c94a8 100644 --- a/src/layouts.jl +++ b/src/layouts.jl @@ -4,34 +4,6 @@ to_pixels(m::AbsoluteLength) = m.value / 0.254 const _cbar_width = 5mm - -#Base.broadcast(::typeof(Base.:.*), m::Measure, n::Number) = m * n -#Base.broadcast(::typeof(Base.:.*), m::Number, n::Measure) = m * n -Base.:-(m::Measure, a::AbstractArray) = map(ai -> m - ai, a) -Base.:-(a::AbstractArray, m::Measure) = map(ai -> ai - m, a) -Base.zero(::Type{typeof(mm)}) = 0mm -Base.one(::Type{typeof(mm)}) = 1mm -Base.typemin(::typeof(mm)) = -Inf*mm -Base.typemax(::typeof(mm)) = Inf*mm -Base.convert(::Type{F}, l::AbsoluteLength) where {F<:AbstractFloat} = convert(F, l.value) - -# TODO: these are unintuitive and may cause tricky bugs -# Base.:+(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value)) -# Base.:+(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (1 + m1.value)) -# Base.:-(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 - m2.value)) -# Base.:-(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (m1.value - 1)) - -Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value) -Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value) -Base.:/(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value) -Base.:/(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value) - - -Base.zero(::Type{typeof(pct)}) = 0pct -Base.one(::Type{typeof(pct)}) = 1pct -Base.typemin(::typeof(pct)) = 0pct -Base.typemax(::typeof(pct)) = 1pct - const defaultbox = BoundingBox(0mm, 0mm, 0mm, 0mm) left(bbox::BoundingBox) = bbox.x0[1] @@ -258,7 +230,7 @@ end grid(args...; kw...) Create a grid layout for subplots. `args` specify the dimensions, e.g. -`grid(3,2, widths = (0.6,04))` creates a grid with three rows and two +`grid(3,2, widths = (0.6,0.4))` creates a grid with three rows and two columns of different width. """ grid(args...; kw...) = GridLayout(args...; kw...) @@ -345,10 +317,8 @@ end # recursively compute the bounding boxes for the layout and plotarea (relative to canvas!) function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) nr, nc = size(layout) - # # create a matrix for each minimum padding direction # _update_min_padding!(layout) - minpad_left = map(leftpad, layout.grid) minpad_top = map(toppad, layout.grid) minpad_right = map(rightpad, layout.grid) @@ -407,10 +377,10 @@ function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0 # this is the minimum perimeter as decided by this child's parent, so that # all children on this border have the same value min_child_perimeter = [ - c == 1 ? layout.minpad[1] : 0mm, - r == 1 ? layout.minpad[2] : 0mm, - c == nc ? layout.minpad[3] : 0mm, - r == nr ? layout.minpad[4] : 0mm + c == 1 ? layout.minpad[1] : pad_left[c], + r == 1 ? layout.minpad[2] : pad_top[r], + c == nc ? layout.minpad[3] : pad_right[c], + r == nr ? layout.minpad[4] : pad_bottom[r] ] # recursively update the child's children @@ -436,7 +406,7 @@ end # ---------------------------------------------------------------------- -calc_num_subplots(layout::AbstractLayout) = 1 +calc_num_subplots(layout::AbstractLayout) = get(layout.attr, :blank, false) ? 0 : 1 function calc_num_subplots(layout::GridLayout) tot = 0 for l in layout.grid @@ -805,9 +775,17 @@ end "Adds a new, empty subplot overlayed on top of `sp`, with a mirrored y-axis and linked x-axis." function twinx(sp::Subplot) - sp[:right_margin] = max(sp[:right_margin], 30px) - plot!(sp.plt, inset = (sp[:subplot_index], bbox(0,0,1,1))) + plot!(sp.plt, + inset = (sp[:subplot_index], bbox(0,0,1,1)), + right_margin = sp[:right_margin], + left_margin = sp[:left_margin], + top_margin = sp[:top_margin], + bottom_margin = sp[:bottom_margin], + ) twinsp = sp.plt.subplots[end] + twinsp[:xaxis][:grid] = false + twinsp[:yaxis][:grid] = false + twinsp[:xaxis][:showaxis] = false twinsp[:yaxis][:mirror] = true twinsp[:background_color_inside] = RGBA{Float64}(0,0,0,0) link_axes!(sp[:xaxis], twinsp[:xaxis]) diff --git a/src/legend.jl b/src/legend.jl new file mode 100644 index 00000000..0b996619 --- /dev/null +++ b/src/legend.jl @@ -0,0 +1,59 @@ +""" +```julia +legend_pos_from_angle(theta, xmin, xcenter, xmax, ymin, ycenter, ymax, inout) +``` + +Return `(x,y)` at an angle `theta` degrees from +`(xcenter,ycenter)` on a rectangle defined by (`xmin`, +`xmax`, `ymin`, `ymax`). +""" +function legend_pos_from_angle(theta, xmin, xcenter, xmax, ymin, ycenter, ymax) + (s,c) = sincosd(theta) + x = c < 0 ? (xmin-xcenter)/c : (xmax-xcenter)/c + y = s < 0 ? (ymin-ycenter)/s : (ymax-ycenter)/s + A = min(x,y) + return (xcenter + A*c, ycenter + A*s) +end + + +""" +Split continuous range `[-1,1]` evenly into an integer `[1,2,3]` +""" +function legend_anchor_index(x) + x<-1//3 && return 1 + x<1//3 && return 2 + return 3 +end + +""" +Turn legend argument into a (theta, :inner) or (theta, :outer) tuple. +For backends where legend position is given in normal coordinates (0,0) -- (1,1), +so :topleft exactly corresponds to (45, :inner) etc. + +If `leg` is a (::Real,::Real) tuple, keep it as is. +""" +legend_angle(leg::Real) = (leg,:inner) +legend_angle(leg::Tuple{S,T}) where {S<:Real,T<:Real} = leg +legend_angle(leg::Tuple{S,Symbol}) where S<:Real = leg +legend_angle(leg::Symbol) = get( + ( + topleft = (135,:inner), + top = (90, :inner), + topright = (45, :inner), + left = (180,:inner), + right = (0, :inner), + bottomleft = (225,:inner), + bottom = (270,:inner), + bottomright = (315,:inner), + outertopleft = (135,:outer), + outertop = (90, :outer), + outertopright = (45, :outer), + outerleft = (180,:outer), + outerright = (0, :outer), + outerbottomleft = (225,:outer), + outerbottom = (270,:outer), + outerbottomright = (315,:outer), + ), + leg, + (45, :inner) + ) diff --git a/src/output.jl b/src/output.jl index 1501c214..92f7ec01 100644 --- a/src/output.jl +++ b/src/output.jl @@ -112,7 +112,7 @@ function savefig(plt::Plot, fn::AbstractString) fn = abspath(expanduser(fn)) # get the extension - fn, ext = splitext(fn) + _, ext = splitext(fn) ext = chop(ext, head = 1, tail = 0) if isempty(ext) ext = defaultOutputFormat(plt) @@ -194,9 +194,9 @@ function _display(plt::Plot) @warn("_display is not defined for this backend.") end +Base.show(io::IO, m::MIME"text/plain", plt::Plot) = show(io, plt) # for writing to io streams... first prepare, then callback for mime in ( - "text/plain", "text/html", "image/png", "image/eps", @@ -221,9 +221,6 @@ end Base.show(io::IO, m::MIME"application/prs.juno.plotpane+html", plt::Plot) = showjuno(io, MIME("text/html"), plt) -# default text/plain for all backends -_show(io::IO, ::MIME{Symbol("text/plain")}, plt::Plot) = show(io, plt) - "Close all open gui windows of the current backend" closeall() = closeall(backend()) @@ -249,25 +246,17 @@ closeall() = closeall(backend()) # Atom PlotPane # --------------------------------------------------------- function showjuno(io::IO, m, plt) - sz = collect(plt[:size]) dpi = plt[:dpi] - thickness_scaling = plt[:thickness_scaling] - jsize = get(io, :juno_plotsize, [400, 500]) jratio = get(io, :juno_dpi_ratio, 1) - scale = minimum(jsize[i] / sz[i] for i in 1:2) - plt[:size] = [s * scale for s in sz] plt[:dpi] = jratio * Plots.DPI - plt[:thickness_scaling] *= scale prepare_output(plt) try _showjuno(io, m, plt) finally - plt[:size] = sz plt[:dpi] = dpi - plt[:thickness_scaling] = thickness_scaling end end diff --git a/src/pipeline.jl b/src/pipeline.jl index c58aff1a..63bed9ef 100644 --- a/src/pipeline.jl +++ b/src/pipeline.jl @@ -4,43 +4,31 @@ function RecipesPipeline.warn_on_recipe_aliases!( plt::Plot, - plotattributes, - recipe_type, - args..., + plotattributes::AKW, + recipe_type::Symbol, + @nospecialize(args) ) for k in keys(plotattributes) if !is_default_attribute(k) dk = get(_keyAliases, k, k) if k !== dk - @warn "Attribute alias `$k` detected in the $recipe_type recipe defined for the signature $(_signature_string(Val{recipe_type}, args...)). To ensure expected behavior it is recommended to use the default attribute `$dk`." + if recipe_type == :user + signature_string = RecipesPipeline.userrecipe_signature_string(args) + elseif recipe_type == :type + signature_string = RecipesPipeline.typerecipe_signature_string(args) + elseif recipe_type == :plot + signature_string = RecipesPipeline.plotrecipe_signature_string(args) + elseif recipe_type == :series + signature_string = RecipesPipeline.seriesrecipe_signature_string(args) + else + throw(ArgumentError("Invalid recipe type `$recipe_type`")) + end + @warn "Attribute alias `$k` detected in the $recipe_type recipe defined for the signature $signature_string. To ensure expected behavior it is recommended to use the default attribute `$dk`." end plotattributes[dk] = RecipesPipeline.pop_kw!(plotattributes, k) end end end -function RecipesPipeline.warn_on_recipe_aliases!( - plt::Plot, - v::AbstractVector, - recipe_type, - args..., -) - foreach(x -> RecipesPipeline.warn_on_recipe_aliases!(plt, x, recipe_type, args...), v) -end -function RecipesPipeline.warn_on_recipe_aliases!( - plt::Plot, - rd::RecipeData, - recipe_type, - args..., -) - RecipesPipeline.warn_on_recipe_aliases!(plt, rd.plotattributes, recipe_type, args...) -end - -function _signature_string(::Type{Val{:user}}, args...) - return string("(::", join(string.(typeof.(args)), ", ::"), ")") -end -_signature_string(::Type{Val{:type}}, T) = "(::Type{$T}, ::$T)" -_signature_string(::Type{Val{:plot}}, st) = "(::Type{Val{:$st}}, ::AbstractPlot)" -_signature_string(::Type{Val{:series}}, st) = "(::Type{Val{:$st}}, x, y, z)" ## Grouping @@ -49,12 +37,23 @@ RecipesPipeline.splittable_attribute(plt::Plot, key, val::SeriesAnnotations, len RecipesPipeline.splittable_attribute(plt, key, val.strs, len) function RecipesPipeline.split_attribute(plt::Plot, key, val::SeriesAnnotations, indices) - split_strs = _RecipesPipeline.split_attribute(key, val.strs, indices) + split_strs = RecipesPipeline.split_attribute(plt, key, val.strs, indices) return SeriesAnnotations(split_strs, val.font, val.baseshape, val.scalefactor) end ## Preprocessing attributes +function RecipesPipeline.preprocess_axis_args!(plt::Plot, plotattributes, letter) + # Fix letter for seriestypes that are x only but data gets passed as y + if treats_y_as_x(get(plotattributes, :seriestype, :path)) + if get(plotattributes, :orientation, :vertical) == :vertical + letter = :x + end + end + + plotattributes[:letter] = letter + RecipesPipeline.preprocess_axis_args!(plt, plotattributes) +end RecipesPipeline.preprocess_attributes!(plt::Plot, plotattributes) = RecipesPipeline.preprocess_attributes!(plotattributes) # in src/args.jl @@ -142,7 +141,7 @@ function _add_smooth_kw(kw_list::Vector{KW}, kw::AKW) end -RecipesPipeline.get_axis_limits(plt::Plot, f, letter) = axis_limits(plt[1], letter) +RecipesPipeline.get_axis_limits(plt::Plot, letter) = axis_limits(plt[1], letter) ## Plot recipes @@ -155,8 +154,23 @@ RecipesPipeline.type_alias(plt::Plot) = get(_typeAliases, st, st) function RecipesPipeline.plot_setup!(plt::Plot, plotattributes, kw_list) _plot_setup(plt, plotattributes, kw_list) _subplot_setup(plt, plotattributes, kw_list) + return nothing end +function RecipesPipeline.process_sliced_series_attributes!(plt::Plots.Plot, kw_list) + # swap errors + err_inds = findall(kw -> get(kw, :seriestype, :path) in (:xerror, :yerror, :zerror), kw_list) + for ind in err_inds + if get(kw_list[ind-1],:seriestype,:path) == :scatter + tmp = copy(kw_list[ind]) + kw_list[ind] = copy(kw_list[ind-1]) + kw_list[ind-1] = tmp + end + end + return nothing +end + + # TODO: Should some of this logic be moved to RecipesPipeline? function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW}) # merge in anything meant for the Plot @@ -292,6 +306,7 @@ function RecipesPipeline.slice_series_attributes!(plt::Plot, kw_list, kw) # in series attributes given as vector with one element per series, # select the value for current series _slice_series_args!(kw, plt, sp, series_idx(kw_list, kw)) + return nothing end RecipesPipeline.series_defaults(plt::Plot) = _series_defaults # in args.jl @@ -316,7 +331,7 @@ function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where {T} st = _override_seriestype_check(plotattributes, st) # change to a 3d projection for this subplot? - if RecipesPipeline.needs_3d_axes(st) + if RecipesPipeline.needs_3d_axes(st) || (st == :quiver && plotattributes[:z] !== nothing) sp.attr[:projection] = "3d" end @@ -330,9 +345,9 @@ end function _override_seriestype_check(plotattributes::AKW, st::Symbol) # do we want to override the series type? - if !RecipesPipeline.is3d(st) && !(st in (:contour, :contour3d)) + if !RecipesPipeline.is3d(st) && !(st in (:contour, :contour3d, :quiver)) z = plotattributes[:z] - if !isa(z, Nothing) && + if z !== nothing && (size(plotattributes[:x]) == size(plotattributes[:y]) == size(z)) st = (st == :scatter ? :scatter3d : :path3d) plotattributes[:seriestype] = st @@ -341,6 +356,14 @@ function _override_seriestype_check(plotattributes::AKW, st::Symbol) st end +function needs_any_3d_axes(sp::Subplot) + any( + RecipesPipeline.needs_3d_axes( + _override_seriestype_check(s.plotattributes, s.plotattributes[:seriestype]) + ) for s in series_list(sp) + ) +end + function _expand_subplot_extrema(sp::Subplot, plotattributes::AKW, st::Symbol) # adjust extrema and discrete info if st == :image diff --git a/src/plot.jl b/src/plot.jl index e52ac499..fb802774 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -23,7 +23,38 @@ current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = plot) Base.string(plt::Plot) = "Plot{$(plt.backend) n=$(plt.n)}" Base.print(io::IO, plt::Plot) = print(io, string(plt)) -Base.show(io::IO, plt::Plot) = print(io, string(plt)) +function Base.show(io::IO, plt::Plot) + print(io, string(plt)) + sp_ekwargs = getindex.(plt.subplots, :extra_kwargs) + s_ekwargs = getindex.(plt.series_list, :extra_kwargs) + if isempty(plt[:extra_plot_kwargs]) && all(isempty, sp_ekwargs) && all(isempty, s_ekwargs) + return + end + print(io,"\nCaptured extra kwargs:\n") + do_show = true + for (key, value) in plt[:extra_plot_kwargs] + do_show && println(io, " Plot:") + println(io, " "^4, key, ": ", value) + do_show = false + end + do_show = true + for (i, ekwargs) in enumerate(sp_ekwargs) + for (key, value) in ekwargs + do_show && println(io, " SubplotPlot{$i}:") + println(io, " "^4, key, ": ", value) + do_show = false + end + do_show = true + end + for (i, ekwargs) in enumerate(s_ekwargs) + for (key, value) in ekwargs + do_show && println(io, " Series{$i}:") + println(io, " "^4, key, ": ", value) + do_show = false + end + do_show = true + end +end getplot(plt::Plot) = plt getattr(plt::Plot, idx::Int = 1) = plt.attr @@ -43,10 +74,11 @@ The main plot command. Use `plot` to create a new plot object, and `plot!` to ad There are lots of ways to pass in data, and lots of keyword arguments... just try it and it will likely work as expected. When you pass in matrices, it splits by columns. To see the list of available attributes, use the `plotattr([attr])` -function, where `attr` is the symbol `:Series:`, `:Subplot:`, `:Plot` or `:Axis`. Pass any attribute to `plotattr` -as a String to look up its docstring; e.g. `plotattr("seriestype")`. +function, where `attr` is the symbol `:Series`, `:Subplot`, `:Plot`, or `:Axis`. Pass any attribute to `plotattr` +as a String to look up its docstring, e.g., `plotattr("seriestype")`. """ function plot(args...; kw...) + @nospecialize # this creates a new plot with args/kw and sets it to be the current plot plotattributes = KW(kw) RecipesPipeline.preprocess_attributes!(plotattributes) @@ -59,7 +91,9 @@ end # build a new plot from existing plots # note: we split into plt1 and plts_tail so we can dispatch correctly -function plot(plt1::Plot, plts_tail::Plot...; kw...) +plot(plt1::Plot, plts_tail::Plot...; kw...) = plot!(deepcopy(plt1), deepcopy.(plts_tail)...; kw...) +function plot!(plt1::Plot, plts_tail::Plot...; kw...) + @nospecialize plotattributes = KW(kw) RecipesPipeline.preprocess_attributes!(plotattributes) @@ -140,7 +174,8 @@ end # this adds to the current plot, or creates a new plot if none are current -function plot!(args...; kw...) +function plot!(args...; kw...) + @nospecialize local plt try plt = current() @@ -152,6 +187,7 @@ end # this adds to a specific plot... most plot commands will flow through here function plot!(plt::Plot, args...; kw...) + @nospecialize plotattributes = KW(kw) RecipesPipeline.preprocess_attributes!(plotattributes) # merge!(plt.user_attr, plotattributes) @@ -164,6 +200,7 @@ end # a list of series KW dicts. # note: at entry, we only have those preprocessed args which were passed in... no default values yet function _plot!(plt::Plot, plotattributes, args) + @nospecialize RecipesPipeline.recipe_pipeline!(plt, plotattributes, args) current(plt) _do_plot_show(plt, plt[:show]) @@ -205,10 +242,12 @@ end # plot to a Subplot function plot(sp::Subplot, args...; kw...) + @nospecialize plt = sp.plt plot(plt, args...; kw..., subplot = findfirst(isequal(sp), plt.subplots)) end function plot!(sp::Subplot, args...; kw...) + @nospecialize plt = sp.plt plot!(plt, args...; kw..., subplot = findfirst(isequal(sp), plt.subplots)) end diff --git a/src/precompile.jl b/src/precompile.jl deleted file mode 100644 index 7a6e09e2..00000000 --- a/src/precompile.jl +++ /dev/null @@ -1,740 +0,0 @@ -function _precompile_() - ccall(:jl_generating_output, Cint, ()) == 1 || return nothing - isdefined(Plots, Symbol("#@layout")) && precompile(Tuple{getfield(Plots, Symbol("#@layout")), LineNumberNode, Module, Expr}) - isdefined(Plots, Symbol("#_make_hist##kw")) && precompile(Tuple{getfield(Plots, Symbol("#_make_hist##kw")), NamedTuple{(:normed, :weights), Tuple{Bool, Array{Int64, 1}}}, typeof(Plots._make_hist), Tuple{Array{Float64, 1}}, Symbol}) - isdefined(Plots, Symbol("#_make_hist##kw")) && precompile(Tuple{getfield(Plots, Symbol("#_make_hist##kw")), NamedTuple{(:normed, :weights), Tuple{Bool, Nothing}}, typeof(Plots._make_hist), Tuple{Array{Float64, 1}, Array{Float64, 1}}, Int64}) - isdefined(Plots, Symbol("#_make_hist##kw")) && precompile(Tuple{getfield(Plots, Symbol("#_make_hist##kw")), NamedTuple{(:normed, :weights), Tuple{Bool, Nothing}}, typeof(Plots._make_hist), Tuple{Array{Float64, 1}, Array{Float64, 1}}, Tuple{Int64, Int64}}) - isdefined(Plots, Symbol("#_make_hist##kw")) && precompile(Tuple{getfield(Plots, Symbol("#_make_hist##kw")), NamedTuple{(:normed, :weights), Tuple{Bool, Nothing}}, typeof(Plots._make_hist), Tuple{Array{Float64, 1}}, Symbol}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:formatter,), Tuple{Symbol}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:formatter,), Tuple{typeof(RecipesPipeline.datetimeformatter)}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:grid, :lims), Tuple{Bool, Tuple{Int64, Int64}}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:grid, :lims, :flip), Tuple{Bool, Tuple{Int64, Int64}, Bool}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:grid,), Tuple{Bool}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:gridlinewidth, :grid, :gridalpha, :gridstyle, :foreground_color_grid), Tuple{Int64, Bool, Float64, Symbol, ColorTypes.RGBA{Float64}}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:guide,), Tuple{String}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:lims, :flip, :ticks, :guide), Tuple{Tuple{Int64, Int64}, Bool, Base.StepRange{Int64, Int64}, String}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:lims,), Tuple{Tuple{Float64, Float64}}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:lims,), Tuple{Tuple{Int64, Float64}}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:lims,), Tuple{Tuple{Int64, Int64}}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:rotation,), Tuple{Int64}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:scale, :guide), Tuple{Symbol, String}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:ticks,), Tuple{Base.UnitRange{Int64}}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#attr!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#attr!##kw")), NamedTuple{(:ticks,), Tuple{Nothing}}, typeof(Plots.attr!), Plots.Axis}) - isdefined(Plots, Symbol("#contour##kw")) && precompile(Tuple{getfield(Plots, Symbol("#contour##kw")), NamedTuple{(:fill,), Tuple{Bool}}, typeof(Plots.contour), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Int}) - isdefined(Plots, Symbol("#default##kw")) && precompile(Tuple{getfield(Plots, Symbol("#default##kw")), NamedTuple{(:titlefont, :legendfontsize, :guidefont, :tickfont, :guide, :framestyle, :yminorgrid), Tuple{Tuple{Int64, String}, Int64, Tuple{Int64, Symbol}, Tuple{Int64, Symbol}, String, Symbol, Bool}}, typeof(Plots.default)}) - isdefined(Plots, Symbol("#gr_polyline##kw")) && precompile(Tuple{getfield(Plots, Symbol("#gr_polyline##kw")), NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}}, typeof(Plots.gr_polyline), Array{Float64, 1}, Array{Float64, 1}}) - isdefined(Plots, Symbol("#gr_polyline##kw")) && precompile(Tuple{getfield(Plots, Symbol("#gr_polyline##kw")), NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}}, typeof(Plots.gr_polyline), Array{Int64, 1}, Array{Float64, 1}}) - isdefined(Plots, Symbol("#gr_polyline##kw")) && precompile(Tuple{getfield(Plots, Symbol("#gr_polyline##kw")), NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}}, typeof(Plots.gr_polyline), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 1}}) - isdefined(Plots, Symbol("#gr_polyline##kw")) && precompile(Tuple{getfield(Plots, Symbol("#gr_polyline##kw")), NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}}, typeof(Plots.gr_polyline), Base.StepRange{Int64, Int64}, Array{Float64, 1}}) - isdefined(Plots, Symbol("#gr_polyline##kw")) && precompile(Tuple{getfield(Plots, Symbol("#gr_polyline##kw")), NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}}, typeof(Plots.gr_polyline), Base.UnitRange{Int64}, Array{Float64, 1}}) - isdefined(Plots, Symbol("#gr_polyline##kw")) && precompile(Tuple{getfield(Plots, Symbol("#gr_polyline##kw")), NamedTuple{(:arrowside, :arrowstyle), Tuple{Symbol, Symbol}}, typeof(Plots.gr_polyline), Base.UnitRange{Int64}, Base.UnitRange{Int64}}) - isdefined(Plots, Symbol("#gr_set_font##kw")) && precompile(Tuple{getfield(Plots, Symbol("#gr_set_font##kw")), NamedTuple{(:halign, :valign, :rotation), Tuple{Symbol, Symbol, Int64}}, typeof(Plots.gr_set_font), Plots.Font}) - isdefined(Plots, Symbol("#gr_set_font##kw")) && precompile(Tuple{getfield(Plots, Symbol("#gr_set_font##kw")), NamedTuple{(:halign, :valign, :rotation, :color), Tuple{Symbol, Symbol, Int64, ColorTypes.RGBA{Float64}}}, typeof(Plots.gr_set_font), Plots.Font}) - isdefined(Plots, Symbol("#heatmap##kw")) && precompile(Tuple{getfield(Plots, Symbol("#heatmap##kw")), NamedTuple{(:aspect_ratio,), Tuple{Int64}}, typeof(Plots.heatmap), Array{String, 1}, Int}) - isdefined(Plots, Symbol("#histogram##kw")) && precompile(Tuple{getfield(Plots, Symbol("#histogram##kw")), NamedTuple{(:bins, :weights), Tuple{Symbol, Array{Int64, 1}}}, typeof(Plots.histogram), Array{Float64, 1}}) - isdefined(Plots, Symbol("#histogram2d##kw")) && precompile(Tuple{getfield(Plots, Symbol("#histogram2d##kw")), NamedTuple{(:nbins, :show_empty_bins, :normed, :aspect_ratio), Tuple{Tuple{Int64, Int64}, Bool, Bool, Int64}}, typeof(Plots.histogram2d), Array{Base.Complex{Float64}, 1}}) - isdefined(Plots, Symbol("#histogram2d##kw")) && precompile(Tuple{getfield(Plots, Symbol("#histogram2d##kw")), NamedTuple{(:nbins,), Tuple{Int64}}, typeof(Plots.histogram2d), Array{Float64, 1}, Array{Float64, 1}}) - isdefined(Plots, Symbol("#hline!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#hline!##kw")), NamedTuple{(:line,), Tuple{Tuple{Int64, Symbol, Float64, Array{Symbol, 2}}}}, typeof(Plots.hline!), Array{Float64, 2}}) - isdefined(Plots, Symbol("#lens!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#lens!##kw")), NamedTuple{(:inset,), Tuple{Tuple{Int64, Measures.BoundingBox{Tuple{Measures.Length{:w, Float64}, Measures.Length{:h, Float64}}, Tuple{Measures.Length{:w, Float64}, Measures.Length{:h, Float64}}}}}}, typeof(Plots.lens!), Array{Int64, 1}, Int}) - isdefined(Plots, Symbol("#pie##kw")) && precompile(Tuple{getfield(Plots, Symbol("#pie##kw")), NamedTuple{(:title, :l), Tuple{String, Float64}}, typeof(Plots.pie), Array{String, 1}, Int}) - isdefined(Plots, Symbol("#plotly_annotation_dict##kw")) && precompile(Tuple{getfield(Plots, Symbol("#plotly_annotation_dict##kw")), NamedTuple{(:xref, :yref), Tuple{String, String}}, typeof(Plots.plotly_annotation_dict), Float64, Float64, Plots.PlotText}) - isdefined(Plots, Symbol("#plotly_annotation_dict##kw")) && precompile(Tuple{getfield(Plots, Symbol("#plotly_annotation_dict##kw")), NamedTuple{(:xref, :yref), Tuple{String, String}}, typeof(Plots.plotly_annotation_dict), Float64, Float64, String}) - isdefined(Plots, Symbol("#plotly_annotation_dict##kw")) && precompile(Tuple{getfield(Plots, Symbol("#plotly_annotation_dict##kw")), NamedTuple{(:xref, :yref), Tuple{String, String}}, typeof(Plots.plotly_annotation_dict), Int64, Float64, Plots.PlotText}) - isdefined(Plots, Symbol("#plotly_annotation_dict##kw")) && precompile(Tuple{getfield(Plots, Symbol("#plotly_annotation_dict##kw")), NamedTuple{(:xref, :yref), Tuple{String, String}}, typeof(Plots.plotly_annotation_dict), Int64, Float64, String}) - isdefined(Plots, Symbol("#portfoliocomposition##kw")) && precompile(Tuple{getfield(Plots, Symbol("#portfoliocomposition##kw")), NamedTuple{(:labels,), Tuple{Array{String, 2}}}, typeof(Plots.portfoliocomposition), Array{Float64, 2}, Int}) - isdefined(Plots, Symbol("#scatter!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#scatter!##kw")), NamedTuple{(:marker, :series_annotations), Tuple{Tuple{Int64, Float64, Symbol}, Array{Any, 1}}}, typeof(Plots.scatter!), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Int}) - isdefined(Plots, Symbol("#scatter!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#scatter!##kw")), NamedTuple{(:markersize, :c), Tuple{Int64, Symbol}}, typeof(Plots.scatter!), Array{Float64, 1}}) - isdefined(Plots, Symbol("#scatter!##kw")) && precompile(Tuple{getfield(Plots, Symbol("#scatter!##kw")), NamedTuple{(:zcolor, :m, :ms, :lab), Tuple{Array{Float64, 1}, Tuple{Symbol, Float64, Plots.Stroke}, Array{Float64, 1}, String}}, typeof(Plots.scatter!), Array{Float64, 1}}) - isdefined(Plots, Symbol("#scatter##kw")) && precompile(Tuple{getfield(Plots, Symbol("#scatter##kw")), NamedTuple{(:framestyle, :title, :color, :layout, :label, :markerstrokewidth, :ticks), Tuple{Array{Symbol, 2}, Array{String, 2}, Base.ReshapedArray{Int64, 2, Base.UnitRange{Int64}, Tuple{}}, Int64, String, Int64, Base.UnitRange{Int64}}}, typeof(Plots.scatter), Array{Array{Float64, 1}, 1}, Array{Array{Float64, 1}, 1}}) - isdefined(Plots, Symbol("#scatter##kw")) && precompile(Tuple{getfield(Plots, Symbol("#scatter##kw")), NamedTuple{(:m, :lab, :bg, :xlim, :ylim), Tuple{Tuple{Int64, Symbol}, Array{String, 2}, Symbol, Tuple{Int64, Int64}, Tuple{Int64, Int64}}}, typeof(Plots.scatter), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Int}) - isdefined(Plots, Symbol("#scatter##kw")) && precompile(Tuple{getfield(Plots, Symbol("#scatter##kw")), NamedTuple{(:marker_z, :color, :legend), Tuple{typeof(Base.:+), Symbol, Bool}}, typeof(Plots.scatter), Array{Float64, 1}, Array{Float64, 1}}) - isdefined(Plots, Symbol("#standalone_html##kw")) && precompile(Tuple{getfield(Plots, Symbol("#standalone_html##kw")), NamedTuple{(:title,), Tuple{String}}, typeof(Plots.standalone_html), Plots.Plot{Plots.PlotlyBackend}}) - isdefined(Plots, Symbol("#test_examples##kw")) && precompile(Tuple{getfield(Plots, Symbol("#test_examples##kw")), NamedTuple{(:skip,), Tuple{Array{Int64, 1}}}, typeof(Plots.test_examples), Symbol}) - precompile(Tuple{typeof(Plots.__init__)}) - precompile(Tuple{typeof(Plots._add_errorbar_kw), Array{Base.Dict{Symbol, Any}, 1}, Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots._add_markershape), Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots._add_smooth_kw), Array{Base.Dict{Symbol, Any}, 1}, Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots._add_the_series), Plots.Plot{Plots.GRBackend}, Plots.Subplot{Plots.GRBackend}, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots._add_the_series), Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots._as_gradient), PlotUtils.ContinuousColorGradient}) - precompile(Tuple{typeof(Plots._backend_instance), Symbol}) - precompile(Tuple{typeof(Plots._bin_centers), Array{Float64, 1}}) - precompile(Tuple{typeof(Plots._binbarlike_baseline), Float64, Symbol}) - precompile(Tuple{typeof(Plots._cbar_unique), Array{Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, 1}, String}) - precompile(Tuple{typeof(Plots._cbar_unique), Array{Int64, 1}, String}) - precompile(Tuple{typeof(Plots._cbar_unique), Array{Nothing, 1}, String}) - precompile(Tuple{typeof(Plots._cbar_unique), Array{PlotUtils.ContinuousColorGradient, 1}, String}) - precompile(Tuple{typeof(Plots._cbar_unique), Array{Symbol, 1}, String}) - precompile(Tuple{typeof(Plots._create_backend_figure), Plots.Plot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots._create_backend_figure), Plots.Plot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots._cycle), Array{Any, 1}, Int64}) - precompile(Tuple{typeof(Plots._cycle), Array{Float64, 1}, Array{Int64, 1}}) - precompile(Tuple{typeof(Plots._cycle), Array{Float64, 1}, Base.StepRange{Int64, Int64}}) - precompile(Tuple{typeof(Plots._cycle), Array{Float64, 1}, Base.UnitRange{Int64}}) - precompile(Tuple{typeof(Plots._cycle), Array{Float64, 1}, Int64}) - precompile(Tuple{typeof(Plots._cycle), Array{Plots.Subplot{T} where T<:RecipesBase.AbstractBackend, 1}, Int64}) - precompile(Tuple{typeof(Plots._cycle), Array{String, 1}, Int64}) - precompile(Tuple{typeof(Plots._cycle), Base.OneTo{Int64}, Array{Int64, 1}}) - precompile(Tuple{typeof(Plots._cycle), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Int64}) - precompile(Tuple{typeof(Plots._cycle), Base.StepRange{Int64, Int64}, Array{Int64, 1}}) - precompile(Tuple{typeof(Plots._cycle), ColorTypes.RGBA{Float64}, Int64}) - precompile(Tuple{typeof(Plots._cycle), Float64, Int64}) - precompile(Tuple{typeof(Plots._cycle), Int64, Base.StepRange{Int64, Int64}}) - precompile(Tuple{typeof(Plots._cycle), Int64, Int64}) - precompile(Tuple{typeof(Plots._cycle), Nothing, Array{Int64, 1}}) - precompile(Tuple{typeof(Plots._cycle), Nothing, Base.UnitRange{Int64}}) - precompile(Tuple{typeof(Plots._cycle), Nothing, Int64}) - precompile(Tuple{typeof(Plots._cycle), PlotUtils.ColorPalette, Int64}) - precompile(Tuple{typeof(Plots._cycle), PlotUtils.ContinuousColorGradient, Int64}) - precompile(Tuple{typeof(Plots._cycle), Plots.Shape, Int64}) - precompile(Tuple{typeof(Plots._cycle), Plots.Subplot{Plots.GRBackend}, Int64}) - precompile(Tuple{typeof(Plots._cycle), Plots.Subplot{Plots.PlotlyBackend}, Int64}) - precompile(Tuple{typeof(Plots._cycle), Symbol, Int64}) - precompile(Tuple{typeof(Plots._display), Plots.Plot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots._do_plot_show), Plots.Plot{Plots.GRBackend}, Bool}) - precompile(Tuple{typeof(Plots._do_plot_show), Plots.Plot{Plots.GRBackend}, Symbol}) - precompile(Tuple{typeof(Plots._do_plot_show), Plots.Plot{Plots.PlotlyBackend}, Bool}) - precompile(Tuple{typeof(Plots._expand_subplot_extrema), Plots.Subplot{Plots.GRBackend}, RecipesPipeline.DefaultsDict, Symbol}) - precompile(Tuple{typeof(Plots._expand_subplot_extrema), Plots.Subplot{Plots.PlotlyBackend}, RecipesPipeline.DefaultsDict, Symbol}) - precompile(Tuple{typeof(Plots._heatmap_edges), Array{Float64, 1}, Bool}) - precompile(Tuple{typeof(Plots._hist_edge), Tuple{Array{Float64, 1}}, Int64, Symbol}) - precompile(Tuple{typeof(Plots._hist_edges), Tuple{Array{Float64, 1}, Array{Float64, 1}}, Int64}) - precompile(Tuple{typeof(Plots._hist_edges), Tuple{Array{Float64, 1}, Array{Float64, 1}}, Tuple{Int64, Int64}}) - precompile(Tuple{typeof(Plots._hist_edges), Tuple{Array{Float64, 1}}, Symbol}) - precompile(Tuple{typeof(Plots._initialize_backend), Plots.PlotlyBackend}) - precompile(Tuple{typeof(Plots._override_seriestype_check), RecipesPipeline.DefaultsDict, Symbol}) - precompile(Tuple{typeof(Plots._pick_default_backend)}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Array{Float64, 1}, 1}, Array{Array{Float64, 1}, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Array{T, 1} where T, 1}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Array{T, 1} where T, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Base.Complex{Float64}, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Dates.DateTime, 1}, Base.UnitRange{Int64}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Float64, 1}, Array{Float64, 1}, Base.UnitRange{Int64}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Float64, 1}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Function, 1}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Function, 1}, Float64, Float64}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Function, 1}, Int64}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Int64, 1}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Int64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Plots.OHLC{T} where T<:Real, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{String, 1}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{String, 1}, Array{String, 1}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Tuple{Int64, Real}, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Union{Base.Missing, Int64}, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Base.StepRange{Int64, Int64}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Base.UnitRange{Int64}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Plots.PortfolioComposition}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{Plots.Spy}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{typeof(Base.log), Int64}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Tuple{}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Array{Float64, 1}, 1}, Array{Array{Float64, 1}, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Array{T, 1} where T, 1}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Array{T, 1} where T, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Base.Complex{Float64}, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Dates.DateTime, 1}, Base.UnitRange{Int64}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Float64, 1}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Function, 1}, Float64, Float64}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Int64, 1}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Int64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Plots.OHLC{T} where T<:Real, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{String, 1}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{String, 1}, Array{String, 1}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Tuple{Int64, Real}, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Array{Union{Base.Missing, Int64}, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Base.StepRange{Int64, Int64}, Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Base.UnitRange{Int64}}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Plots.PortfolioComposition}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{Plots.Spy}}) - precompile(Tuple{typeof(Plots._plot!), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Tuple{}}) - precompile(Tuple{typeof(Plots._plot_setup), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Array{Base.Dict{Symbol, Any}, 1}}) - precompile(Tuple{typeof(Plots._plot_setup), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Array{Base.Dict{Symbol, Any}, 1}}) - precompile(Tuple{typeof(Plots._plotly_framestyle), Symbol}) - precompile(Tuple{typeof(Plots._plots_defaults)}) - precompile(Tuple{typeof(Plots._prepare_subplot), Plots.Plot{Plots.GRBackend}, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots._prepare_subplot), Plots.Plot{Plots.PlotlyBackend}, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots._preprocess_barlike), RecipesPipeline.DefaultsDict, Array{Float64, 1}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots._preprocess_barlike), RecipesPipeline.DefaultsDict, Array{Int64, 1}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots._preprocess_barlike), RecipesPipeline.DefaultsDict, Base.OneTo{Int64}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots._preprocess_binlike), RecipesPipeline.DefaultsDict, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots._preprocess_userrecipe), Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots._replace_linewidth), RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots._replace_markershape), Array{Symbol, 2}}) - precompile(Tuple{typeof(Plots._replace_markershape), Plots.Shape}) - precompile(Tuple{typeof(Plots._scale_adjusted_values), Type{Float64}, Array{Float64, 1}, Symbol}) - precompile(Tuple{typeof(Plots._series_index), RecipesPipeline.DefaultsDict, Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots._series_index), RecipesPipeline.DefaultsDict, Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots._show), Base.IOStream, Base.Multimedia.MIME{Symbol("image/png")}, Plots.Plot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots._slice_series_args!), Base.Dict{Symbol, Any}, Plots.Plot{Plots.GRBackend}, Plots.Subplot{Plots.GRBackend}, Int64}) - precompile(Tuple{typeof(Plots._slice_series_args!), Base.Dict{Symbol, Any}, Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}, Int64}) - precompile(Tuple{typeof(Plots._slice_series_args!), RecipesPipeline.DefaultsDict, Plots.Plot{Plots.GRBackend}, Plots.Subplot{Plots.GRBackend}, Int64}) - precompile(Tuple{typeof(Plots._slice_series_args!), RecipesPipeline.DefaultsDict, Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}, Int64}) - precompile(Tuple{typeof(Plots._subplot_setup), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Array{Base.Dict{Symbol, Any}, 1}}) - precompile(Tuple{typeof(Plots._subplot_setup), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Array{Base.Dict{Symbol, Any}, 1}}) - precompile(Tuple{typeof(Plots._transform_ticks), Base.StepRange{Int64, Int64}}) - precompile(Tuple{typeof(Plots._transform_ticks), Base.UnitRange{Int64}}) - precompile(Tuple{typeof(Plots._transform_ticks), Nothing}) - precompile(Tuple{typeof(Plots._transform_ticks), Symbol}) - precompile(Tuple{typeof(Plots._update_axis), Plots.Axis, Base.Dict{Symbol, Any}, Symbol, Int64}) - precompile(Tuple{typeof(Plots._update_axis), Plots.Axis, RecipesPipeline.DefaultsDict, Symbol, Int64}) - precompile(Tuple{typeof(Plots._update_axis), Plots.Plot{Plots.GRBackend}, Plots.Subplot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Symbol, Int64}) - precompile(Tuple{typeof(Plots._update_axis), Plots.Plot{Plots.GRBackend}, Plots.Subplot{Plots.GRBackend}, RecipesPipeline.DefaultsDict, Symbol, Int64}) - precompile(Tuple{typeof(Plots._update_axis), Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Symbol, Int64}) - precompile(Tuple{typeof(Plots._update_axis), Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}, RecipesPipeline.DefaultsDict, Symbol, Int64}) - precompile(Tuple{typeof(Plots._update_axis_colors), Plots.Axis}) - precompile(Tuple{typeof(Plots._update_axis_links), Plots.Plot{Plots.GRBackend}, Plots.Axis, Symbol}) - precompile(Tuple{typeof(Plots._update_axis_links), Plots.Plot{Plots.PlotlyBackend}, Plots.Axis, Symbol}) - precompile(Tuple{typeof(Plots._update_clims), Float64, Float64, Float64, Float64}) - precompile(Tuple{typeof(Plots._update_clims), Float64, Float64, Int64, Int64}) - precompile(Tuple{typeof(Plots._update_min_padding!), Plots.GridLayout}) - precompile(Tuple{typeof(Plots._update_min_padding!), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots._update_min_padding!), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots._update_plot_args), Plots.Plot{Plots.GRBackend}, Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots._update_plot_args), Plots.Plot{Plots.GRBackend}, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots._update_plot_args), Plots.Plot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots._update_plot_args), Plots.Plot{Plots.PlotlyBackend}, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots._update_series_attributes!), RecipesPipeline.DefaultsDict, Plots.Plot{Plots.GRBackend}, Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots._update_series_attributes!), RecipesPipeline.DefaultsDict, Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots._update_subplot_args), Plots.Plot{Plots.GRBackend}, Plots.Subplot{Plots.GRBackend}, Base.Dict{Symbol, Any}, Int64, Bool}) - precompile(Tuple{typeof(Plots._update_subplot_args), Plots.Plot{Plots.GRBackend}, Plots.Subplot{Plots.GRBackend}, RecipesPipeline.DefaultsDict, Int64, Bool}) - precompile(Tuple{typeof(Plots._update_subplot_args), Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}, Base.Dict{Symbol, Any}, Int64, Bool}) - precompile(Tuple{typeof(Plots._update_subplot_args), Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}, RecipesPipeline.DefaultsDict, Int64, Bool}) - precompile(Tuple{typeof(Plots._update_subplot_colors), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots._update_subplot_colors), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots._update_subplot_periphery), Plots.Subplot{Plots.GRBackend}, Array{Any, 1}}) - precompile(Tuple{typeof(Plots._update_subplot_periphery), Plots.Subplot{Plots.PlotlyBackend}, Array{Any, 1}}) - precompile(Tuple{typeof(Plots.addExtension), String, String}) - precompile(Tuple{typeof(Plots.add_layout_pct!), Base.Dict{Symbol, Any}, Expr, Int64, Int64}) - precompile(Tuple{typeof(Plots.aliasesAndAutopick), RecipesPipeline.DefaultsDict, Symbol, Base.Dict{Symbol, Symbol}, Array{Symbol, 1}, Int64}) - precompile(Tuple{typeof(Plots.allAlphas), Int64}) - precompile(Tuple{typeof(Plots.allStyles), Int64}) - precompile(Tuple{typeof(Plots.allStyles), Symbol}) - precompile(Tuple{typeof(Plots.annotate!), Array{Tuple{Int64, Float64, Plots.PlotText}, 1}}) - precompile(Tuple{typeof(Plots.arrow), Int64}) - precompile(Tuple{typeof(Plots.attr), Plots.EmptyLayout, Symbol, Symbol}) - precompile(Tuple{typeof(Plots.attr), Plots.EmptyLayout, Symbol}) - precompile(Tuple{typeof(Plots.autopick_ignore_none_auto), Array{Symbol, 1}, Int64}) - precompile(Tuple{typeof(Plots.axis_drawing_info), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.axis_drawing_info_3d), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.axis_limits), Plots.Subplot{Plots.GRBackend}, Symbol, Bool, Bool}) - precompile(Tuple{typeof(Plots.axis_limits), Plots.Subplot{Plots.GRBackend}, Symbol}) - precompile(Tuple{typeof(Plots.axis_limits), Plots.Subplot{Plots.PlotlyBackend}, Symbol, Bool, Bool}) - precompile(Tuple{typeof(Plots.axis_limits), Plots.Subplot{Plots.PlotlyBackend}, Symbol}) - precompile(Tuple{typeof(Plots.backend), Plots.GRBackend}) - precompile(Tuple{typeof(Plots.backend), Plots.PlotlyBackend}) - precompile(Tuple{typeof(Plots.backend), Symbol}) - precompile(Tuple{typeof(Plots.backend)}) - precompile(Tuple{typeof(Plots.bar), Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.bbox!), Plots.GridLayout, Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.bbox!), Plots.Subplot{Plots.GRBackend}, Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.bbox!), Plots.Subplot{Plots.PlotlyBackend}, Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.bbox), Float64, Float64, Float64, Float64}) - precompile(Tuple{typeof(Plots.bbox), Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}) - precompile(Tuple{typeof(Plots.bbox_to_pcts), Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}, Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}, Bool}) - precompile(Tuple{typeof(Plots.bbox_to_pcts), Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}, Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}) - precompile(Tuple{typeof(Plots.bottom), Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.bottom), Measures.BoundingBox{Tuple{Measures.Length{:w, Float64}, Measures.Length{:h, Float64}}, Tuple{Measures.Length{:w, Float64}, Measures.Length{:h, Float64}}}}) - precompile(Tuple{typeof(Plots.bottompad), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.bottompad), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.build_layout), Plots.GridLayout, Int64, Array{Plots.Plot{T} where T<:RecipesBase.AbstractBackend, 1}}) - precompile(Tuple{typeof(Plots.build_layout), Plots.GridLayout, Int64}) - precompile(Tuple{typeof(Plots.build_layout), RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.calc_num_subplots), Plots.EmptyLayout}) - precompile(Tuple{typeof(Plots.calc_num_subplots), Plots.GridLayout}) - precompile(Tuple{typeof(Plots.color_or_nothing!), RecipesPipeline.DefaultsDict, Symbol}) - precompile(Tuple{typeof(Plots.colorbar_style), Plots.Series}) - precompile(Tuple{typeof(Plots.compute_gridsize), Int64, Int64, Int64}) - precompile(Tuple{typeof(Plots.concatenate_fillrange), Base.UnitRange{Int64}, Tuple{Array{Float64, 1}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots.contour), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Int}) - precompile(Tuple{typeof(Plots.contour_levels), Plots.Series, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.convertLegendValue), Bool}) - precompile(Tuple{typeof(Plots.convertLegendValue), Symbol}) - precompile(Tuple{typeof(Plots.convert_sci_unicode), String}) - precompile(Tuple{typeof(Plots.convert_to_polar), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 1}, Tuple{Int64, Float64}}) - precompile(Tuple{typeof(Plots.copy_series!), Plots.Series, Symbol}) - precompile(Tuple{typeof(Plots.create_grid), Expr}) - precompile(Tuple{typeof(Plots.create_grid), Symbol}) - precompile(Tuple{typeof(Plots.create_grid_curly), Expr}) - precompile(Tuple{typeof(Plots.create_grid_vcat), Expr}) - precompile(Tuple{typeof(Plots.default), Symbol, Bool}) - precompile(Tuple{typeof(Plots.default), Symbol, Int64}) - precompile(Tuple{typeof(Plots.default), Symbol, String}) - precompile(Tuple{typeof(Plots.default), Symbol, Symbol}) - precompile(Tuple{typeof(Plots.default), Symbol}) - precompile(Tuple{typeof(Plots.default_should_widen), Plots.Axis}) - precompile(Tuple{typeof(Plots.discrete_value!), Plots.Axis, Array{String, 1}}) - precompile(Tuple{typeof(Plots.discrete_value!), Plots.Axis, Array{Union{Base.Missing, Float64}, 1}}) - precompile(Tuple{typeof(Plots.discrete_value!), Plots.Axis, Base.Missing}) - precompile(Tuple{typeof(Plots.discrete_value!), Plots.Axis, Char}) - precompile(Tuple{typeof(Plots.discrete_value!), Plots.Axis, String}) - precompile(Tuple{typeof(Plots.ensure_gradient!), RecipesPipeline.DefaultsDict, Symbol, Symbol}) - precompile(Tuple{typeof(Plots.error_coords), Array{Float64, 1}, Array{Float64, 1}, Array{Float64, 1}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.error_coords), Array{Float64, 1}, Array{Float64, 1}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.error_style!), RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.error_zipit), Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, Array{Int64, 1}}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, Base.OneTo{Int64}}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, Base.StepRange{Int64, Int64}}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, Base.UnitRange{Int64}}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, Float64}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, Int64}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, RecipesPipeline.Surface{Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Axis, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Subplot{Plots.GRBackend}, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.expand_extrema!), Plots.Subplot{Plots.PlotlyBackend}, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.extend_by_data!), Array{Float64, 1}, Float64}) - precompile(Tuple{typeof(Plots.extend_series_data!), Plots.Series, Float64, Symbol}) - precompile(Tuple{typeof(Plots.fakedata), Int64, Int64}) - precompile(Tuple{typeof(Plots.fg_color), RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.font), Int64, Int}) - precompile(Tuple{typeof(Plots.font), String, Int}) - precompile(Tuple{typeof(Plots.font), Symbol, Int}) - precompile(Tuple{typeof(Plots.frame), Plots.Animation, Plots.Plot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.frame), Plots.Animation}) - precompile(Tuple{typeof(Plots.get_aspect_ratio), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.get_aspect_ratio), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.get_axis), Plots.Subplot{Plots.GRBackend}, Symbol}) - precompile(Tuple{typeof(Plots.get_axis), Plots.Subplot{Plots.PlotlyBackend}, Symbol}) - precompile(Tuple{typeof(Plots.get_clims), Plots.Series}) - precompile(Tuple{typeof(Plots.get_clims), Plots.Subplot{Plots.GRBackend}, Plots.Series}) - precompile(Tuple{typeof(Plots.get_clims), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.get_clims), Plots.Subplot{Plots.PlotlyBackend}, Plots.Series}) - precompile(Tuple{typeof(Plots.get_clims), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.get_colorgradient), Plots.Series}) - precompile(Tuple{typeof(Plots.get_fillalpha), Plots.Series, Int64}) - precompile(Tuple{typeof(Plots.get_fillalpha), Plots.Series}) - precompile(Tuple{typeof(Plots.get_fillcolor), Plots.Series, Float64, Float64, Int64}) - precompile(Tuple{typeof(Plots.get_fillcolor), Plots.Series, Tuple{Float64, Float64}, Int64}) - precompile(Tuple{typeof(Plots.get_fillcolor), Plots.Series, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.get_gradient), PlotUtils.ContinuousColorGradient}) - precompile(Tuple{typeof(Plots.get_linealpha), Plots.Series, Int64}) - precompile(Tuple{typeof(Plots.get_linealpha), Plots.Series}) - precompile(Tuple{typeof(Plots.get_linecolor), Plots.Series, Float64, Float64, Int64}) - precompile(Tuple{typeof(Plots.get_linecolor), Plots.Series, Tuple{Float64, Float64}, Int64}) - precompile(Tuple{typeof(Plots.get_linecolor), Plots.Series, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.get_linestyle), Plots.Series, Int64}) - precompile(Tuple{typeof(Plots.get_linestyle), Plots.Series}) - precompile(Tuple{typeof(Plots.get_linewidth), Plots.Series, Int64}) - precompile(Tuple{typeof(Plots.get_linewidth), Plots.Series}) - precompile(Tuple{typeof(Plots.get_markeralpha), Plots.Series, Int64}) - precompile(Tuple{typeof(Plots.get_markercolor), Plots.Series, Float64, Float64, Int64}) - precompile(Tuple{typeof(Plots.get_markerstrokealpha), Plots.Series, Int64}) - precompile(Tuple{typeof(Plots.get_markerstrokecolor), Plots.Series, Int64}) - precompile(Tuple{typeof(Plots.get_minor_ticks), Plots.Subplot{Plots.GRBackend}, Plots.Axis, Tuple{Array{Float64, 1}, Array{Any, 1}}}) - precompile(Tuple{typeof(Plots.get_minor_ticks), Plots.Subplot{Plots.GRBackend}, Plots.Axis, Tuple{Array{Float64, 1}, Array{String, 1}}}) - precompile(Tuple{typeof(Plots.get_minor_ticks), Plots.Subplot{Plots.GRBackend}, Plots.Axis, Tuple{Array{Int64, 1}, Array{String, 1}}}) - precompile(Tuple{typeof(Plots.get_plotly_marker), Symbol, String}) - precompile(Tuple{typeof(Plots.get_series_color), ColorTypes.RGBA{Float64}, Plots.Subplot{Plots.GRBackend}, Int64, Symbol}) - precompile(Tuple{typeof(Plots.get_series_color), ColorTypes.RGBA{Float64}, Plots.Subplot{Plots.PlotlyBackend}, Int64, Symbol}) - precompile(Tuple{typeof(Plots.get_series_color), Int64, Plots.Subplot{Plots.GRBackend}, Int64, Symbol}) - precompile(Tuple{typeof(Plots.get_series_color), Int64, Plots.Subplot{Plots.PlotlyBackend}, Int64, Symbol}) - precompile(Tuple{typeof(Plots.get_series_color), PlotUtils.ContinuousColorGradient, Plots.Subplot{Plots.GRBackend}, Int64, Symbol}) - precompile(Tuple{typeof(Plots.get_series_color), PlotUtils.ContinuousColorGradient, Plots.Subplot{Plots.PlotlyBackend}, Int64, Symbol}) - precompile(Tuple{typeof(Plots.get_series_color), Symbol, Plots.Subplot{Plots.GRBackend}, Int64, Symbol}) - precompile(Tuple{typeof(Plots.get_series_color), Symbol, Plots.Subplot{Plots.PlotlyBackend}, Int64, Symbol}) - precompile(Tuple{typeof(Plots.get_subplot), Plots.Plot{Plots.GRBackend}, Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.get_subplot), Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.get_ticks), Plots.Subplot{Plots.GRBackend}, Plots.Axis}) - precompile(Tuple{typeof(Plots.get_ticks), Plots.Subplot{Plots.PlotlyBackend}, Plots.Axis}) - precompile(Tuple{typeof(Plots.get_xy), Array{Plots.OHLC{T} where T<:Real, 1}, Base.OneTo{Int64}}) - precompile(Tuple{typeof(Plots.get_xy), Plots.OHLC{Float64}, Int64, Float64}) - precompile(Tuple{typeof(Plots.gr_axis_height), Plots.Subplot{Plots.GRBackend}, Plots.Axis}) - precompile(Tuple{typeof(Plots.gr_axis_width), Plots.Subplot{Plots.GRBackend}, Plots.Axis}) - precompile(Tuple{typeof(Plots.gr_color), ColorTypes.RGBA{Float64}, Type{ColorTypes.RGB{Float64}}}) - precompile(Tuple{typeof(Plots.gr_colorbar_colors), Plots.Series, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.gr_contour_levels), Plots.Series, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.gr_display), Plots.Plot{Plots.GRBackend}, String}) - precompile(Tuple{typeof(Plots.gr_display), Plots.Subplot{Plots.GRBackend}, Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.gr_draw_colorbar), Plots.GRColorbar, Plots.Subplot{Plots.GRBackend}, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.gr_draw_marker), Plots.Series, Float64, Float64, Tuple{Float64, Float64}, Int64, Float64, Float64, Plots.Shape}) - precompile(Tuple{typeof(Plots.gr_draw_marker), Plots.Series, Float64, Float64, Tuple{Float64, Float64}, Int64, Float64, Float64, Symbol}) - precompile(Tuple{typeof(Plots.gr_draw_marker), Plots.Series, Int64, Float64, Tuple{Float64, Float64}, Int64, Float64, Float64, Plots.Shape}) - precompile(Tuple{typeof(Plots.gr_draw_marker), Plots.Series, Int64, Float64, Tuple{Float64, Float64}, Int64, Float64, Float64, Symbol}) - precompile(Tuple{typeof(Plots.gr_draw_marker), Plots.Series, Int64, Int64, Tuple{Float64, Float64}, Int64, Float64, Float64, Plots.Shape}) - precompile(Tuple{typeof(Plots.gr_draw_marker), Plots.Series, Int64, Int64, Tuple{Float64, Float64}, Int64, Float64, Float64, Symbol}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Array{Float64, 1}, Array{Float64, 1}, Tuple{Float64, Float64}, Int64, Int64}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Array{Float64, 1}, Array{Float64, 1}, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Array{Int64, 1}, Array{Float64, 1}, Tuple{Float64, Float64}, Int64, Int64}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Array{Int64, 1}, Array{Float64, 1}, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Array{Int64, 1}, Array{Int64, 1}, Tuple{Float64, Float64}, Int64, Int64}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Array{Int64, 1}, Array{Int64, 1}, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Base.OneTo{Int64}, Array{Float64, 1}, Tuple{Float64, Float64}, Array{Float64, 1}, Int64}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Base.OneTo{Int64}, Array{Float64, 1}, Tuple{Float64, Float64}, Int64, Int64}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Base.OneTo{Int64}, Array{Float64, 1}, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 1}, Tuple{Float64, Float64}, Int64, Int64}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 1}, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.gr_draw_markers), Plots.Series, Float64, Float64, Tuple{Float64, Float64}, Float64, Float64}) - precompile(Tuple{typeof(Plots.gr_fill_viewport), Array{Float64, 1}, ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.gr_get_ticks_size), Tuple{Array{Float64, 1}, Array{Any, 1}}, Int64}) - precompile(Tuple{typeof(Plots.gr_get_ticks_size), Tuple{Array{Float64, 1}, Array{String, 1}}, Int64}) - precompile(Tuple{typeof(Plots.gr_get_ticks_size), Tuple{Array{Int64, 1}, Array{String, 1}}, Int64}) - precompile(Tuple{typeof(Plots.gr_getcolorind), ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.gr_inqtext), Int64, Int64, String}) - precompile(Tuple{typeof(Plots.gr_legend_pos), Plots.Subplot{Plots.GRBackend}, Float64, Float64}) - precompile(Tuple{typeof(Plots.gr_linetype), Symbol}) - precompile(Tuple{typeof(Plots.gr_polaraxes), Int64, Float64, Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.gr_polyline), Array{Float64, 1}, Array{Float64, 1}, typeof(identity)}) - precompile(Tuple{typeof(Plots.gr_polyline), Array{Float64, 1}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.gr_set_bordercolor), ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.gr_set_fill), ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.gr_set_fillcolor), ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.gr_set_font), Plots.Font}) - precompile(Tuple{typeof(Plots.gr_set_gradient), PlotUtils.ContinuousColorGradient}) - precompile(Tuple{typeof(Plots.gr_set_gradient), Plots.Series}) - precompile(Tuple{typeof(Plots.gr_set_line), Float64, Symbol, ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.gr_set_line), Int64, Symbol, ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.gr_set_line), Int64, Symbol, PlotUtils.ContinuousColorGradient}) - precompile(Tuple{typeof(Plots.gr_set_linecolor), PlotUtils.ContinuousColorGradient}) - precompile(Tuple{typeof(Plots.gr_set_markercolor), ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.gr_set_textcolor), ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.gr_set_transparency), ColorTypes.RGBA{Float64}, Float64}) - precompile(Tuple{typeof(Plots.gr_set_transparency), ColorTypes.RGBA{Float64}, Int64}) - precompile(Tuple{typeof(Plots.gr_set_transparency), ColorTypes.RGBA{Float64}, Nothing}) - precompile(Tuple{typeof(Plots.gr_set_transparency), Float64}) - precompile(Tuple{typeof(Plots.gr_set_viewport_polar)}) - precompile(Tuple{typeof(Plots.gr_set_xticks_font), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.gr_set_yticks_font), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.gr_text), Float64, Float64, String}) - precompile(Tuple{typeof(Plots.gr_text_size), String, Int64}) - precompile(Tuple{typeof(Plots.gr_text_size), String}) - precompile(Tuple{typeof(Plots.gr_tick_label), Plots.Axis, String}) - precompile(Tuple{typeof(Plots.gr_update_colorbar!), Plots.GRColorbar, Plots.Series}) - precompile(Tuple{typeof(Plots.gr_viewport_from_bbox), Plots.Subplot{Plots.GRBackend}, Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}, Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.gr_w3tondc), Float64, Float64, Float64}) - precompile(Tuple{typeof(Plots.gui), Plots.Plot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.gui), Plots.Plot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.guide_padding), Plots.Axis}) - precompile(Tuple{typeof(Plots.guidefont), Plots.Axis}) - precompile(Tuple{typeof(Plots.handleColors!), Base.Dict{Symbol, Any}, Array{Symbol, 2}, Symbol}) - precompile(Tuple{typeof(Plots.handleColors!), Base.Dict{Symbol, Any}, ColorTypes.RGBA{Float64}, Symbol}) - precompile(Tuple{typeof(Plots.handleColors!), Base.Dict{Symbol, Any}, Plots.Shape, Symbol}) - precompile(Tuple{typeof(Plots.handleColors!), Base.Dict{Symbol, Any}, Symbol, Symbol}) - precompile(Tuple{typeof(Plots.has_attribute_segments), Plots.Series}) - precompile(Tuple{typeof(Plots.has_black_border_for_default), Symbol}) - precompile(Tuple{typeof(Plots.hascolorbar), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.hascolorbar), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.hasgrid), Symbol, Symbol}) - precompile(Tuple{typeof(Plots.heatmap), Array{Dates.DateTime, 1}, Int}) - precompile(Tuple{typeof(Plots.heatmap_edges), Array{Float64, 1}, Symbol, Array{Float64, 1}, Symbol, Tuple{Int64, Int64}}) - precompile(Tuple{typeof(Plots.heatmap_edges), Array{Float64, 1}, Symbol, Base.UnitRange{Int64}, Symbol, Tuple{Int64, Int64}}) - precompile(Tuple{typeof(Plots.heatmap_edges), Array{Float64, 1}, Symbol, Bool}) - precompile(Tuple{typeof(Plots.heatmap_edges), Array{Float64, 1}, Symbol}) - precompile(Tuple{typeof(Plots.heatmap_edges), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Symbol, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Symbol, Tuple{Int64, Int64}}) - precompile(Tuple{typeof(Plots.heatmap_edges), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Symbol, Bool}) - precompile(Tuple{typeof(Plots.heatmap_edges), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Symbol}) - precompile(Tuple{typeof(Plots.heatmap_edges), Base.UnitRange{Int64}, Symbol, Bool}) - precompile(Tuple{typeof(Plots.heatmap_edges), Base.UnitRange{Int64}, Symbol}) - precompile(Tuple{typeof(Plots.ignorenan_extrema), Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.ignorenan_extrema), Array{Float64, 2}}) - precompile(Tuple{typeof(Plots.ignorenan_extrema), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}}) - precompile(Tuple{typeof(Plots.ignorenan_extrema), Base.StepRange{Int64, Int64}}) - precompile(Tuple{typeof(Plots.ignorenan_extrema), Plots.Axis}) - precompile(Tuple{typeof(Plots.ignorenan_maximum), Base.OneTo{Int64}}) - precompile(Tuple{typeof(Plots.ignorenan_minimum), Array{Int64, 1}}) - precompile(Tuple{typeof(Plots.ignorenan_minimum), Base.OneTo{Int64}}) - precompile(Tuple{typeof(Plots.inline), Plots.Plot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.inline), Plots.Plot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.intersection_point), Float64, Float64, Float64, Float64, Float64, Float64}) - precompile(Tuple{typeof(Plots.is_2tuple), Int64}) - precompile(Tuple{typeof(Plots.is_2tuple), Symbol}) - precompile(Tuple{typeof(Plots.is_2tuple), Tuple{Array{Float64, 1}, Array{Float64, 1}}}) - precompile(Tuple{typeof(Plots.is_2tuple), Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.is_2tuple), Tuple{Int64, Float64}}) - precompile(Tuple{typeof(Plots.is_2tuple), Tuple{Int64, Int64}}) - precompile(Tuple{typeof(Plots.is_2tuple), Tuple{Int64, Measures.BoundingBox{Tuple{Measures.Length{:w, Float64}, Measures.Length{:h, Float64}}, Tuple{Measures.Length{:w, Float64}, Measures.Length{:h, Float64}}}}}) - precompile(Tuple{typeof(Plots.is_axis_attr), Symbol}) - precompile(Tuple{typeof(Plots.is_default_attribute), Symbol}) - precompile(Tuple{typeof(Plots.is_marker_supported), Plots.GRBackend, Symbol}) - precompile(Tuple{typeof(Plots.is_marker_supported), Plots.PlotlyBackend, Symbol}) - precompile(Tuple{typeof(Plots.is_marker_supported), Plots.Shape}) - precompile(Tuple{typeof(Plots.is_marker_supported), Plots.Stroke}) - precompile(Tuple{typeof(Plots.is_marker_supported), Symbol}) - precompile(Tuple{typeof(Plots.is_seriestype_supported), Plots.GRBackend, Symbol}) - precompile(Tuple{typeof(Plots.is_seriestype_supported), Plots.PlotlyBackend, Symbol}) - precompile(Tuple{typeof(Plots.is_seriestype_supported), Symbol}) - precompile(Tuple{typeof(Plots.is_uniformly_spaced), Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.iscontour), Plots.Series}) - precompile(Tuple{typeof(Plots.isijulia)}) - precompile(Tuple{typeof(Plots.ispolar), Plots.Series}) - precompile(Tuple{typeof(Plots.ispolar), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.ispolar), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.isvertical), Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots.isvertical), RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.iter_segments), Array{Float64, 1}, Array{Float64, 1}, Int}) - precompile(Tuple{typeof(Plots.iter_segments), Array{Float64, 1}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.iter_segments), Array{Int64, 1}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.iter_segments), Base.OneTo{Int64}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.iter_segments), Base.OneTo{Int64}, Base.UnitRange{Int64}}) - precompile(Tuple{typeof(Plots.iter_segments), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.iter_segments), Base.StepRange{Int64, Int64}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.iter_segments), Base.UnitRange{Int64}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.iter_segments), Plots.Series}) - precompile(Tuple{typeof(Plots.labelfunc), Symbol, Plots.GRBackend}) - precompile(Tuple{typeof(Plots.labelfunc), Symbol, Plots.PlotlyBackend}) - precompile(Tuple{typeof(Plots.layout_args), Base.Dict{Symbol, Any}, Int64}) - precompile(Tuple{typeof(Plots.layout_args), Int64, Int64}) - precompile(Tuple{typeof(Plots.layout_args), Int64, Plots.GridLayout}) - precompile(Tuple{typeof(Plots.layout_args), Int64, Tuple{Int64, Int64}}) - precompile(Tuple{typeof(Plots.layout_args), Int64}) - precompile(Tuple{typeof(Plots.layout_args), Plots.GridLayout}) - precompile(Tuple{typeof(Plots.layout_args), RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.left), Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.left), Measures.BoundingBox{Tuple{Measures.Length{:w, Float64}, Measures.Length{:h, Float64}}, Tuple{Measures.Length{:w, Float64}, Measures.Length{:h, Float64}}}}) - precompile(Tuple{typeof(Plots.leftpad), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.leftpad), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.legendfont), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.legendfont), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.legendtitlefont), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.link_axes!), Array{RecipesBase.AbstractLayout, 1}, Symbol}) - precompile(Tuple{typeof(Plots.link_axes!), Plots.Axis, Plots.Axis}) - precompile(Tuple{typeof(Plots.link_axes!), Plots.Axis}) - precompile(Tuple{typeof(Plots.link_axes!), Plots.GridLayout, Symbol}) - precompile(Tuple{typeof(Plots.link_axes!), Plots.Subplot{Plots.GRBackend}, Symbol}) - precompile(Tuple{typeof(Plots.link_axes!), Plots.Subplot{Plots.PlotlyBackend}, Symbol}) - precompile(Tuple{typeof(Plots.link_subplots), Array{RecipesBase.AbstractLayout, 1}, Symbol}) - precompile(Tuple{typeof(Plots.locate_annotation), Plots.Subplot{Plots.GRBackend}, Int64, Float64, Plots.PlotText}) - precompile(Tuple{typeof(Plots.locate_annotation), Plots.Subplot{Plots.PlotlyBackend}, Int64, Float64, Plots.PlotText}) - precompile(Tuple{typeof(Plots.make_fillrange_from_ribbon), Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots.make_fillrange_side), Base.UnitRange{Int64}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.make_fillrange_side), Base.UnitRange{Int64}, Base.LinRange{Float64}}) - precompile(Tuple{typeof(Plots.make_fillrange_side), Base.UnitRange{Int64}, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}}) - precompile(Tuple{typeof(Plots.make_fillrange_side), Base.UnitRange{Int64}, Int64}) - precompile(Tuple{typeof(Plots.make_steps), Array{Float64, 1}, Symbol}) - precompile(Tuple{typeof(Plots.make_steps), Array{Int64, 1}, Symbol}) - precompile(Tuple{typeof(Plots.make_steps), Base.OneTo{Int64}, Symbol}) - precompile(Tuple{typeof(Plots.make_steps), Nothing, Symbol}) - precompile(Tuple{typeof(Plots.nanappend!), Array{Float64, 1}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.ohlc), Array{Plots.OHLC{T} where T<:Real, 1}}) - precompile(Tuple{typeof(Plots.optimal_ticks_and_labels), Plots.Subplot{Plots.GRBackend}, Plots.Axis, Base.StepRange{Int64, Int64}}) - precompile(Tuple{typeof(Plots.optimal_ticks_and_labels), Plots.Subplot{Plots.GRBackend}, Plots.Axis, Base.UnitRange{Int64}}) - precompile(Tuple{typeof(Plots.optimal_ticks_and_labels), Plots.Subplot{Plots.GRBackend}, Plots.Axis, Nothing}) - precompile(Tuple{typeof(Plots.optimal_ticks_and_labels), Plots.Subplot{Plots.PlotlyBackend}, Plots.Axis, Base.UnitRange{Int64}}) - precompile(Tuple{typeof(Plots.optimal_ticks_and_labels), Plots.Subplot{Plots.PlotlyBackend}, Plots.Axis, Nothing}) - precompile(Tuple{typeof(Plots.parse_axis_kw), Symbol}) - precompile(Tuple{typeof(Plots.partialcircle), Float64, Float64, Int64, Int64}) - precompile(Tuple{typeof(Plots.partialcircle), Int64, Float64, Int64, Int64}) - precompile(Tuple{typeof(Plots.plotarea!), Plots.GridLayout, Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.plotarea!), Plots.Subplot{Plots.GRBackend}, Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.plotarea!), Plots.Subplot{Plots.PlotlyBackend}, Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.plotarea), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.plotarea), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.plotly_annotation_dict), Float64, Float64, Plots.PlotText}) - precompile(Tuple{typeof(Plots.plotly_apply_aspect_ratio), Plots.Subplot{Plots.PlotlyBackend}, Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.plotly_axis), Plots.Plot{Plots.PlotlyBackend}, Plots.Axis, Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.plotly_colorbar_hack), Plots.Series, Base.Dict{Symbol, Any}, Symbol}) - precompile(Tuple{typeof(Plots.plotly_colorscale), PlotUtils.CategoricalColorGradient, Int64}) - precompile(Tuple{typeof(Plots.plotly_colorscale), PlotUtils.ContinuousColorGradient, Int64}) - precompile(Tuple{typeof(Plots.plotly_colorscale), PlotUtils.ContinuousColorGradient, Nothing}) - precompile(Tuple{typeof(Plots.plotly_data), Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.plotly_data), Array{Int64, 1}}) - precompile(Tuple{typeof(Plots.plotly_data), Plots.Series, Symbol, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.plotly_data), Plots.Series, Symbol, Array{Int64, 1}}) - precompile(Tuple{typeof(Plots.plotly_data), Plots.Series, Symbol, Base.OneTo{Int64}}) - precompile(Tuple{typeof(Plots.plotly_data), Plots.Series, Symbol, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}}) - precompile(Tuple{typeof(Plots.plotly_data), Plots.Series, Symbol, Base.StepRange{Int64, Int64}}) - precompile(Tuple{typeof(Plots.plotly_data), Plots.Series, Symbol, Base.UnitRange{Int64}}) - precompile(Tuple{typeof(Plots.plotly_data), Plots.Series, Symbol, Nothing}) - precompile(Tuple{typeof(Plots.plotly_data), Plots.Series, Symbol, RecipesPipeline.Surface{Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots.plotly_domain), Plots.Subplot{Plots.PlotlyBackend}, Symbol}) - precompile(Tuple{typeof(Plots.plotly_font), Plots.Font, ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.plotly_font), Plots.Font}) - precompile(Tuple{typeof(Plots.plotly_hover!), Base.Dict{Symbol, Any}, Array{Nothing, 1}}) - precompile(Tuple{typeof(Plots.plotly_hover!), Base.Dict{Symbol, Any}, Nothing}) - precompile(Tuple{typeof(Plots.plotly_html_body), Plots.Plot{Plots.PlotlyBackend}, Nothing}) - precompile(Tuple{typeof(Plots.plotly_html_head), Plots.Plot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.plotly_layout), Plots.Plot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.plotly_legend_pos), Symbol}) - precompile(Tuple{typeof(Plots.plotly_link_indicies), Plots.Plot{Plots.PlotlyBackend}, Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.plotly_native_data), Plots.Axis, Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.plotly_native_data), Plots.Axis, Array{Float64, 2}}) - precompile(Tuple{typeof(Plots.plotly_native_data), Plots.Axis, Array{Int64, 1}}) - precompile(Tuple{typeof(Plots.plotly_native_data), Plots.Axis, Base.OneTo{Int64}}) - precompile(Tuple{typeof(Plots.plotly_native_data), Plots.Axis, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}}) - precompile(Tuple{typeof(Plots.plotly_native_data), Plots.Axis, Base.StepRange{Int64, Int64}}) - precompile(Tuple{typeof(Plots.plotly_native_data), Plots.Axis, Base.UnitRange{Int64}}) - precompile(Tuple{typeof(Plots.plotly_native_data), Plots.Axis, RecipesPipeline.Surface{Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots.plotly_polar!), Base.Dict{Symbol, Any}, Plots.Series}) - precompile(Tuple{typeof(Plots.plotly_polaraxis), Plots.Subplot{Plots.PlotlyBackend}, Plots.Axis}) - precompile(Tuple{typeof(Plots.plotly_series), Plots.Plot{Plots.PlotlyBackend}, Plots.Series}) - precompile(Tuple{typeof(Plots.plotly_series), Plots.Plot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Array{Float64, 1}, Array{Float64, 1}, Array{Float64, 1}, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Array{Float64, 1}, Array{Float64, 1}, Nothing, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Array{Int64, 1}, Array{Float64, 1}, Nothing, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Array{Int64, 1}, Array{Int64, 1}, Nothing, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Base.OneTo{Int64}, Array{Float64, 1}, Nothing, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Base.OneTo{Int64}, Base.UnitRange{Int64}, Nothing, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}, Array{Float64, 1}, Nothing, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Base.StepRange{Int64, Int64}, Array{Float64, 1}, Nothing, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Base.UnitRange{Int64}, Array{Float64, 1}, Nothing, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_segments), Plots.Series, Base.Dict{Symbol, Any}, Nothing, Nothing, Nothing, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_series_shapes), Plots.Plot{Plots.PlotlyBackend}, Plots.Series, Tuple{Float64, Float64}}) - precompile(Tuple{typeof(Plots.plotly_surface_data), Plots.Series, RecipesPipeline.Surface{Array{Float64, 2}}}) - precompile(Tuple{typeof(Plots.png), Plots.Plot{Plots.GRBackend}, String}) - precompile(Tuple{typeof(Plots.prepare_output), Plots.Plot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.prepare_output), Plots.Plot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.processFillArg), Base.Dict{Symbol, Any}, Bool}) - precompile(Tuple{typeof(Plots.processFillArg), Base.Dict{Symbol, Any}, Int64}) - precompile(Tuple{typeof(Plots.processFillArg), Base.Dict{Symbol, Any}, Symbol}) - precompile(Tuple{typeof(Plots.processFontArg!), Base.Dict{Symbol, Any}, Symbol, Int64}) - precompile(Tuple{typeof(Plots.processFontArg!), Base.Dict{Symbol, Any}, Symbol, String}) - precompile(Tuple{typeof(Plots.processFontArg!), Base.Dict{Symbol, Any}, Symbol, Symbol}) - precompile(Tuple{typeof(Plots.processGridArg!), Base.Dict{Symbol, Any}, Bool, Symbol}) - precompile(Tuple{typeof(Plots.processGridArg!), Base.Dict{Symbol, Any}, Float64, Symbol}) - precompile(Tuple{typeof(Plots.processGridArg!), Base.Dict{Symbol, Any}, Int64, Symbol}) - precompile(Tuple{typeof(Plots.processGridArg!), Base.Dict{Symbol, Any}, Symbol, Symbol}) - precompile(Tuple{typeof(Plots.processGridArg!), RecipesPipeline.DefaultsDict, Bool, Symbol}) - precompile(Tuple{typeof(Plots.processLineArg), Base.Dict{Symbol, Any}, Array{Symbol, 2}}) - precompile(Tuple{typeof(Plots.processLineArg), Base.Dict{Symbol, Any}, Float64}) - precompile(Tuple{typeof(Plots.processLineArg), Base.Dict{Symbol, Any}, Int64}) - precompile(Tuple{typeof(Plots.processLineArg), Base.Dict{Symbol, Any}, Symbol}) - precompile(Tuple{typeof(Plots.processMarkerArg), Base.Dict{Symbol, Any}, Array{Symbol, 2}}) - precompile(Tuple{typeof(Plots.processMarkerArg), Base.Dict{Symbol, Any}, Bool}) - precompile(Tuple{typeof(Plots.processMarkerArg), Base.Dict{Symbol, Any}, ColorTypes.RGBA{Float64}}) - precompile(Tuple{typeof(Plots.processMarkerArg), Base.Dict{Symbol, Any}, Float64}) - precompile(Tuple{typeof(Plots.processMarkerArg), Base.Dict{Symbol, Any}, Int64}) - precompile(Tuple{typeof(Plots.processMarkerArg), Base.Dict{Symbol, Any}, Plots.Shape}) - precompile(Tuple{typeof(Plots.processMarkerArg), Base.Dict{Symbol, Any}, Plots.Stroke}) - precompile(Tuple{typeof(Plots.processMarkerArg), Base.Dict{Symbol, Any}, Symbol}) - precompile(Tuple{typeof(Plots.processMinorGridArg!), Base.Dict{Symbol, Any}, Bool, Symbol}) - precompile(Tuple{typeof(Plots.process_annotation), Plots.Subplot{Plots.GRBackend}, Int64, Float64, Plots.PlotText, Plots.Font}) - precompile(Tuple{typeof(Plots.process_annotation), Plots.Subplot{Plots.GRBackend}, Int64, Float64, Plots.PlotText}) - precompile(Tuple{typeof(Plots.process_annotation), Plots.Subplot{Plots.PlotlyBackend}, Int64, Float64, Plots.PlotText, Plots.Font}) - precompile(Tuple{typeof(Plots.process_annotation), Plots.Subplot{Plots.PlotlyBackend}, Int64, Float64, Plots.PlotText}) - precompile(Tuple{typeof(Plots.process_axis_arg!), Base.Dict{Symbol, Any}, Base.StepRange{Int64, Int64}, Symbol}) - precompile(Tuple{typeof(Plots.process_axis_arg!), Base.Dict{Symbol, Any}, String, Symbol}) - precompile(Tuple{typeof(Plots.process_axis_arg!), Base.Dict{Symbol, Any}, Symbol, Symbol}) - precompile(Tuple{typeof(Plots.process_axis_arg!), Base.Dict{Symbol, Any}, Tuple{Int64, Int64}, Symbol}) - precompile(Tuple{typeof(Plots.recompute_lengths), Array{Measures.Measure, 1}}) - precompile(Tuple{typeof(Plots.replaceAlias!), Base.Dict{Symbol, Any}, Symbol, Base.Dict{Symbol, Symbol}}) - precompile(Tuple{typeof(Plots.replaceAlias!), RecipesPipeline.DefaultsDict, Symbol, Base.Dict{Symbol, Symbol}}) - precompile(Tuple{typeof(Plots.replaceAliases!), Base.Dict{Symbol, Any}, Base.Dict{Symbol, Symbol}}) - precompile(Tuple{typeof(Plots.replaceAliases!), RecipesPipeline.DefaultsDict, Base.Dict{Symbol, Symbol}}) - precompile(Tuple{typeof(Plots.reset_axis_defaults_byletter!)}) - precompile(Tuple{typeof(Plots.right), Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.rightpad), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.rightpad), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.rowsize), Expr}) - precompile(Tuple{typeof(Plots.rowsize), Symbol}) - precompile(Tuple{typeof(Plots.series_annotations), Array{Any, 1}}) - precompile(Tuple{typeof(Plots.series_annotations), Plots.SeriesAnnotations}) - precompile(Tuple{typeof(Plots.series_annotations_shapes!), Plots.Series, Symbol}) - precompile(Tuple{typeof(Plots.series_idx), Array{Base.Dict{Symbol, Any}, 1}, Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots.shape_data), Plots.Series, Int64}) - precompile(Tuple{typeof(Plots.should_add_to_legend), Plots.Series}) - precompile(Tuple{typeof(Plots.showaxis), Symbol, Symbol}) - precompile(Tuple{typeof(Plots.shrink_by), Float64, Float64, Float64}) - precompile(Tuple{typeof(Plots.slice_arg!), Base.Dict{Symbol, Any}, Base.Dict{Symbol, Any}, Symbol, Int64, Bool}) - precompile(Tuple{typeof(Plots.slice_arg!), Base.Dict{Symbol, Any}, RecipesPipeline.DefaultsDict, Symbol, Int64, Bool}) - precompile(Tuple{typeof(Plots.slice_arg!), RecipesPipeline.DefaultsDict, RecipesPipeline.DefaultsDict, Symbol, Int64, Bool}) - precompile(Tuple{typeof(Plots.slice_arg), Array{ColorTypes.RGBA{Float64}, 2}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Array{Float64, 2}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Array{Measures.Length{:mm, Float64}, 2}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Array{PlotUtils.ContinuousColorGradient, 2}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Array{String, 2}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Array{Symbol, 2}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Base.ReshapedArray{Int64, 2, Base.UnitRange{Int64}, Tuple{}}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Base.StepRange{Int64, Int64}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Base.UnitRange{Int64}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Bool, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), ColorTypes.RGBA{Float64}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Float64, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Int64, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Nothing, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), String, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Symbol, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Tuple{Float64, Float64}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Tuple{Int64, Float64}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), Tuple{Int64, Int64}, Int64}) - precompile(Tuple{typeof(Plots.slice_arg), typeof(identity), Int64}) - precompile(Tuple{typeof(Plots.straightline_data), Plots.Series, Int64}) - precompile(Tuple{typeof(Plots.straightline_data), Tuple{Int64, Int64}, Tuple{Float64, Float64}, Array{Float64, 1}, Array{Float64, 1}, Int64}) - precompile(Tuple{typeof(Plots.stroke), Int64, Int}) - precompile(Tuple{typeof(Plots.supported_markers), Plots.GRBackend}) - precompile(Tuple{typeof(Plots.supported_markers), Plots.PlotlyBackend}) - precompile(Tuple{typeof(Plots.supported_markers)}) - precompile(Tuple{typeof(Plots.supported_styles), Plots.GRBackend}) - precompile(Tuple{typeof(Plots.supported_styles), Plots.PlotlyBackend}) - precompile(Tuple{typeof(Plots.supported_styles)}) - precompile(Tuple{typeof(Plots.test_examples), Symbol}) - precompile(Tuple{typeof(Plots.text), String, Int64, Symbol, Symbol}) - precompile(Tuple{typeof(Plots.text), String, Plots.Font}) - precompile(Tuple{typeof(Plots.text), String, Symbol, Int64, Int}) - precompile(Tuple{typeof(Plots.text), String, Symbol}) - precompile(Tuple{typeof(Plots.text_size), Int64, Int64, Int64}) - precompile(Tuple{typeof(Plots.tick_padding), Plots.Subplot{Plots.PlotlyBackend}, Plots.Axis}) - precompile(Tuple{typeof(Plots.tickfont), Plots.Axis}) - precompile(Tuple{typeof(Plots.ticksType), Tuple{Array{Float64, 1}, Array{Any, 1}}}) - precompile(Tuple{typeof(Plots.ticksType), Tuple{Array{Float64, 1}, Array{String, 1}}}) - precompile(Tuple{typeof(Plots.ticksType), Tuple{Array{Int64, 1}, Array{String, 1}}}) - precompile(Tuple{typeof(Plots.title!), String}) - precompile(Tuple{typeof(Plots.title_padding), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.titlefont), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.titlefont), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.top), Measures.BoundingBox{Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}, Tuple{Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}}}) - precompile(Tuple{typeof(Plots.toppad), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.toppad), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.transpose_z), Plots.Series, Array{Float64, 2}, Bool}) - precompile(Tuple{typeof(Plots.update_child_bboxes!), Plots.GridLayout, Array{Measures.Length{:mm, Float64}, 1}}) - precompile(Tuple{typeof(Plots.update_child_bboxes!), Plots.GridLayout}) - precompile(Tuple{typeof(Plots.update_child_bboxes!), Plots.Subplot{Plots.GRBackend}, Array{Measures.Length{:mm, Float64}, 1}}) - precompile(Tuple{typeof(Plots.update_child_bboxes!), Plots.Subplot{Plots.PlotlyBackend}, Array{Measures.Length{:mm, Float64}, 1}}) - precompile(Tuple{typeof(Plots.update_inset_bboxes!), Plots.Plot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.update_inset_bboxes!), Plots.Plot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.vline!), Array{Int64, 1}}) - precompile(Tuple{typeof(Plots.wand_edges), Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.warn_on_unsupported), Plots.GRBackend, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.warn_on_unsupported), Plots.PlotlyBackend, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.warn_on_unsupported_args), Plots.GRBackend, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.warn_on_unsupported_args), Plots.PlotlyBackend, RecipesPipeline.DefaultsDict}) - precompile(Tuple{typeof(Plots.warn_on_unsupported_scales), Plots.GRBackend, Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots.warn_on_unsupported_scales), Plots.PlotlyBackend, Base.Dict{Symbol, Any}}) - precompile(Tuple{typeof(Plots.widen), Float64, Float64, Symbol}) - precompile(Tuple{typeof(Plots.wraptuple), Array{Any, 1}}) - precompile(Tuple{typeof(Plots.wraptuple), Array{Float64, 1}}) - precompile(Tuple{typeof(Plots.wraptuple), Base.StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}}) - precompile(Tuple{typeof(Plots.wraptuple), Bool}) - precompile(Tuple{typeof(Plots.wraptuple), Float64}) - precompile(Tuple{typeof(Plots.wraptuple), Int64}) - precompile(Tuple{typeof(Plots.wraptuple), Nothing}) - precompile(Tuple{typeof(Plots.wraptuple), Plots.SeriesAnnotations}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Array{Symbol, 2}, Int64, Float64, Plots.Stroke}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Array{Symbol, 2}, Int64}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Base.LinRange{Float64}, Base.LinRange{Float64}}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Int64, Array{Symbol, 2}}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Int64, Float64, Symbol, Plots.Stroke}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Int64, Float64, Symbol}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Int64, String}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Int64, Symbol, Float64, Array{Symbol, 2}}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Int64, Symbol, Symbol}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Int64, Symbol}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Plots.Shape, Int64, ColorTypes.RGBA{Float64}}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{String, Symbol}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{String, Tuple{Int64, Int64}, Base.StepRange{Int64, Int64}, Symbol}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Symbol, Float64, Plots.Stroke}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Symbol, Int64}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Symbol, Symbol, Int64, Symbol, Float64}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{Symbol, Symbol, Symbol, Int64, Float64}}) - precompile(Tuple{typeof(Plots.wraptuple), Tuple{}}) - precompile(Tuple{typeof(Plots.write_temp_html), Plots.Plot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.xgrid!), Plots.Plot{Plots.GRBackend}, Symbol, Int}) - precompile(Tuple{typeof(Plots.xgrid!), Plots.Plot{Plots.PlotlyBackend}, Symbol, Int}) - precompile(Tuple{typeof(Plots.xlims), Int64}) - precompile(Tuple{typeof(Plots.xlims), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.xlims), Plots.Subplot{Plots.PlotlyBackend}}) - precompile(Tuple{typeof(Plots.xy_mm_to_pcts), Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}, Measures.Length{:mm, Float64}}) - precompile(Tuple{typeof(Plots.yaxis!), String, Symbol}) - precompile(Tuple{typeof(Plots.ylims), Int64}) - precompile(Tuple{typeof(Plots.ylims), Plots.Subplot{Plots.GRBackend}}) - precompile(Tuple{typeof(Plots.ylims), Plots.Subplot{Plots.PlotlyBackend}}) -end diff --git a/src/precompile_includer.jl b/src/precompile_includer.jl new file mode 100644 index 00000000..ed5fe1c5 --- /dev/null +++ b/src/precompile_includer.jl @@ -0,0 +1,20 @@ +should_precompile = true + + +# Don't edit the following! Instead change the script for `snoop_bot`. +ismultios = false +ismultiversion = false +# precompile_enclosure +@static if !should_precompile + # nothing +elseif !ismultios && !ismultiversion + @static if isfile(joinpath( + @__DIR__, + "../deps/SnoopCompile/precompile/precompile_Plots.jl", + )) + include("../deps/SnoopCompile/precompile/precompile_Plots.jl") + _precompile_() + end +else + +end # precompile_enclosure diff --git a/src/recipes.jl b/src/recipes.jl index 804cd63d..c2911db9 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -78,6 +78,8 @@ const POTENTIAL_VECTOR_ARGUMENTS = [ :fillrange, ] +@nospecialize + @recipe function f(::Type{Val{:line}}, x, y, z) indices = sortperm(x) x := x[indices] @@ -112,7 +114,7 @@ end @recipe function f(::Type{Val{:hline}}, x, y, z) n = length(y) - newx = repeat(Float64[-1, 1, NaN], n) + newx = repeat(Float64[1, 2, NaN], n) newy = vec(Float64[yi for i = 1:3, yi in y]) x := newx y := newy @@ -124,9 +126,8 @@ end @recipe function f(::Type{Val{:vline}}, x, y, z) n = length(y) newx = vec(Float64[yi for i = 1:3, yi in y]) - newy = repeat(Float64[-1, 1, NaN], n) x := newx - y := newy + y := repeat(Float64[1, 2, NaN], n) seriestype := :straightline () end @@ -164,45 +165,81 @@ end x := x y := y seriestype := :scatter + @series begin + () + end @series begin seriestype := :path label := "" primary := false () end + primary := false () end @deps scatterpath path scatter +# --------------------------------------------------------------------------- +# regression line and scatter + +# plots line corresponding to linear regression of y on a constant and x +@recipe function f(::Type{Val{:linearfit}}, x, y, z) + x := x + y := y + seriestype := :scatter + @series begin + () + end + @series begin + y := mean(y) .+ cov(x, y) / var(x) .* (x .- mean(x)) + seriestype := :path + label := "" + primary := false + () + end + primary := false + () +end + + +@specialize + + # --------------------------------------------------------------------------- # steps -make_steps(x, st) = x -function make_steps(x::AbstractArray, st) +make_steps(x, st, even) = x +function make_steps(x::AbstractArray, st, even) n = length(x) n == 0 && return zeros(0) - newx = zeros(2n - 1) - for i = 1:n + newx = zeros(2n - (even ? 0 : 1)) + newx[1] = x[1] + for i = 2:n idx = 2i - 1 - newx[idx] = x[i] - if i > 1 + if st == :mid + newx[idx] = newx[idx-1] = (x[i] + x[i-1]) / 2 + else + newx[idx] = x[i] newx[idx - 1] = x[st == :pre ? i : i - 1] end end + even && (newx[end] = x[end]) return newx end -make_steps(t::Tuple, st) = Tuple(make_steps(ti, st) for ti in t) +make_steps(t::Tuple, st, even) = Tuple(make_steps(ti, st, even) for ti in t) +@nospecialize + # create a path from steps @recipe function f(::Type{Val{:steppre}}, x, y, z) - plotattributes[:x] = make_steps(x, :post) - plotattributes[:y] = make_steps(y, :pre) + plotattributes[:x] = make_steps(x, :post, false) + plotattributes[:y] = make_steps(y, :pre, false) seriestype := :path # handle fillrange - plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :pre) + plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :pre, false) # create a secondary series for the markers if plotattributes[:markershape] != :none @@ -221,13 +258,38 @@ end @deps steppre path scatter # create a path from steps -@recipe function f(::Type{Val{:steppost}}, x, y, z) - plotattributes[:x] = make_steps(x, :pre) - plotattributes[:y] = make_steps(y, :post) +@recipe function f(::Type{Val{:stepmid}}, x, y, z) + plotattributes[:x] = make_steps(x, :mid, true) + plotattributes[:y] = make_steps(y, :post, true) seriestype := :path # handle fillrange - plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :post) + plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :post, true) + + # create a secondary series for the markers + if plotattributes[:markershape] != :none + @series begin + seriestype := :scatter + x := x + y := y + label := "" + primary := false + () + end + markershape := :none + end + () +end +@deps stepmid path scatter + +# create a path from steps +@recipe function f(::Type{Val{:steppost}}, x, y, z) + plotattributes[:x] = make_steps(x, :pre, false) + plotattributes[:y] = make_steps(y, :post, false) + seriestype := :path + + # handle fillrange + plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :post, false) # create a secondary series for the markers if plotattributes[:markershape] != :none @@ -263,24 +325,39 @@ end end end newx, newy = zeros(3n), zeros(3n) - for i = 1:n + newz = z !== nothing ? zeros(3n) : nothing + for (i, (xi, yi, zi)) = enumerate(zip(x, y, z !== nothing ? z : 1:n)) rng = (3i - 2):(3i) - newx[rng] = [x[i], x[i], NaN] - newy[rng] = [_cycle(fr, i), y[i], NaN] + newx[rng] = [xi, xi, NaN] + if z !== nothing + newy[rng] = [yi, yi, NaN] + newz[rng] = [_cycle(fr, i), zi, NaN] + else + newy[rng] = [_cycle(fr, i), yi, NaN] + end end x := newx y := newy + if z !== nothing + z := newz + end fillrange := nothing seriestype := :path + if plotattributes[:linecolor] == :auto && plotattributes[:marker_z] !== nothing && plotattributes[:line_z] === nothing + line_z := plotattributes[:marker_z] + end - # create a secondary series for the markers + # create a primary series for the markers if plotattributes[:markershape] != :none + primary := false @series begin seriestype := :scatter x := x y := y - label := "" - primary := false + if z !== nothing + z := z + end + primary := true () end markershape := :none @@ -289,6 +366,8 @@ end end @deps sticks path scatter +@specialize + # --------------------------------------------------------------------------- # bezier curves @@ -303,6 +382,8 @@ function bezier_value(pts::AVec, t::Real) val end +@nospecialize + # create segmented bezier curves in place of line segments @recipe function f(::Type{Val{:curves}}, x, y, z; npoints = 30) args = z !== nothing ? (x, y, z) : (x, y) @@ -374,7 +455,7 @@ end bw = plotattributes[:bar_width] hw = if bw === nothing if nx > 1 - 0.5 * _bar_width * ignorenan_minimum(filter(x -> x > 0, diff(procx))) + 0.5 * _bar_width * ignorenan_minimum(filter(x -> x > 0, diff(sort(procx)))) else 0.5 * _bar_width end @@ -391,6 +472,15 @@ end fillto = map(x -> _is_positive(x) ? typeof(baseline)(x) : baseline, fillto) end + if !isnothing(plotattributes[:series_annotations]) + if isvertical(plotattributes) + annotations := (x,y,plotattributes[:series_annotations].strs,:bottom) + else + annotations := (y,x,plotattributes[:series_annotations].strs,:left) + end + series_annotations := nothing + end + # create the bar shapes by adding x/y segments xseg, yseg = Segments(), Segments() for i = 1:ny @@ -458,9 +548,12 @@ end () end @deps plots_heatmap shape + +@specialize + is_3d(::Type{Val{:plots_heatmap}}) = true RecipesPipeline.is_surface(::Type{Val{:plots_heatmap}}) = true - +RecipesPipeline.is_surface(::Type{Val{:hexbin}}) = true # --------------------------------------------------------------------------- # Histograms @@ -521,6 +614,8 @@ function _preprocess_binlike(plotattributes, x, y) end +@nospecialize + @recipe function f(::Type{Val{:barbins}}, x, y, z) edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, x, y) @@ -552,6 +647,8 @@ end end @deps scatterbins xerror scatter +@specialize + function _stepbins_path( edge, weights, @@ -618,8 +715,8 @@ function _stepbins_path( (x, y) end - @recipe function f(::Type{Val{:stepbins}}, x, y, z) + @nospecialize axis = plotattributes[:subplot][Plots.isvertical(plotattributes) ? :xaxis : :yaxis] @@ -756,6 +853,7 @@ function _make_hist( normalize!(h, mode = _hist_norm_mode(normed)) end +@nospecialize @recipe function f(::Type{Val{:histogram}}, x, y, z) seriestype := length(y) > 1e6 ? :stephist : :barhist @@ -765,7 +863,7 @@ end @recipe function f(::Type{Val{:barhist}}, x, y, z) h = _make_hist( - (y,), + tuple(y), plotattributes[:bins], normed = plotattributes[:normalize], weights = plotattributes[:weights], @@ -779,7 +877,7 @@ end @recipe function f(::Type{Val{:stephist}}, x, y, z) h = _make_hist( - (y,), + tuple(y), plotattributes[:bins], normed = plotattributes[:normalize], weights = plotattributes[:weights], @@ -793,7 +891,7 @@ end @recipe function f(::Type{Val{:scatterhist}}, x, y, z) h = _make_hist( - (y,), + tuple(y), plotattributes[:bins], normed = plotattributes[:normalize], weights = plotattributes[:weights], @@ -860,9 +958,7 @@ end x := Plots._bin_centers(edge_x) y := Plots._bin_centers(edge_y) - z := Surface(float_weights) - - match_dimensions := true + z := Surface(permutedims(float_weights)) seriestype := :heatmap () end @@ -913,6 +1009,19 @@ end @deps pie shape +# --------------------------------------------------------------------------- +# mesh 3d replacement for non-plotly backends + +@recipe function f(::Type{Val{:mesh3d}}, x, y, z) + # As long as no i,j,k are supplied this should work with PyPlot and GR + seriestype := :surface + if plotattributes[:connections] !== nothing + throw(ArgumentError("Giving triangles using the connections argument is only supported on Plotly backend.")) + end + () +end + + # --------------------------------------------------------------------------- # scatter 3d @@ -928,7 +1037,6 @@ end # note: don't add dependencies because this really isn't a drop-in replacement - # --------------------------------------------------------------------------- # lens! - magnify a region of a plot lens!(args...;kwargs...) = plot!(args...; seriestype=:lens, kwargs...) @@ -950,8 +1058,12 @@ export lens! lens_index = last(plt.subplots)[:subplot_index] + 1 x1, x2 = plotattributes[:x] y1, y2 = plotattributes[:y] + backup = copy(plotattributes) + empty!(plotattributes) + + series_plotindex := backup[:series_plotindex] seriestype := :path - label := "" + primary := false linecolor := :lightgray bbx_mag = (x1 + x2) / 2 bby_mag = (y1 + y2) / 2 @@ -961,6 +1073,7 @@ export lens! if xl1 < xi_lens < xl2 && yl1 < yi_lens < yl2 @series begin + primary := false subplot := sp_index x := [xi_mag, xi_lens] y := [yi_mag, yi_lens] @@ -969,6 +1082,7 @@ export lens! end # add magnification shape @series begin + primary := false subplot := sp_index x := [x1, x1, x2, x2, x1] y := [y1, y2, y2, y1, y1] @@ -977,20 +1091,19 @@ export lens! # add subplot for series in sp.series_list @series begin - plotattributes = merge(plotattributes, copy(series.plotattributes)) + plotattributes = merge(backup, copy(series.plotattributes)) subplot := lens_index - label := "" + primary := false xlims := (x1, x2) ylims := (y1, y2) () end end - backup = copy(plotattributes) - empty!(plotattributes) - seriestype := :path - series_plotindex := backup[:series_plotindex] + nothing end +@specialize + function intersection_point(xA, yA, xB, yB, h, w) s = (yA - yB) / (xA - xB) hh = h / 2 @@ -1018,6 +1131,7 @@ end # contourf - filled contours @recipe function f(::Type{Val{:contourf}}, x, y, z) + @nospecialize fillrange := true seriestype := :contour () @@ -1027,8 +1141,24 @@ end # Error Bars function error_style!(plotattributes::AKW) + msc = plotattributes[:markerstrokecolor] + msc = if msc === :match + plotattributes[:subplot][:foreground_color_subplot] + elseif msc === :auto + get_series_color( + plotattributes[:linecolor], + plotattributes[:subplot], + plotattributes[:series_plotindex], + plotattributes[:seriestype], + ) + else + msc + end + plotattributes[:seriestype] = :path - plotattributes[:markercolor] = plotattributes[:markerstrokecolor] + plotattributes[:markerstrokecolor] = msc + plotattributes[:markercolor] = msc + plotattributes[:linecolor] = msc plotattributes[:linewidth] = plotattributes[:markerstrokewidth] plotattributes[:label] = "" end @@ -1059,9 +1189,17 @@ function error_coords(errorbar, errordata, otherdata...) return (ed, od...) end +# clamp non-NaN values in an array to Base.eps(Float64) for log-scale plots +function clamp_to_eps!(ary) + replace!(x -> x <= 0.0 ? Base.eps(Float64) : x, ary) + nothing +end + # we will create a series of path segments, where each point represents one # side of an errorbar +@nospecialize + @recipe function f(::Type{Val{:xerror}}, x, y, z) error_style!(plotattributes) markershape := :vline @@ -1072,6 +1210,9 @@ end plotattributes[:x], plotattributes[:y], plotattributes[:z] = error_coords(xerr, x, y, z) end + if :xscale ∈ keys(plotattributes) && plotattributes[:xscale] == :log10 + clamp_to_eps!(plotattributes[:x]) + end () end @deps xerror path @@ -1086,6 +1227,9 @@ end plotattributes[:y], plotattributes[:x], plotattributes[:z] = error_coords(yerr, y, x, z) end + if :yscale ∈ keys(plotattributes) && plotattributes[:yscale] == :log10 + clamp_to_eps!(plotattributes[:y]) + end () end @deps yerror path @@ -1098,10 +1242,14 @@ end plotattributes[:z], plotattributes[:x], plotattributes[:y] = error_coords(zerr, z, x, y) end + if :zscale ∈ keys(plotattributes) && plotattributes[:zscale] == :log10 + clamp_to_eps!(plotattributes[:z]) + end () end @deps zerror path +@specialize # TODO: move quiver to PlotRecipes @@ -1115,35 +1263,51 @@ function quiver_using_arrows(plotattributes::AKW) if !isa(plotattributes[:arrow], Arrow) plotattributes[:arrow] = arrow() end - + is_3d = haskey(plotattributes,:z) && !isnothing(plotattributes[:z]) velocity = error_zipit(plotattributes[:quiver]) xorig, yorig = plotattributes[:x], plotattributes[:y] + zorig = is_3d ? plotattributes[:z] : [] # for each point, we create an arrow of velocity vi, translated to the x/y coordinates x, y = zeros(0), zeros(0) - for i = 1:max(length(xorig), length(yorig)) + is_3d && ( z = zeros(0)) + for i = 1:max(length(xorig), length(yorig), is_3d ? 0 : length(zorig)) # get the starting position xi = _cycle(xorig, i) yi = _cycle(yorig, i) - + zi = is_3d ? _cycle(zorig, i) : 0 # get the velocity vi = _cycle(velocity, i) - vx, vy = if istuple(vi) - first(vi), last(vi) - elseif isscalar(vi) - vi, vi - elseif isa(vi, Function) - vi(xi, yi) - else - error("unexpected vi type $(typeof(vi)) for quiver: $vi") + if is_3d + vx, vy, vz = if istuple(vi) + vi[1], vi[2], vi[3] + elseif isscalar(vi) + vi, vi, vi + elseif isa(vi, Function) + vi(xi, yi, zi) + else + error("unexpected vi type $(typeof(vi)) for quiver: $vi") + end + else # 2D quiver + vx, vy = if istuple(vi) + first(vi), last(vi) + elseif isscalar(vi) + vi, vi + elseif isa(vi, Function) + vi(xi, yi) + else + error("unexpected vi type $(typeof(vi)) for quiver: $vi") + end end - # add the points nanappend!(x, [xi, xi + vx, NaN]) nanappend!(y, [yi, yi + vy, NaN]) + is_3d && nanappend!(z, [zi, zi + vz, NaN]) end - plotattributes[:x], plotattributes[:y] = x, y + if is_3d + plotattributes[:z] = z + end # KW[plotattributes] end @@ -1198,6 +1362,7 @@ end # function apply_series_recipe(plotattributes::AKW, ::Type{Val{:quiver}}) @recipe function f(::Type{Val{:quiver}}, x, y, z) + @nospecialize if :arrow in supported_attrs() quiver_using_arrows(plotattributes) else @@ -1230,12 +1395,14 @@ end else seriestype := :heatmap yflip --> true - cbar --> false - fillcolor --> ColorGradient([:black, :white]) + colorbar --> false + fillcolor --> cgrad([:black, :white]) SliceIt, m, n, Surface(clamp!(convert(Matrix{Float64}, mat), 0.0, 1.0)) end end +@nospecialize + # images - colors @recipe function f(mat::AMat{T}) where {T <: Colorant} n, m = axes(mat) @@ -1247,7 +1414,7 @@ end else seriestype := :heatmap yflip --> true - cbar --> false + colorbar --> false aspect_ratio --> :equal z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat) SliceIt, m, n, Surface(z) @@ -1261,12 +1428,22 @@ end coords(shape) end -@recipe function f(shapes::AVec{Shape}) +@recipe function f(shapes::AVec{<:Shape}) seriestype --> :shape + # For backwards compatibility, column vectors of segmenting attributes are + # interpreted as having one element per shape + for attr in union(_segmenting_array_attributes, _segmenting_vector_attributes) + v = get(plotattributes, attr, nothing) + if v isa AVec || v isa AMat && size(v,2) == 1 + @warn "Column vector attribute `$attr` reinterpreted as row vector (one value per shape).\n" * + "Pass a row vector instead (e.g. using `permutedims`) to suppress this warning." + plotattributes[attr] = permutedims(v) + end + end coords(shapes) end -@recipe function f(shapes::AMat{Shape}) +@recipe function f(shapes::AMat{<:Shape}) seriestype --> :shape for j in axes(shapes, 2) @series coords(vec(shapes[:, j])) @@ -1287,8 +1464,8 @@ end else seriestype := :heatmap yflip --> true - cbar --> false - fillcolor --> ColorGradient([:black, :white]) + colorbar --> false + fillcolor --> cgrad([:black, :white]) SliceIt, x, y, Surface(convert(Matrix{Float64}, mat)) end end @@ -1302,23 +1479,24 @@ end else seriestype := :heatmap yflip --> true - cbar --> false + colorbar --> false z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat) SliceIt, x, y, Surface(z) end end # -------------------------------------------------------------------- -# Lists of tuples and GeometryTypes.Points +# Lists of tuples and GeometryBasics.Points # -------------------------------------------------------------------- -@recipe f(v::AVec{<:GeometryTypes.Point}) = RecipesPipeline.unzip(v) -@recipe f(p::GeometryTypes.Point) = [p] +@recipe f(v::AVec{<:GeometryBasics.Point}) = RecipesPipeline.unzip(v) +@recipe f(p::GeometryBasics.Point) = [p] # Special case for 4-tuples in :ohlc series @recipe f(xyuv::AVec{<:Tuple{R1, R2, R3, R4}}) where {R1, R2, R3, R4} = get(plotattributes, :seriestype, :path) == :ohlc ? OHLC[OHLC(t...) for t in xyuv] : RecipesPipeline.unzip(xyuv) +@specialize # ------------------------------------------------- @@ -1358,6 +1536,8 @@ end # TODO: when I allow `@recipe f(::Type{T}, v::T) = ...` definitions to replace convertToAnyVector, # then I should replace these with one definition to convert to a vector of 4-tuples +@nospecialize + # to squash ambiguity warnings... @recipe f(x::AVec{Function}, v::AVec{OHLC}) = error() @recipe f( @@ -1415,17 +1595,17 @@ end @recipe function f(::Type{Val{:spy}}, x, y, z) yflip := true aspect_ratio := 1 - rs, cs, zs = findnz(z.surf) - xlims := ignorenan_extrema(cs) - ylims := ignorenan_extrema(rs) - if plotattributes[:markershape] == :none - markershape := :circle - end - if plotattributes[:markersize] == default(:markersize) - markersize := 1 - end + rs, cs, zs = Plots.findnz(z.surf) + xlims := widen(ignorenan_extrema(cs)..., get(plotattributes, :xscale, :identity)) + ylims := widen(ignorenan_extrema(rs)..., get(plotattributes, :yscale, :identity)) + markershape --> :circle + markersize --> 1 markerstrokewidth := 0 - marker_z := zs + if length(unique(zs)) == 1 + seriescolor --> :black + else + marker_z := zs + end label := "" x := cs y := rs @@ -1435,8 +1615,24 @@ end () end +@specialize + + +Plots.findnz(A::AbstractSparseMatrix) = SparseArrays.findnz(A) + +# fallback function for finding non-zero elements of non-sparse matrices +function Plots.findnz(A::AbstractMatrix) + keysnz = findall(!iszero, A) + rs = [k[1] for k in keysnz] + cs = [k[2] for k in keysnz] + zs = A[keysnz] + rs, cs, zs +end + # ------------------------------------------------- +@nospecialize + "Adds ax+b... straight line over the current plot, without changing the axis limits" abline!(plt::Plot, a, b; kw...) = plot!(plt, [0, 1], [b, b + a]; seriestype = :straightline, kw...) @@ -1462,50 +1658,6 @@ end end -# -------------------------------------------------- -# Color Gradients - -@userplot ShowLibrary -@recipe function f(cl::ShowLibrary) - if !(length(cl.args) == 1 && isa(cl.args[1], Symbol)) - error("showlibrary takes the name of a color library as a Symbol") - end - - library = PlotUtils.color_libraries[cl.args[1]] - z = sqrt.((1:15) * reshape(1:20, 1, :)) - - seriestype := :heatmap - ticks := nothing - legend := false - - layout --> length(library.lib) - - i = 0 - for grad in sort(collect(keys(library.lib))) - @series begin - seriescolor := cgrad(grad, cl.args[1]) - title := string(grad) - subplot := i += 1 - z - end - end -end - -@userplot ShowGradient -@recipe function f(grad::ShowGradient) - if !(length(grad.args) == 1 && isa(grad.args[1], Symbol)) - error("showgradient takes the name of a color gradient as a Symbol") - end - z = sqrt.((1:15) * reshape(1:20, 1, :)) - seriestype := :heatmap - ticks := nothing - legend := false - seriescolor := grad.args[1] - title := string(grad.args[1]) - z -end - - # Moved in from PlotRecipes - see: http://stackoverflow.com/a/37732384/5075246 @userplot PortfolioComposition @@ -1550,3 +1702,5 @@ julia> areaplot(1:3, [1 2 3; 7 8 9; 4 5 6], seriescolor = [:red :green :blue], f end end end + +@specialize diff --git a/src/shorthands.jl b/src/shorthands.jl index 63bec003..dfcc2302 100644 --- a/src/shorthands.jl +++ b/src/shorthands.jl @@ -1,3 +1,5 @@ +@nospecialize + """ scatter(x,y) scatter!(x,y) @@ -55,6 +57,7 @@ Plot a histogram. # Example ```julia-repl julia> histogram([1,2,1,1,4,3,8],bins=0:8) +julia> histogram([1,2,1,1,4,3,8],bins=0:8,weights=weights([4,7,3,9,12,2,6])) ``` """ @shorthands histogram @@ -69,7 +72,7 @@ Make a histogram bar plot. See `histogram`. """ stephist(x) - stephist(x) + stephist!(x) Make a histogram step plot (bin counts are represented using horizontal lines instead of bars). See `histogram`. @@ -318,6 +321,27 @@ julia> scatter3d([0,1,2,3],[0,1,4,9],[0,1,8,27]) """ @shorthands scatter3d +""" + mesh3d(x,y,z) + mesh3d(x,y,z; connections) + +Plot a 3d mesh. On Plotly the triangles can be specified using the connections argument. + +# Example +```Julia +x=[0, 1, 2, 0] +y=[0, 0, 1, 2] +z=[0, 2, 0, 1] + +i=[0, 0, 0, 1] +j=[1, 2, 3, 2] +k=[2, 3, 1, 3] + +plot(x,y,z,seriestype=:mesh3d;connections=(i,j,k)) +``` +""" +@shorthands mesh3d + """ boxplot(x, y) boxplot!(x, y) @@ -397,13 +421,13 @@ xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...) ylabel!(s::AbstractString; kw...) = plot!(; ylabel = s, kw...) "Set xlims for an existing plot" -xlims!(lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(; xlims = lims, kw...) +xlims!(lims::Tuple; kw...) = plot!(; xlims = lims, kw...) "Set ylims for an existing plot" -ylims!(lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(; ylims = lims, kw...) +ylims!(lims::Tuple; kw...) = plot!(; ylims = lims, kw...) "Set zlims for an existing plot" -zlims!(lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(; zlims = lims, kw...) +zlims!(lims::Tuple; kw...) = plot!(; zlims = lims, kw...) xlims!(xmin::Real, xmax::Real; kw...) = plot!(; xlims = (xmin,xmax), kw...) ylims!(ymin::Real, ymax::Real; kw...) = plot!(; ylims = (ymin,ymax), kw...) @@ -411,10 +435,10 @@ zlims!(zmin::Real, zmax::Real; kw...) = plot!(; zlims = (zmi "Set xticks for an existing plot" -xticks!(v::TicksArgs; kw...) where {T<:Real} = plot!(; xticks = v, kw...) +xticks!(v::TicksArgs; kw...) = plot!(; xticks = v, kw...) "Set yticks for an existing plot" -yticks!(v::TicksArgs; kw...) where {T<:Real} = plot!(; yticks = v, kw...) +yticks!(v::TicksArgs; kw...) = plot!(; yticks = v, kw...) xticks!( ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(; xticks = (ticks,labels), kw...) @@ -454,3 +478,5 @@ xaxis!(args...; kw...) = plot!(; xaxis = args yaxis!(args...; kw...) = plot!(; yaxis = args, kw...) xgrid!(args...; kw...) = plot!(; xgrid = args, kw...) ygrid!(args...; kw...) = plot!(; ygrid = args, kw...) + +@specialize diff --git a/src/utils.jl b/src/utils.jl index a33c82ea..4356e6ed 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,4 +1,6 @@ - +function treats_y_as_x(seriestype) + return seriestype in (:vline, :vspan, :histogram, :barhist, :stephist, :scatterhist) +end function replace_image_with_heatmap(z::Array{T}) where T<:Colorant n, m = size(z) colors = palette(vec(z)) @@ -51,10 +53,16 @@ function Base.push!(segments::Segments{T}, vs::AVec) where T end +struct SeriesSegment + # indexes of this segement in series data vectors + range::UnitRange + # index into vector-valued attributes corresponding to this segment + attr_index::Int +end + # ----------------------------------------------------- # helper to manage NaN-separated segments - -mutable struct SegmentsIterator +struct NaNSegmentsIterator args::Tuple n1::Int n2::Int @@ -64,30 +72,50 @@ function iter_segments(args...) tup = Plots.wraptuple(args) n1 = minimum(map(firstindex, tup)) n2 = maximum(map(lastindex, tup)) - SegmentsIterator(tup, n1, n2) + NaNSegmentsIterator(tup, n1, n2) end -function iter_segments(series::Series) +function series_segments(series::Series, seriestype::Symbol = :path) x, y, z = series[:x], series[:y], series[:z] - if x === nothing - return UnitRange{Int}[] - elseif has_attribute_segments(series) - if series[:seriestype] in (:scatter, :scatter3d) - return [[i] for i in eachindex(y)] - else - if any(isnan,y) - return [iter_segments(y)...] + (x === nothing || isempty(x)) && return UnitRange{Int}[] + + args = RecipesPipeline.is3d(series) ? (x, y, z) : (x, y) + nan_segments = collect(iter_segments(args...)) + + segments = if has_attribute_segments(series) + Iterators.flatten(map(nan_segments) do r + if seriestype in (:scatter, :scatter3d) + (SeriesSegment(i:i, i) for i in r) else - return [i:(i + 1) for i in firstindex(y):lastindex(y)-1] + (SeriesSegment(i:i+1, i) for i in first(r):last(r)-1) + end + end) + else + (SeriesSegment(r, 1) for r in nan_segments) + end + + warn_on_attr_dim_mismatch(series, x, y, z, segments) + return segments +end + +function warn_on_attr_dim_mismatch(series, x, y, z, segments) + isempty(segments) && return + seg_range = UnitRange(minimum(first(seg.range) for seg in segments), + maximum(last(seg.range) for seg in segments)) + for attr in _segmenting_vector_attributes + v = get(series, attr, nothing) + if v isa AVec && eachindex(v) != seg_range + @warn "Indices $(eachindex(v)) of attribute `$attr` does not match data indices $seg_range." + if any(v -> !isnothing(v) && any(isnan, v), (x,y,z)) + @info """Data contains NaNs or missing values, and indices of `$attr` vector do not match data indices. + If you intend elements of `$attr` to apply to individual NaN-separated segements in the data, + pass each segment in a separate vector instead, and use a row vector for `$attr`. Legend entries + may be suppressed by passing an empty label. + For example, + plot([1:2,1:3], [[4,5],[3,4,5]], label=["y" ""], $attr=[1 2]) + """ end end - else - segs = UnitRange{Int}[] - args = RecipesPipeline.is3d(series) ? (x, y, z) : (x, y) - for seg in iter_segments(args...) - push!(segs, seg) - end - return segs end end @@ -97,7 +125,7 @@ anynan(args::Tuple) = i -> anynan(i,args) anynan(istart::Int, iend::Int, args::Tuple) = any(anynan(args), istart:iend) allnan(istart::Int, iend::Int, args::Tuple) = all(anynan(args), istart:iend) -function Base.iterate(itr::SegmentsIterator, nextidx::Int = itr.n1) +function Base.iterate(itr::NaNSegmentsIterator, nextidx::Int = itr.n1) i = findfirst(!anynan(itr.args), nextidx:itr.n2) i === nothing && return nothing nextval = nextidx + i - 1 @@ -107,6 +135,7 @@ function Base.iterate(itr::SegmentsIterator, nextidx::Int = itr.n1) nextval:nextnan-1, nextnan end +Base.IteratorSize(::NaNSegmentsIterator) = Base.SizeUnknown() # Find minimal type that can contain NaN and x # To allow use of NaN separated segments with categorical x axis @@ -126,8 +155,8 @@ isnothing(x) = false _cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj _cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj -_cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))] -_cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))] +_cycle(v::AVec, idx::Int) = v[mod(idx, axes(v,1))] +_cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[end, mod(idx, axes(v,2))] : v[:, mod(idx, axes(v,2))] _cycle(v, idx::Int) = v _cycle(v::AVec, indices::AVec{Int}) = map(i -> _cycle(v,i), indices) @@ -151,15 +180,18 @@ maketuple(x::Tuple{T,S}) where {T,S} = x for i in 2:4 @eval begin - RecipesPipeline.unzip(v::Union{AVec{<:Tuple{Vararg{T,$i} where T}}, - AVec{<:GeometryTypes.Point{$i}}}) = $(Expr(:tuple, (:([t[$j] for t in v]) for j=1:i)...)) + RecipesPipeline.unzip( + v::Union{AVec{<:Tuple{Vararg{T,$i} where T}}, AVec{<:GeometryBasics.Point{$i}}}, + ) = $(Expr(:tuple, (:([t[$j] for t in v]) for j=1:i)...)) end end -RecipesPipeline.unzip(v::Union{AVec{<:GeometryTypes.Point{N}}, - AVec{<:Tuple{Vararg{T,N} where T}}}) where N = error("$N-dimensional unzip not implemented.") -RecipesPipeline.unzip(v::Union{AVec{<:GeometryTypes.Point}, - AVec{<:Tuple}}) = error("Can't unzip points of different dimensions.") +RecipesPipeline.unzip( + ::Union{AVec{<:GeometryBasics.Point{N}}, AVec{<:Tuple{Vararg{T,N} where T}}} +) where N = error("$N-dimensional unzip not implemented.") +RecipesPipeline.unzip(::Union{AVec{<:GeometryBasics.Point}, AVec{<:Tuple}}) = error( + "Can't unzip points of different dimensions." +) # given 2-element lims and a vector of data x, widen lims to account for the extrema of x function _expand_limits(lims, x) @@ -206,30 +238,27 @@ end createSegments(z) = collect(repeat(reshape(z,1,:),2,1))[2:end] -Base.first(c::Colorant) = c -Base.first(x::Symbol) = x - sortedkeys(plotattributes::Dict) = sort(collect(keys(plotattributes))) -function _heatmap_edges(v::AVec, isedges::Bool = false) - length(v) == 1 && return v[1] .+ [-0.5, 0.5] +function _heatmap_edges(v::AVec, isedges::Bool = false, ispolar::Bool = false) + length(v) == 1 && return v[1] .+ [ispolar ? max(-v[1], -0.5) : -0.5, 0.5] if isedges return v end # `isedges = true` means that v is a vector which already describes edges # and does not need to be extended. vmin, vmax = ignorenan_extrema(v) - extra_min = (v[2] - v[1]) / 2 + extra_min = ispolar ? min(v[1], (v[2] - v[1]) / 2) : (v[2] - v[1]) / 2 extra_max = (v[end] - v[end - 1]) / 2 vcat(vmin-extra_min, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra_max) end "create an (n+1) list of the outsides of heatmap rectangles" -function heatmap_edges(v::AVec, scale::Symbol = :identity, isedges::Bool = false) +function heatmap_edges(v::AVec, scale::Symbol = :identity, isedges::Bool = false, ispolar::Bool = false) f, invf = RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale) - map(invf, _heatmap_edges(map(f,v), isedges)) + map(invf, _heatmap_edges(map(f,v), isedges, ispolar)) end -function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size::Tuple{Int, Int}) +function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size::Tuple{Int, Int}, ispolar::Bool = false) nx, ny = length(x), length(y) # ismidpoints = z_size == (ny, nx) # This fails some tests, but would actually be # the correct check, since (4, 3) != (3, 4) and a missleading plot is produced. @@ -241,7 +270,7 @@ function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size: or `size(z) == (length(y)+1, length(x)+1))` (x & y define edges).""") end x, y = heatmap_edges(x, xscale, isedges), - heatmap_edges(y, yscale, isedges) + heatmap_edges(y, yscale, isedges, ispolar) # special handle for `r` in polar plots return x, y end @@ -294,13 +323,11 @@ limsType(lims::Tuple{T,S}) where {T<:Real,S<:Real} = :limits limsType(lims::Symbol) = lims == :auto ? :auto : :invalid limsType(lims) = :invalid -# axis_Symbol(letter, postfix) = Symbol(letter * postfix) -# axis_symbols(letter, postfix...) = map(s -> axis_Symbol(letter, s), postfix) -Base.convert(::Type{Vector{T}}, rng::AbstractRange{T}) where {T<:Real} = T[x for x in rng] -Base.convert(::Type{Vector{T}}, rng::AbstractRange{S}) where {T<:Real,S<:Real} = T[x for x in rng] - -Base.merge(a::AbstractVector, b::AbstractVector) = sort(unique(vcat(a,b))) +# recursively merge kw-dicts, e.g. for merging extra_kwargs / extra_plot_kwargs in plotly) +recursive_merge(x::AbstractDict...) = merge(recursive_merge, x...) +# if values are not AbstractDicts, take the last definition (as does merge) +recursive_merge(x...) = x[end] nanpush!(a::AbstractVector, b) = (push!(a, NaN); push!(a, b)) nanappend!(a::AbstractVector, b) = (push!(a, NaN); append!(a, b)) @@ -338,17 +365,8 @@ function indices_and_unique_values(z::AbstractArray) newz, vals end -# this is a helper function to determine whether we need to transpose a surface matrix. -# it depends on whether the backend matches rows to x (transpose_on_match == true) or vice versa -# for example: PyPlot sends rows to y, so transpose_on_match should be true -function transpose_z(plotattributes, z, transpose_on_match::Bool = true) - if plotattributes[:match_dimensions] == transpose_on_match - # z' - permutedims(z, [2,1]) - else - z - end -end +handle_surface(z) = z +handle_surface(z::Surface) = permutedims(z.surf) function ok(x::Number, y::Number, z::Number = 0) isfinite(x) && isfinite(y) && isfinite(z) @@ -356,10 +374,10 @@ end ok(tup::Tuple) = ok(tup...) # compute one side of a fill range from a ribbon -function make_fillrange_side(y, rib) - frs = zeros(length(y)) - for (i, (yi, ri)) in enumerate(zip(y, Base.Iterators.cycle(rib))) - frs[i] = yi + ri +function make_fillrange_side(y::AVec, rib) + frs = zeros(axes(y)) + for (i, yi) in pairs(y) + frs[i] = yi + _cycle(rib,i) end frs end @@ -414,80 +432,7 @@ xlims(sp_idx::Int = 1) = xlims(current(), sp_idx) ylims(sp_idx::Int = 1) = ylims(current(), sp_idx) zlims(sp_idx::Int = 1) = zlims(current(), sp_idx) -# These functions return an operator for use in `get_clims(::Seres, op)` -process_clims(lims::NTuple{2,<:Number}) = (zlims -> ifelse.(isfinite.(lims), lims, zlims)) ∘ ignorenan_extrema -process_clims(s::Union{Symbol,Nothing,Missing}) = ignorenan_extrema -# don't specialize on ::Function otherwise python functions won't work -process_clims(f) = f - -function get_clims(sp::Subplot, op=process_clims(sp[:clims])) - zmin, zmax = Inf, -Inf - for series in series_list(sp) - if series[:colorbar_entry] - zmin, zmax = _update_clims(zmin, zmax, get_clims(series, op)...) - end - end - return zmin <= zmax ? (zmin, zmax) : (NaN, NaN) -end - -function get_clims(sp::Subplot, series::Series, op=process_clims(sp[:clims])) - zmin, zmax = if series[:colorbar_entry] - get_clims(sp, op) - else - get_clims(series, op) - end - return zmin <= zmax ? (zmin, zmax) : (NaN, NaN) -end - -""" - get_clims(::Series, op=Plots.ignorenan_extrema) - -Finds the limits for the colorbar by taking the "z-values" for the series and passing them into `op`, -which must return the tuple `(zmin, zmax)`. The default op is the extrema of the finite -values of the input. -""" -function get_clims(series::Series, op=ignorenan_extrema) - zmin, zmax = Inf, -Inf - z_colored_series = (:contour, :contour3d, :heatmap, :histogram2d, :surface) - for vals in (series[:seriestype] in z_colored_series ? series[:z] : nothing, series[:line_z], series[:marker_z], series[:fill_z]) - if (typeof(vals) <: AbstractSurface) && (eltype(vals.surf) <: Union{Missing, Real}) - zmin, zmax = _update_clims(zmin, zmax, op(vals.surf)...) - elseif (vals !== nothing) && (eltype(vals) <: Union{Missing, Real}) - zmin, zmax = _update_clims(zmin, zmax, op(vals)...) - end - end - return zmin <= zmax ? (zmin, zmax) : (NaN, NaN) -end - -_update_clims(zmin, zmax, emin, emax) = NaNMath.min(zmin, emin), NaNMath.max(zmax, emax) - -@enum ColorbarStyle cbar_gradient cbar_fill cbar_lines - -function colorbar_style(series::Series) - colorbar_entry = series[:colorbar_entry] - if !(colorbar_entry isa Bool) - @warn "Non-boolean colorbar_entry ignored." - colorbar_entry = true - end - - if !colorbar_entry - nothing - elseif isfilledcontour(series) - cbar_fill - elseif iscontour(series) - cbar_lines - elseif series[:seriestype] ∈ (:heatmap,:surface) || - any(series[z] !== nothing for z ∈ [:marker_z,:line_z,:fill_z]) - cbar_gradient - else - nothing - end -end - -hascolorbar(series::Series) = colorbar_style(series) !== nothing -hascolorbar(sp::Subplot) = sp[:colorbar] != :none && any(hascolorbar(s) for s in series_list(sp)) - -iscontour(series::Series) = series[:seriestype] == :contour +iscontour(series::Series) = series[:seriestype] in (:contour, :contour3d) isfilledcontour(series::Series) = iscontour(series) && series[:fillrange] !== nothing function contour_levels(series::Series, clims) @@ -583,38 +528,34 @@ function get_markerstrokewidth(series, i::Int = 1) _cycle(series[:markerstrokewidth], i) end +const _segmenting_vector_attributes = ( + :seriescolor, + :seriesalpha, + :linecolor, + :linealpha, + :linewidth, + :linestyle, + :fillcolor, + :fillalpha, + :markercolor, + :markeralpha, + :markersize, + :markerstrokecolor, + :markerstrokealpha, + :markerstrokewidth, + :markershape, +) + +const _segmenting_array_attributes = (:line_z, :fill_z, :marker_z) + function has_attribute_segments(series::Series) # we want to check if a series needs to be split into segments just because # of its attributes - for letter in (:x, :y, :z) - # If we have NaNs in the data they define the segments and - # SegmentsIterator is used - series[letter] !== nothing && NaN in collect(series[letter]) && return false - end series[:seriestype] == :shape && return false - # ... else we check relevant attributes if they have multiple inputs - return any( - (typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1) - for - attr in [ - :seriescolor, - :seriesalpha, - :linecolor, - :linealpha, - :linewidth, - :linestyle, - :fillcolor, - :fillalpha, - :markercolor, - :markeralpha, - :markersize, - :markerstrokecolor, - :markerstrokealpha, - :markerstrokewidth, - ] - ) || any( - typeof(series[attr]) <: AbstractArray for attr in (:line_z, :fill_z, :marker_z) - ) + # check relevant attributes if they have multiple inputs + return any(series[attr] isa AbstractVector && length(series[attr]) > 1 + for attr in _segmenting_vector_attributes + ) || any(series[attr] isa AbstractArray for attr in _segmenting_array_attributes) end function get_aspect_ratio(sp) @@ -983,7 +924,7 @@ px2inch(px::Real) = float(px / PX_PER_INCH) inch2mm(inches::Real) = float(inches * MM_PER_INCH) mm2inch(mm::Real) = float(mm / MM_PER_INCH) px2mm(px::Real) = float(px * MM_PER_PX) -mm2px(mm::Real) = float(px / MM_PER_PX) +mm2px(mm::Real) = float(mm / MM_PER_PX) "Smallest x in plot" @@ -998,54 +939,72 @@ ignorenan_extrema(plt::Plot) = (xmin(plt), xmax(plt)) # --------------------------------------------------------------- # get fonts from objects: -titlefont(sp::Subplot) = font( - sp[:titlefontfamily], - sp[:titlefontsize], - sp[:titlefontvalign], - sp[:titlefonthalign], - sp[:titlefontrotation], - sp[:titlefontcolor], +plottitlefont(p::Plot) = font(; + family = p[:plot_titlefontfamily], + pointsize = p[:plot_titlefontsize], + valign = p[:plot_titlefontvalign], + halign = p[:plot_titlefonthalign], + rotation = p[:plot_titlefontrotation], + color = p[:plot_titlefontcolor], ) -legendfont(sp::Subplot) = font( - sp[:legendfontfamily], - sp[:legendfontsize], - sp[:legendfontvalign], - sp[:legendfonthalign], - sp[:legendfontrotation], - sp[:legendfontcolor], +colorbartitlefont(sp::Subplot) = font(; + family = sp[:colorbar_titlefontfamily], + pointsize = sp[:colorbar_titlefontsize], + valign = sp[:colorbar_titlefontvalign], + halign = sp[:colorbar_titlefonthalign], + rotation = sp[:colorbar_titlefontrotation], + color = sp[:colorbar_titlefontcolor], ) -legendtitlefont(sp::Subplot) = font( - sp[:legendtitlefontfamily], - sp[:legendtitlefontsize], - sp[:legendtitlefontvalign], - sp[:legendtitlefonthalign], - sp[:legendtitlefontrotation], - sp[:legendtitlefontcolor], +titlefont(sp::Subplot) = font(; + family = sp[:titlefontfamily], + pointsize = sp[:titlefontsize], + valign = sp[:titlefontvalign], + halign = sp[:titlefonthalign], + rotation = sp[:titlefontrotation], + color = sp[:titlefontcolor], ) -tickfont(ax::Axis) = font( - ax[:tickfontfamily], - ax[:tickfontsize], - ax[:tickfontvalign], - ax[:tickfonthalign], - ax[:tickfontrotation], - ax[:tickfontcolor], +legendfont(sp::Subplot) = font(; + family = sp[:legendfontfamily], + pointsize = sp[:legendfontsize], + valign = sp[:legendfontvalign], + halign = sp[:legendfonthalign], + rotation = sp[:legendfontrotation], + color = sp[:legendfontcolor], ) -guidefont(ax::Axis) = font( - ax[:guidefontfamily], - ax[:guidefontsize], - ax[:guidefontvalign], - ax[:guidefonthalign], - ax[:guidefontrotation], - ax[:guidefontcolor], +legendtitlefont(sp::Subplot) = font(; + family = sp[:legendtitlefontfamily], + pointsize = sp[:legendtitlefontsize], + valign = sp[:legendtitlefontvalign], + halign = sp[:legendtitlefonthalign], + rotation = sp[:legendtitlefontrotation], + color = sp[:legendtitlefontcolor], +) + +tickfont(ax::Axis) = font(; + family = ax[:tickfontfamily], + pointsize = ax[:tickfontsize], + valign = ax[:tickfontvalign], + halign = ax[:tickfonthalign], + rotation = ax[:tickfontrotation], + color = ax[:tickfontcolor], +) + +guidefont(ax::Axis) = font(; + family = ax[:guidefontfamily], + pointsize = ax[:guidefontsize], + valign = ax[:guidefontvalign], + halign = ax[:guidefonthalign], + rotation = ax[:guidefontrotation], + color = ax[:guidefontcolor], ) # --------------------------------------------------------------- -# converts unicode scientific notation unsupported by pgfplots and gr -# into a format that works +# converts unicode scientific notation, as returned by Showoff, +# to a tex-like format (supported by gr, pyplot, and pgfplots). function convert_sci_unicode(label::AbstractString) unicode_dict = Dict( @@ -1074,10 +1033,21 @@ end function straightline_data(series, expansion_factor = 1) sp = series[:subplot] xl, yl = isvertical(series) ? (xlims(sp), ylims(sp)) : (ylims(sp), xlims(sp)) - x, y = series[:x], series[:y] + + # handle axes scales + xscale = sp[:xaxis][:scale] + xf = RecipesPipeline.scale_func(xscale) + xinvf = RecipesPipeline.inverse_scale_func(xscale) + yscale = sp[:yaxis][:scale] + yf = RecipesPipeline.scale_func(yscale) + yinvf = RecipesPipeline.inverse_scale_func(yscale) + + xl, yl = xf.(xl), yf.(yl) + x, y = xf.(series[:x]), yf.(series[:y]) n = length(x) - if n == 2 - return straightline_data(xl, yl, x, y, expansion_factor) + + xdata, ydata = if n == 2 + straightline_data(xl, yl, x, y, expansion_factor) else k, r = divrem(n, 3) if r == 0 @@ -1086,11 +1056,13 @@ function straightline_data(series, expansion_factor = 1) inds = (3 * i - 2):(3 * i - 1) xdata[inds], ydata[inds] = straightline_data(xl, yl, x[inds], y[inds], expansion_factor) end - return xdata, ydata + xdata, ydata else error("Misformed data. `straightline_data` either accepts vectors of length 2 or 3k. The provided series has length $n") end end + + return xinvf.(xdata), yinvf.(ydata) end function straightline_data(xl, yl, x, y, expansion_factor = 1) @@ -1122,20 +1094,28 @@ end function shape_data(series, expansion_factor = 1) sp = series[:subplot] xl, yl = isvertical(series) ? (xlims(sp), ylims(sp)) : (ylims(sp), xlims(sp)) + + # handle axes scales + xscale = sp[:xaxis][:scale] + xf = RecipesPipeline.scale_func(xscale) + xinvf = RecipesPipeline.inverse_scale_func(xscale) + yscale = sp[:yaxis][:scale] + yf = RecipesPipeline.scale_func(yscale) + yinvf = RecipesPipeline.inverse_scale_func(yscale) + x, y = copy(series[:x]), copy(series[:y]) - factor = 100 for i in eachindex(x) if x[i] == -Inf - x[i] = xl[1] - expansion_factor * (xl[2] - xl[1]) + x[i] = xinvf(xf(xl[1]) - expansion_factor * (xf(xl[2]) - xf(xl[1]))) elseif x[i] == Inf - x[i] = xl[2] + expansion_factor * (xl[2] - xl[1]) + x[i] = xinvf(xf(xl[2]) + expansion_factor * (xf(xl[2]) - xf(xl[1]))) end end for i in eachindex(y) if y[i] == -Inf - y[i] = yl[1] - expansion_factor * (yl[2] - yl[1]) + y[i] = yinvf(yf(yl[1]) - expansion_factor * (yf(yl[2]) - yf(yl[1]))) elseif y[i] == Inf - y[i] = yl[2] + expansion_factor * (yl[2] - yl[1]) + y[i] = yinvf(yf(yl[2]) + expansion_factor * (yf(yl[2]) - yf(yl[1]))) end end return x, y diff --git a/test/imgcomp.jl b/test/imgcomp.jl index e7151573..3a0af3ca 100644 --- a/test/imgcomp.jl +++ b/test/imgcomp.jl @@ -1,15 +1,36 @@ import Plots._current_plots_version -function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = !is_ci(), sigma = [1,1], tol = 1e-2) +# replace `f(args...)` with `f(rng, args...)` for `f ∈ (rand, randn)` +function replace_rand!(ex) end +function replace_rand!(ex::Expr) + for arg in ex.args + replace_rand!(arg) + end + if ex.head === :call && ex.args[1] ∈ (:rand, :randn) + pushfirst!(ex.args, ex.args[1]) + ex.args[2] = :rng + end +end +function fix_rand!(ex) + replace_rand!(ex) + pushfirst!(ex.args[1].args, :(rng = StableRNG(1234))) +end + +function image_comparison_tests( + pkg::Symbol, + idx::Int; + debug = false, + popup = !is_ci(), + sigma = [1, 1], + tol = 1e-2, +) Plots._debugMode.on = debug example = Plots._examples[idx] Plots.theme(:default) @info("Testing plot: $pkg:$idx:$(example.header)") backend(pkg) backend() - default(size=(500,300)) - # ensure consistent results - Random.seed!(1234) + default(size = (500, 300)) fn = "ref$idx.png" reffn = reference_file(pkg, idx, _current_plots_version) @@ -19,25 +40,29 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = !i func = (fn, idx) -> begin expr = Expr(:block) append!(expr.args, example.exprs) + fix_rand!(expr) eval(expr) png(fn) end # the test vtest = VisualTest(func, reffn, idx) - test_images(vtest, popup=popup, sigma=sigma, tol=tol, newfn = newfn) + test_images(vtest, popup = popup, sigma = sigma, tol = tol, newfn = newfn) end -function image_comparison_facts(pkg::Symbol; - skip = [], # skip these examples (int index) - only = nothing, # limit to these examples (int index) - debug = false, # print debug information? - sigma = [1,1], # number of pixels to "blur" - tol = 1e-2) # acceptable error (percent) - for i in 1:length(Plots._examples) - i in skip && continue - if only === nothing || i in only - @test image_comparison_tests(pkg, i, debug=debug, sigma=sigma, tol=tol) |> success == true +function image_comparison_facts( + pkg::Symbol; + skip = [], # skip these examples (int index) + only = nothing, # limit to these examples (int index) + debug = false, # print debug information? + sigma = [1, 1], # number of pixels to "blur" + tol = 1e-2, +) # acceptable error (percent) + for i = 1:length(Plots._examples) + i in skip && continue + if only === nothing || i in only + @test image_comparison_tests(pkg, i, debug = debug, sigma = sigma, tol = tol) |> + success == true + end end - end end diff --git a/test/integration_dates.jl b/test/integration_dates.jl new file mode 100644 index 00000000..081d69fb --- /dev/null +++ b/test/integration_dates.jl @@ -0,0 +1,60 @@ +using Plots, Test, Dates + +@testset "Limits" begin + y=[1.0*i*i for i in 1:10] + x=[Date(2019,11,i) for i in 1:10] + + rx=[x[3],x[5]] + + p = plot(x,y, widen = false) + vspan!(p, rx, label="", alpha=0.2) + + ref_ylims = (y[1], y[end]) + ref_xlims = (x[1].instant.periods.value, x[end].instant.periods.value) + @test Plots.ylims(p) == ref_ylims + @test Plots.xlims(p) == ref_xlims + #@static if (haskey(ENV, "APPVEYOR") || haskey(ENV, "CI")) + @static if haskey(ENV, "APPVEYOR") + @info "Skipping display tests on AppVeyor" + else + @test isa(display(p), Nothing) == true + closeall() + end +end # testset + +@testset "Date xlims" begin + y=[1.0*i*i for i in 1:10] + x=[Date(2019,11,i) for i in 1:10] + span = (Date(2019,10,31), Date(2019,11,11)) + + ref_xlims = map(date->date.instant.periods.value, span) + + p = plot(x,y, xlims=span, widen = false) + + @test Plots.xlims(p) == ref_xlims + #@static if (haskey(ENV, "APPVEYOR") || haskey(ENV, "CI")) + @static if haskey(ENV, "APPVEYOR") + @info "Skipping display tests on AppVeyor" + else + @test isa(display(p), Nothing) == true + closeall() + end +end # testset + +@testset "DateTime xlims" begin + y=[1.0*i*i for i in 1:10] + x=[DateTime(2019,11,i,11) for i in 1:10] + span = (DateTime(2019,10,31,11,59,59), DateTime(2019,11,11,12,01,15)) + + ref_xlims = map(date->date.instant.periods.value, span) + + p = plot(x,y, xlims=span, widen = false) + @test Plots.xlims(p) == ref_xlims + #@static if (haskey(ENV, "APPVEYOR") || haskey(ENV, "CI")) + @static if haskey(ENV, "APPVEYOR") + @info "Skipping display tests on AppVeyor" + else + @test isa(display(p), Nothing) == true + closeall() + end +end # testset diff --git a/test/runtests.jl b/test/runtests.jl index a6a88b31..997e1258 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,14 +1,41 @@ +using Plots: guidefont, series_annotations import ImageMagick using VisualRegressionTests using Plots using Random +using StableRNGs using Test +using TestImages using FileIO using Gtk using LibGit2 -using GeometryTypes +import GeometryBasics using Dates +using RecipesBase + +@testset "Plotly standalone" begin + @test_nowarn Plots._init_ijulia_plotting() + @test Plots.plotly_local_file_path[] === nothing + temp = Plots.use_local_dependencies[] + withenv("PLOTS_HOST_DEPENDENCY_LOCAL" => true) do + Plots.__init__() + @test Plots.plotly_local_file_path[] isa String + @test isfile(Plots.plotly_local_file_path[]) + @test Plots.use_local_dependencies[] = true + @test_nowarn Plots._init_ijulia_plotting() +end + Plots.plotly_local_file_path[] = nothing + Plots.use_local_dependencies[] = temp +end # testset + +include("test_defaults.jl") +include("test_axes.jl") +include("test_axis_letter.jl") +include("test_components.jl") +include("test_shorthands.jl") +include("integration_dates.jl") +include("test_recipes.jl") include("test_hdf5plots.jl") include("test_pgfplotsx.jl") @@ -17,7 +44,7 @@ reference_dir(args...) = joinpath(homedir(), ".julia", "dev", "PlotReferenceImag function reference_file(backend, i, version) refdir = reference_dir("Plots", string(backend)) fn = "ref$i.png" - versions = sort(VersionNumber.(readdir(refdir)), rev = true) + versions = sort(VersionNumber.(readdir(refdir)), rev=true) reffn = joinpath(refdir, string(version), fn) for v in versions @@ -43,27 +70,27 @@ include("imgcomp.jl") Random.seed!(1234) default(show=false, reuse=true) is_ci() = get(ENV, "CI", "false") == "true" -img_tol = is_ci() ? 1e-2 : Sys.islinux() ? 1e-3 : 0.1 +const IMG_TOL = VERSION < v"1.4" && Sys.iswindows() ? 1e-1 : is_ci() ? 1e-2 : 1e-3 ## Uncomment the following lines to update reference images for different backends # @testset "GR" begin -# image_comparison_facts(:gr, tol=img_tol, skip = Plots._backend_skips[:gr]) +# image_comparison_facts(:gr, tol=IMG_TOL, skip = Plots._backend_skips[:gr]) # end # # plotly() # @testset "Plotly" begin -# image_comparison_facts(:plotly, tol=img_tol, skip = Plots._backend_skips[:plotlyjs]) +# image_comparison_facts(:plotly, tol=IMG_TOL, skip = Plots._backend_skips[:plotlyjs]) # end # # pyplot() # @testset "PyPlot" begin -# image_comparison_facts(:pyplot, tol=img_tol, skip = Plots._backend_skips[:pyplot]) +# image_comparison_facts(:pyplot, tol=IMG_TOL, skip = Plots._backend_skips[:pyplot]) # end # # pgfplotsx() # @testset "PGFPlotsX" begin -# image_comparison_facts(:pgfplotsx, tol=img_tol, skip = Plots._backend_skips[:pgfplotsx]) +# image_comparison_facts(:pgfplotsx, tol=IMG_TOL, skip = Plots._backend_skips[:pgfplotsx]) # end # 10 Histogram2D @@ -81,7 +108,7 @@ img_tol = is_ci() ? 1e-2 : Sys.islinux() ? 1e-3 : 0.1 @static if haskey(ENV, "APPVEYOR") @info "Skipping GR image comparison tests on AppVeyor" else - image_comparison_facts(:gr, tol=img_tol, skip = Plots._backend_skips[:gr]) + image_comparison_facts(:gr, tol=IMG_TOL, skip=Plots._backend_skips[:gr]) end end @@ -107,12 +134,21 @@ img_tol = is_ci() ? 1e-2 : Sys.islinux() ? 1e-3 : 0.1 @test isa(p, Plots.Plot) == true @test isa(display(p), Nothing) == true p = plot([Dates.Date(2019, 1, 1), Dates.Date(2019, 2, 1)], [3, 4]) - annotate!(p, [(Dates.Date(2019, 1, 15), 3.2, Plots.text("Test", :red, :center))]) + annotate!(p, [(Dates.Date(2019, 1, 15), 3.2, :auto)]) hline!(p, [3.1]) @test isa(p, Plots.Plot) == true @test isa(display(p), Nothing) == true end + @testset "PlotlyJS" begin + @test plotlyjs() == Plots.PlotlyJSBackend() + @test backend() == Plots.PlotlyJSBackend() + + p = plot(rand(10)) + @test isa(p, Plots.Plot) == true + @test_broken isa(display(p), Nothing) == true + end + end @testset "Axes" begin @@ -121,27 +157,42 @@ end @test typeof(axis) == Plots.Axis @test Plots.discrete_value!(axis, "HI") == (0.5, 1) @test Plots.discrete_value!(axis, :yo) == (1.5, 2) - @test Plots.ignorenan_extrema(axis) == (0.5,1.5) + @test Plots.ignorenan_extrema(axis) == (0.5, 1.5) @test axis[:discrete_map] == Dict{Any,Any}(:yo => 2, "HI" => 1) - Plots.discrete_value!(axis, ["x$i" for i=1:5]) - Plots.discrete_value!(axis, ["x$i" for i=0:2]) + Plots.discrete_value!(axis, ["x$i" for i = 1:5]) + Plots.discrete_value!(axis, ["x$i" for i = 0:2]) @test Plots.ignorenan_extrema(axis) == (0.5, 7.5) end @testset "NoFail" begin - plots = [histogram([1, 0, 0, 0, 0, 0]), - plot([missing]), - plot([missing; 1:4]), - plot([fill(missing,10); 1:4]), - plot([1 1; 1 missing]), - plot(["a" "b"; missing "d"], [1 2; 3 4])] - for plt in plots - display(plt) + #ensure backend with tested display + @test unicodeplots() == Plots.UnicodePlotsBackend() + @test backend() == Plots.UnicodePlotsBackend() + + @testset "Plot" begin + plots = [histogram([1, 0, 0, 0, 0, 0]), + plot([missing]), + plot([missing, missing]), + plot(fill(missing, 10)), + plot([missing; 1:4]), + plot([fill(missing, 10); 1:4]), + plot([1 1; 1 missing]), + plot(["a" "b"; missing "d"], [1 2; 3 4])] + for plt in plots + display(plt) + end + @test_nowarn plot(x -> x^2, 0, 2) + end + + @testset "Bar" begin + p = bar([3,2,1], [1,2,3]); + @test isa(p, Plots.Plot) + @test isa(display(p), Nothing) == true end - @test_nowarn plot(x->x^2,0,2) end + @testset "EmptyAnim" begin anim = @animate for i in [] end @@ -149,16 +200,10 @@ end @test_throws ArgumentError gif(anim) end -@testset "Segments" begin - function segments(args...) - segs = UnitRange{Int}[] - for seg in iter_segments(args...) - push!(segs,seg) - end - segs - end +@testset "NaN-separated Segments" begin + segments(args...) = collect(iter_segments(args...)) - nan10 = fill(NaN,10) + nan10 = fill(NaN, 10) @test segments(11:20) == [1:10] @test segments([NaN]) == [] @test segments(nan10) == [] @@ -170,12 +215,17 @@ end end @testset "Utils" begin - zipped = ([(1,2)], [("a","b")], [(1,"a"),(2,"b")], - [(1,2),(3,4)], [(1,2,3),(3,4,5)], [(1,2,3,4),(3,4,5,6)], - [(1,2.0),(missing,missing)], [(1,missing),(missing,"a")], - [(missing,missing)], [(missing,missing,missing),("a","b","c")]) + zipped = ([(1, 2)], [("a", "b")], [(1, "a"),(2, "b")], + [(1, 2),(3, 4)], [(1, 2, 3),(3, 4, 5)], [(1, 2, 3, 4),(3, 4, 5, 6)], + [(1, 2.0),(missing, missing)], [(1, missing),(missing, "a")], + [(missing, missing)], [(missing, missing, missing),("a", "b", "c")]) for z in zipped @test isequal(collect(zip(Plots.unzip(z)...)), z) - @test isequal(collect(zip(Plots.unzip(GeometryTypes.Point.(z))...)), z) + @test isequal(collect(zip(Plots.unzip(GeometryBasics.Point.(z))...)), z) end + op1 = Plots.process_clims((1.0, 2.0)) + op2 = Plots.process_clims((1, 2.0)) + data = randn(100, 100) + @test op1(data) == op2(data) + @test Plots.process_clims(nothing) == Plots.process_clims(missing) == Plots.process_clims(:auto) end diff --git a/test/test_axes.jl b/test/test_axes.jl new file mode 100644 index 00000000..f65cd745 --- /dev/null +++ b/test/test_axes.jl @@ -0,0 +1,65 @@ +using Plots, Test + +@testset "Showaxis" begin + for value in Plots._allShowaxisArgs + @test plot(1:5, showaxis = value)[1][:yaxis][:showaxis] isa Bool + end + @test plot(1:5, showaxis = :y)[1][:yaxis][:showaxis] == true + @test plot(1:5, showaxis = :y)[1][:xaxis][:showaxis] == false +end + +@testset "Magic axis" begin + @test plot(1, axis=nothing)[1][:xaxis][:ticks] == [] + @test plot(1, axis=nothing)[1][:yaxis][:ticks] == [] +end # testset + +@testset "Categorical ticks" begin + p1 = plot('A':'M', 1:13) + p2 = plot('A':'Z', 1:26) + p3 = plot('A':'Z', 1:26, ticks = :all) + @test Plots.get_ticks(p1[1], p1[1][:xaxis])[2] == string.('A':'M') + @test Plots.get_ticks(p2[1], p2[1][:xaxis])[2] == string.('C':3:'Z') + @test Plots.get_ticks(p3[1], p3[1][:xaxis])[2] == string.('A':'Z') +end + +@testset "Ticks getter functions" begin + ticks1 = ([1,2,3], ("a","b","c")) + ticks2 = ([4,5], ("e","f")) + p1 = plot(1:5, 1:5, 1:5, xticks=ticks1, yticks=ticks1, zticks=ticks1) + p2 = plot(1:5, 1:5, 1:5, xticks=ticks2, yticks=ticks2, zticks=ticks2) + p = plot(p1, p2) + @test xticks(p) == yticks(p) == zticks(p) == [ticks1, ticks2] + @test xticks(p[1]) == yticks(p[1]) == zticks(p[1]) == ticks1 +end + +@testset "Axis limits" begin + pl = plot(1:5, xlims=:symmetric, widen = false) + @test Plots.xlims(pl) == (-5, 5) + + pl = plot(1:3) + @test Plots.xlims(pl) == Plots.widen(1,3) + + pl = plot([1.05,2.0,2.95], ylims=:round) + @test Plots.ylims(pl) == (1, 3) + + pl = plot(1:3, xlims=(1,5)) + @test Plots.xlims(pl) == (1, 5) + + pl = plot(1:3, xlims=(1,5), widen=true) + @test Plots.xlims(pl) == Plots.widen(1, 5) +end + +@testset "3D Axis" begin + ql = quiver([1, 2], [2, 1], [3, 4], quiver = ([1, -1], [0, 0], [1, -0.5]), arrow=true) + @test ql[1][:projection] == "3d" +end + +@testset "twinx" begin + pl = plot(1:10, margin = 2Plots.cm) + twpl = twinx(pl) + pl! = plot!(twinx(), -(1:10)) + @test twpl[:right_margin] == 2Plots.cm + @test twpl[:left_margin] == 2Plots.cm + @test twpl[:top_margin] == 2Plots.cm + @test twpl[:bottom_margin] == 2Plots.cm +end diff --git a/test/test_axis_letter.jl b/test/test_axis_letter.jl new file mode 100644 index 00000000..691034b8 --- /dev/null +++ b/test/test_axis_letter.jl @@ -0,0 +1,25 @@ +using Plots, Test + +@testset "axis letter" begin + using Plots, RecipesBase + # a custom type for dispacthing the axis-letter-testing recipe + struct MyType <: Number + val::Float64 + end + value(m::MyType) = m.val + data = MyType.(sort(randn(20))) + # A recipe that puts the axis letter in the title + @recipe function f(::Type{T}, m::T) where T <: AbstractArray{<:MyType} + title --> string(plotattributes[:letter]) + value.(m) + end + @testset "$f (orientation = $o)" for f in [histogram, barhist, stephist, scatterhist], o in [:vertical, :horizontal] + @test f(data, orientation=o).subplots[1].attr[:title] == (o == :vertical ? "x" : "y") + end + @testset "$f" for f in [hline, hspan] + @test f(data).subplots[1].attr[:title] == "y" + end + @testset "$f" for f in [vline, vspan] + @test f(data).subplots[1].attr[:title] == "x" + end +end diff --git a/test/test_components.jl b/test/test_components.jl new file mode 100644 index 00000000..ce616075 --- /dev/null +++ b/test/test_components.jl @@ -0,0 +1,151 @@ +using Plots, Test + +@testset "Shapes" begin + @testset "Type" begin + square = Shape([(0, 0.0), (1, 0.0), (1, 1.0), (0, 1.0)]) + @test isa(square, Shape{Int64,Float64}) + @test coords(square) isa Tuple{Vector{S},Vector{T}} where {T,S} + end + + @testset "Copy" begin + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) + square2 = Shape(square) + @test square2.x == square.x + @test square2.y == square.y + end + + @testset "Center" begin + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) + @test Plots.center(square) == (0.5, 0.5) + end + + @testset "Translate" begin + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) + squareUp = Shape([(0, 1), (1, 1), (1, 2), (0, 2)]) + squareUpRight = Shape([(1, 1), (2, 1), (2, 2), (1, 2)]) + + @test Plots.translate(square, 0, 1).x == squareUp.x + @test Plots.translate(square, 0, 1).y == squareUp.y + + @test Plots.center(translate!(square, 1)) == (1.5, 1.5) + end + + @testset "Rotate" begin + # 2 radians rotation matrix + R2 = [cos(2) sin(2); -sin(2) cos(2)] + coords = [0 0; 1 0; 1 1; 0 1]' + coordsRotated2 = R2 * (coords .- 0.5) .+ 0.5 + + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) + + # make a new, rotated square + square2 = Plots.rotate(square, -2) + @test square2.x ≈ coordsRotated2[1, :] + @test square2.y ≈ coordsRotated2[2, :] + + # unrotate the new square in place + rotate!(square2, 2) + @test square2.x ≈ coords[1, :] + @test square2.y ≈ coords[2, :] + end + + @testset "Plot" begin + ang = range(0, 2π, length = 60) + ellipse(x, y, w, h) = Shape(w * sin.(ang) .+ x, h * cos.(ang) .+ y) + myshapes = [ellipse(x, rand(), rand(), rand()) for x = 1:4] + @test coords(myshapes) isa Tuple{Vector{Vector{S}},Vector{Vector{T}}} where {T,S} + local p + @test_nowarn p = plot(myshapes) + @test p[1][1][:seriestype] == :shape + end +end + +@testset "Brush" begin + @testset "Colors" begin + baseline = brush(1, RGB(0, 0, 0)) + @test brush(:black) == baseline + @test brush("black") == baseline + end + @testset "Weight" begin + @test brush(10).size == 10 + @test brush(0.1).size == 1 + end + @testset "Alpha" begin + @test brush(0.4).alpha == 0.4 + @test brush(20).alpha == nothing + end + @testset "Bad Argument" begin + # using test_logs because test_warn seems to not work anymore + @test_logs (:warn, "Unused brush arg: nothing (Nothing)") begin + brush(nothing) + end + end +end + +@testset "Fonts" begin + @testset "Scaling" begin + sizesToCheck = [ + :titlefontsize, + :legendfontsize, + :legendtitlefontsize, + :xtickfontsize, + :ytickfontsize, + :ztickfontsize, + :xguidefontsize, + :yguidefontsize, + :zguidefontsize, + ] + # get inital font sizes + initialSizes = [Plots.default(s) for s in sizesToCheck] + + #scale up font sizes + scalefontsizes(2) + + # get inital font sizes + doubledSizes = [Plots.default(s) for s in sizesToCheck] + + @test doubledSizes == initialSizes * 2 + + # reset font sizes + resetfontsizes() + + finalSizes = [Plots.default(s) for s in sizesToCheck] + + @test finalSizes == initialSizes + end +end + +@testset "Series Annotations" begin + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) + @test_logs (:warn, "Unused SeriesAnnotations arg: triangle (Symbol)") begin + p = plot( + [1, 2, 3], + series_annotations = ( + ["a"], + 2, # pass a scale factor + (1, 4), # pass two scale factors (overwrites first one) + square, # pass a shape + font(:courier), # pass an annotation font + :triangle, # pass an incorrect argument + ), + ) + sa = p.series_list[1].plotattributes[:series_annotations] + @test sa.strs == ["a"] + @test sa.font.family == "courier" + @test sa.baseshape == square + @test sa.scalefactor == (1, 4) + end + spl = scatter( + 4.53 .* [1/1 1/2 1/3 1/4 1/5], + [0 0 0 0 0], + layout = (5, 1), + ylims = (-1.1, 1.1), + xlims = (0, 5), + series_annotations = permutedims([["1/1"],["1/2"],["1/3"],["1/4"],["1/5"]]), + ) + @test spl.series_list[1].plotattributes[:series_annotations].strs == ["1/1"] + @test spl.series_list[2].plotattributes[:series_annotations].strs == ["1/2"] + @test spl.series_list[3].plotattributes[:series_annotations].strs == ["1/3"] + @test spl.series_list[4].plotattributes[:series_annotations].strs == ["1/4"] + @test spl.series_list[5].plotattributes[:series_annotations].strs == ["1/5"] +end diff --git a/test/test_defaults.jl b/test/test_defaults.jl new file mode 100644 index 00000000..9ce50738 --- /dev/null +++ b/test/test_defaults.jl @@ -0,0 +1,13 @@ +using Plots, Test + +const PLOTS_DEFAULTS = Dict(:theme => :wong2, :fontfamily => :palantino) +Plots.__init__() + +@testset "Loading theme" begin + pl = plot(1:5) + @test pl[1][1][:seriescolor] == RGBA(colorant"black") + @test guidefont(pl[1][:xaxis]).family == "palantino" +end + +empty!(PLOTS_DEFAULTS) +Plots.__init__() diff --git a/test/test_pgfplotsx.jl b/test/test_pgfplotsx.jl index 302d4fb2..18fd68e5 100644 --- a/test/test_pgfplotsx.jl +++ b/test/test_pgfplotsx.jl @@ -21,12 +21,12 @@ end @test !haskey(axis.contents[1].options.dict, "fill") @testset "Legends" begin - legends_plot = plot( rand(5,2), lab = ["1" ""] ) - scatter!(legends_plot, rand(5) ) + legends_plot = plot(rand(5, 2), lab = ["1" ""]) + scatter!(legends_plot, rand(5)) Plots._update_plot_object(legends_plot) axis_contents = Plots.pgfx_axes(legends_plot.o)[1].contents - leg_entries = filter( x -> x isa PGFPlotsX.LegendEntry, axis_contents ) - series = filter( x -> x isa PGFPlotsX.Plot, axis_contents ) + leg_entries = filter(x -> x isa PGFPlotsX.LegendEntry, axis_contents) + series = filter(x -> x isa PGFPlotsX.Plot, axis_contents) @test length(leg_entries) == 2 @test !haskey(series[1].options.dict, "forget plot") @test haskey(series[2].options.dict, "forget plot") @@ -91,8 +91,8 @@ end scatter!(pic, rand(100), markersize = 6, c = :orange) Plots._update_plot_object(pic) axis_contents = Plots.pgfx_axes(pic.o)[1].contents - leg_entries = filter( x -> x isa PGFPlotsX.LegendEntry, axis_contents ) - series = filter( x -> x isa PGFPlotsX.Plot, axis_contents ) + leg_entries = filter(x -> x isa PGFPlotsX.LegendEntry, axis_contents) + series = filter(x -> x isa PGFPlotsX.Plot, axis_contents) @test length(leg_entries) == 2 @test length(series) == 4 @test haskey(series[1].options.dict, "forget plot") @@ -106,7 +106,7 @@ end end), Plots._shape_keys) markers = reshape(markers, 1, length(markers)) n = length(markers) - x = (range(0, stop = 10, length = n + 2))[2:(end - 1)] + x = (range(0, stop = 10, length = n + 2))[2:(end-1)] y = repeat(reshape(reverse(x), 1, :), n, 1) scatter( x, @@ -231,7 +231,7 @@ end # TODO: support :semi end # testset @testset "Quiver" begin - x = (-2pi):0.2:(2 * pi) + x = (-2pi):0.2:(2*pi) y = sin.(x) u = ones(length(x)) @@ -255,7 +255,7 @@ end nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content) @test length(nodes) == 1 mktempdir() do path - file_path =joinpath(path,"annotations.tex") + file_path = joinpath(path, "annotations.tex") @test_nowarn savefig(pgfx_plot, file_path) open(file_path) do io lines = readlines(io) @@ -271,7 +271,7 @@ end nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content) @test length(nodes) == 3 mktempdir() do path - file_path =joinpath(path,"annotations.tex") + file_path = joinpath(path, "annotations.tex") @test_nowarn savefig(pgfx_plot, file_path) open(file_path) do io lines = readlines(io) @@ -296,12 +296,17 @@ end nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content) @test length(nodes) == 9 mktempdir() do path - file_path =joinpath(path,"annotations.tex") + file_path = joinpath(path, "annotations.tex") @test_nowarn savefig(annotation_plot, file_path) open(file_path) do io lines = readlines(io) @test count(s -> occursin("node", s), lines) == 9 end + # test .tikz extension + file_path = joinpath(path, "annotations.tikz") + @test_nowarn savefig(annotation_plot, file_path) + @test_nowarn open(file_path) do io + end end end # testset @testset "Ribbon" begin @@ -314,17 +319,35 @@ end Plots._update_plot_object(ribbon_plot) axis = Plots.pgfx_axes(ribbon_plot.o)[1] plots = filter(x -> x isa PGFPlotsX.Plot, axis.contents) - @test length(plots) == 4 - @test !haskey(plots[1].options.dict, "fill") - @test !haskey(plots[2].options.dict, "fill") + @test length(plots) == 3 + @test haskey(plots[1].options.dict, "fill") + @test haskey(plots[2].options.dict, "fill") @test !haskey(plots[3].options.dict, "fill") - @test haskey(plots[4].options.dict, "fill") @test ribbon_plot.o !== nothing @test ribbon_plot.o.the_plot !== nothing - # mktempdir() do path - # @test_nowarn savefig(ribbon_plot, path*"ribbon.svg") - # end end # testset + @testset "Markers and Paths" begin + pl = plot( + 5 .- ones(9), + markershape = [:utriangle, :rect], + markersize = 8, + color = [:red, :black], + ) + Plots._update_plot_object(pl) + axis = Plots.pgfx_axes(pl.o)[1] + plots = filter(x -> x isa PGFPlotsX.Plot, axis.contents) + @test length(plots) == 9 + end # testset + @testset "Groups and Subplots" begin + group = rand(map((i->begin + "group $(i)" + end), 1:4), 100) + pl = plot(rand(100), layout = @layout([a b; c]), group = group, linetype = [:bar :scatter :steppre], linecolor = :match) + Plots._update_plot_object(pl) + axis = Plots.pgfx_axes(pl.o)[1] + legend_entries = filter(x -> x isa PGFPlotsX.LegendEntry, axis.contents) + @test length(legend_entries) == 2 + end end # testset @testset "Extra kwargs" begin @@ -334,15 +357,65 @@ end # testset @test pl[1].attr[:extra_kwargs][:test] == "me" pl = plot(1:5, test = "me", extra_kwargs = :plot) @test pl.attr[:extra_plot_kwargs][:test] == "me" - pl = plot(1:5, extra_kwargs = Dict(:plot => Dict(:test => "me"), :series => Dict(:and => "me too"))) + pl = plot( + 1:5, + extra_kwargs = Dict( + :plot => Dict(:test => "me"), + :series => Dict(:and => "me too"), + ), + ) @test pl.attr[:extra_plot_kwargs][:test] == "me" @test pl[1][1].plotattributes[:extra_kwargs][:and] == "me too" pl = plot( - plot(1:5, title="Line"), - scatter(1:5, title="Scatter", extra_kwargs=Dict(:subplot=>Dict("axis line shift" => "10pt"))) + plot(1:5, title = "Line"), + scatter( + 1:5, + title = "Scatter", + extra_kwargs = Dict(:subplot => Dict("axis line shift" => "10pt")), + ), ) Plots._update_plot_object(pl) axes = Plots.pgfx_axes(pl.o) @test !haskey(axes[1].options.dict, "axis line shift") @test haskey(axes[2].options.dict, "axis line shift") + pl = plot( + x -> x, + -1:1; + add = raw"\node at (0,0.5) {\huge hi};", + extra_kwargs = :subplot, + ) + @test pl[1][:extra_kwargs] == Dict(:add => raw"\node at (0,0.5) {\huge hi};") + Plots._update_plot_object(pl) + axes = Plots.pgfx_axes(pl.o) + @test filter(x -> x isa String, axes[1].contents)[1] == + raw"\node at (0,0.5) {\huge hi};" + plot!(pl) + @test pl[1][:extra_kwargs] == Dict(:add => raw"\node at (0,0.5) {\huge hi};") + Plots._update_plot_object(pl) + axes = Plots.pgfx_axes(pl.o) + @test filter(x -> x isa String, axes[1].contents)[1] == + raw"\node at (0,0.5) {\huge hi};" +end # testset + +@testset "Titlefonts" begin + pl = plot(1:5, title = "Test me", titlefont = (2, :left)) + @test pl[1][:title] == "Test me" + @test pl[1][:titlefontsize] == 2 + @test pl[1][:titlefonthalign] == :left + Plots._update_plot_object(pl) + ax_opt = Plots.pgfx_axes(pl.o)[1].options + @test ax_opt["title"] == "Test me" + @test(haskey(ax_opt.dict, "title style")) isa Test.Pass + pl = plot(1:5, plot_title = "Test me", plot_titlefont = (2, :left)) + @test pl[:plot_title] == "Test me" + @test pl[:plot_titlefontsize] == 2 + @test pl[:plot_titlefonthalign] == :left + pl = heatmap( + rand(3, 3), + colorbar_title = "Test me", + colorbar_titlefont = (12, :right), + ) + @test pl[1][:colorbar_title] == "Test me" + @test pl[1][:colorbar_titlefontsize] == 12 + @test pl[1][:colorbar_titlefonthalign] == :right end # testset diff --git a/test/test_recipes.jl b/test/test_recipes.jl new file mode 100644 index 00000000..a228d2a6 --- /dev/null +++ b/test/test_recipes.jl @@ -0,0 +1,49 @@ +using Plots, Test +using OffsetArrays + +@testset "lens!" begin + pl = plot(1:5) + lens!(pl, [1,2], [1,2], inset = (1, bbox(0.0,0.0,0.2,0.2)), colorbar = false) + @test length(pl.series_list) == 4 + @test pl[2][:colorbar] == :none +end # testset + +@testset "vline, vspan" begin + vl = vline([1], widen = false) + @test Plots.xlims(vl) == (1,2) + @test Plots.ylims(vl) == (1,2) + vl = vline([1], xlims=(0,2), widen = false) + @test Plots.xlims(vl) == (0,2) + vl = vline([1], ylims=(-3,5), widen = false) + @test Plots.ylims(vl) == (-3,5) + + vsp = vspan([1,3], widen = false) + @test Plots.xlims(vsp) == (1,3) + @test Plots.ylims(vsp) == (0,1) # TODO: might be problematic on log-scales + vsp = vspan([1,3], xlims=(-2,5), widen = false) + @test Plots.xlims(vsp) == (-2,5) + vsp = vspan([1,3], ylims=(-2,5), widen = false) + @test Plots.ylims(vsp) == (-2,5) +end # testset + +@testset "offset axes" begin + tri = OffsetVector(vcat(1:5, 4:-1:1), 11:19) + sticks = plot(tri, seriestype = :sticks) + @test length(sticks) == 1 +end + +@testset "framestyle axes" begin + pl = plot(-1:1, -1:1, -1:1) + sp = pl.subplots[1] + defaultret = Plots.axis_drawing_info_3d(sp, :x) + for letter in [:x, :y, :z] + for fr in [:box :semi :origin :zerolines :grid :none] + prevha = UInt64(0) + push!(sp.attr, :framestyle => fr) + ret = Plots.axis_drawing_info_3d(sp, letter) + ha = hash(string(ret)) + @test ha != prevha + prevha = ha + end + end +end \ No newline at end of file diff --git a/test/test_shorthands.jl b/test/test_shorthands.jl new file mode 100644 index 00000000..891c0c14 --- /dev/null +++ b/test/test_shorthands.jl @@ -0,0 +1,48 @@ +using Plots, Test + +@testset "Shorthands" begin + @testset "Set Lims" begin + p = plot(rand(10)) + + xlims!((1,20)) + @test xlims(p) == (1,20) + + ylims!((-1,1)) + @test ylims(p) == (-1,1) + + zlims!((-1,1)) + @test zlims(p) == (-1,1) + + xlims!(-1,11) + @test xlims(p) == (-1,11) + + ylims!((-10,10)) + @test ylims(p) == (-10,10) + + zlims!((-10,10)) + @test zlims(p) == (-10,10) + end + + @testset "Set Ticks" begin + p = plot([0,2,3,4,5,6,7,8,9,10]) + + xticks = 2:6 + xticks!(xticks) + @test Plots.get_subplot(current(),1).attr[:xaxis][:ticks] == xticks + + yticks = 0.2:0.1:0.7 + yticks!(yticks) + @test Plots.get_subplot(current(),1).attr[:yaxis][:ticks] == yticks + + xticks = [5,6,7.5] + xlabels = ["a","b","c"] + + xticks!(xticks, xlabels) + @test Plots.get_subplot(current(),1).attr[:xaxis][:ticks] == (xticks, xlabels) + + yticks = [.5,.6,.75] + ylabels = ["z","y","x"] + yticks!(yticks, ylabels) + @test Plots.get_subplot(current(),1).attr[:yaxis][:ticks] == (yticks, ylabels) + end +end diff --git a/tmpplotsave.hdf5 b/tmpplotsave.hdf5 deleted file mode 100644 index 66f7bab5..00000000 Binary files a/tmpplotsave.hdf5 and /dev/null differ