Compare commits

...

272 Commits

Author SHA1 Message Date
Simon Christ 9b4c66be9a adjust markersize (#2589)
* fix px to pt conversion for markers

* fix test

* bump version [skip ci]

@JuliaRegistrator register
2020-04-16 15:13:21 +02:00
Daniel Schwabeneder 3b3a09302f Merge pull request #2584 from isentropic/isen-dev2
Isen dev2
2020-04-16 09:59:29 +02:00
Simon Christ fe92a384b7 fix point meta for colorbars (#2586) 2020-04-16 00:09:28 +02:00
Daniel Schwabeneder befe20700a Merge pull request #2585 from daschw/span
fix vspan and hspan
2020-04-15 22:07:01 +02:00
Daniel Schwabeneder 41b34e59c7 fix vspan and hspan (copy in shape_data) 2020-04-15 19:53:31 +02:00
Zh c01ee17d88 pyplot markercolor 2020-04-16 01:21:05 +09:00
Zh 8c9422f470 py_get<component>color removal;
marker stroke, adjustments
2020-04-16 00:32:33 +09:00
Zh 7c88256ca4 Merge branch 'master' into isen-dev2 2020-04-15 23:33:39 +09:00
Zh f5fe2692e2 Merge branch 'master' into isen-dev 2020-04-15 23:33:26 +09:00
Josef Heinen d00920ca0e Update gr.jl 2020-04-15 15:16:00 +02:00
Daniel Schwabeneder ef36c6bd0a Merge pull request #2581 from daschw/pgfplotsx
add legendtitle for pgfplotsx
2020-04-15 10:53:08 +02:00
Daniel Schwabeneder a64d84ae70 bump version 2020-04-15 10:36:25 +02:00
Daniel Schwabeneder 9c1b30f774 add legendtitle for pgfplotsx 2020-04-15 10:35:29 +02:00
Daniel Schwabeneder ebfe97a692 Merge pull request #2578 from isentropic/isen-dev
fixing default guidefontrotation direction for pyplot
2020-04-15 08:59:51 +02:00
Zh d8c4210e8f fixing default guidefontrotation direction for pyplot 2020-04-14 20:10:39 +09:00
Daniel Schwabeneder a9c0073aff Merge pull request #2571 from JuliaPlots/as/minorchecks
Check finiteness of upper and lower limit in minor ticks
2020-04-13 19:11:28 +02:00
Anshul Singhvi d04bb42f7b Check finiteness of upper and lower limit in minor ticks 2020-04-13 14:49:23 +05:30
Daniel Schwabeneder bf849d90a7 Merge pull request #2569 from daschw/pyplot
fix pyplot shape markers
2020-04-12 17:26:30 +02:00
Daniel Schwabeneder 95157004b6 fix pyplot shape markers 2020-04-12 17:20:46 +02:00
Daniel Schwabeneder c861664aea Merge pull request #2566 from daschw/release
bump version
2020-04-12 10:10:17 +02:00
Daniel Schwabeneder 72983102e3 bump version 2020-04-12 09:40:24 +02:00
Daniel Schwabeneder 693aaa51d7 Merge pull request #2565 from isentropic/isen-dev
guidefontrotation fix for pyplot
2020-04-12 09:39:17 +02:00
Zh 41a32f7782 guidefontrotation fix for pyplot 2020-04-12 16:01:52 +09:00
Daniel Schwabeneder 5d99c19de1 Merge pull request #2562 from daschw/plotly
fix scale_func for plotly
2020-04-11 17:03:44 +02:00
Daniel Schwabeneder d942660f91 fix scale_func for plotly 2020-04-11 17:01:23 +02:00
Josef Heinen c238d42e83 gr: added initial support for GR vector fonts 2020-04-11 09:50:59 +02:00
Josef Heinen 03a27994d5 Merge remote-tracking branch 'upstream/master' 2020-04-11 09:42:47 +02:00
Daniel Schwabeneder 1f7773ae4c Merge pull request #2561 from mbfstudios/pgfplotsx-fix-minor-ticks
Fix error when setting minor ticks. See #2560
2020-04-10 23:52:16 +02:00
Mats 4a5c82ec7f Fix error when setting minor ticks
Fix type error when specifying number of minor ticks for PGFPlotsX backend.
2020-04-10 22:44:31 +02:00
Daniel Schwabeneder 70f207f5cc Merge pull request #2557 from JuliaPlots/ds/rewrite
Use RecipesPipeline
2020-04-10 20:46:38 +02:00
Daniel Schwabeneder a1cd3d8f95 don't require julia 1.1 2020-04-10 19:47:09 +02:00
Daniel Schwabeneder 848b53e7d7 fix 3d plots 2020-04-10 19:45:11 +02:00
Daniel Schwabeneder 987456760c update precompiles 2020-04-10 19:22:45 +02:00
Daniel Schwabeneder 4c43f0ea2f be explicit about RecipesPipeline imports 2020-04-10 19:06:53 +02:00
Daniel Schwabeneder 7d69795364 require julia version 1.1 2020-04-10 16:14:59 +02:00
Daniel Schwabeneder 059985c91c revert travis test command 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder 9fe3428a55 update Project.toml 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder 92d8bd7836 move some recipes to recipepipeline 2020-04-10 16:09:49 +02:00
Sebastian Micluța-Câmpeanu 2a88ab7726 Rename to RecipesPipeline 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder d8f516a456 fix recipepipeline url in travis 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder 28ce7d7995 add recipepipeline manually in travis 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder b6139e2403 remove include 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder 80ec6f03b4 depend on RecipePipeline 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder 330739298c update and enable precompiles 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder bd9cd64e7a move remaining user recipes in series.jl to recipes.jl 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder f73b281996 extract the recipe pipeline into separate submodule 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder f180c666d2 rename some functions 2020-04-10 16:09:49 +02:00
Daniel Schwabeneder 908779f0c6 Merge pull request #2559 from daschw/gr-box
Fix axis arguments in PLOTS_DEFAULTS
2020-04-10 16:07:11 +02:00
Daniel Schwabeneder 345c3bc928 bump version 2020-04-10 16:05:06 +02:00
Daniel Schwabeneder bbdf1dcc0e fix axis agruments in PLOTS_DEFAULTS 2020-04-10 16:04:17 +02:00
Daniel Schwabeneder 5da2d85420 Merge pull request #2558 from daschw/gr-box
fix axis limits with GR framestyle border
2020-04-10 16:02:46 +02:00
Daniel Schwabeneder 5f524d039f fix axis limits with GR framestyle border 2020-04-10 15:15:36 +02:00
Daniel Schwabeneder 824d502c37 Merge pull request #2556 from daschw/gr-box
Gr box
2020-04-10 08:49:07 +02:00
Daniel Schwabeneder 36ff017c4f bump version 2020-04-10 08:31:03 +02:00
Daniel Schwabeneder 3ef9fe142c fix box and semi framestyle for GR 2020-04-10 08:05:36 +02:00
Daniel Schwabeneder 4bf0c496d8 Merge pull request #2553 from daschw/pgfplotsx
minorgrid, minorticks and framestyle zerolines fix for pgfplotsx
2020-04-09 21:37:52 +02:00
Daniel Schwabeneder 9bf4bda852 fix zerolines framestyle and don't draw tick marks for zerolines and grid framestyle 2020-04-09 18:13:28 +02:00
Daniel Schwabeneder fa7f10a6e9 implement minorgrid for pgfplotsx 2020-04-09 17:56:54 +02:00
Simon Christ 483e45db8a fix topleft 2020-04-09 16:11:02 +02:00
Simon Christ e6097f4534 all the legend positions (#2550)
* all the legend positions

* fix default
2020-04-09 14:18:15 +02:00
Daniel Schwabeneder 749f56084a Merge pull request #2551 from daschw/webio
fix webio error for plotlyjs
2020-04-09 10:57:32 +02:00
Daniel Schwabeneder 2fa4a63f02 fix webio error for plotlyjs 2020-04-09 10:55:33 +02:00
Michael Krabbe Borregaard 9b1249abce Merge pull request #2512 from JuliaPlots/compathelper/new_version/2020-03-28-15-04-44-941-2889142658
CompatHelper: bump compat for "StatsBase" to "0.33"
2020-04-08 14:24:28 +02:00
Simon Christ bbfc0bc77a Update Project.toml 2020-04-08 10:09:36 +02:00
Simon Christ 5a416e51d2 fix warning 2020-04-08 10:09:06 +02:00
Daniel Schwabeneder 67ba06cd77 Merge pull request #2548 from daschw/surface
Implement zerror and surface type recipe fixes
2020-04-07 17:50:32 +02:00
Daniel Schwabeneder aa61d681d9 bump version 2020-04-07 16:27:33 +02:00
Daniel Schwabeneder c8e6b967e5 add errorbar test example 2020-04-07 16:27:02 +02:00
Daniel Schwabeneder 819e91aa37 add zerror recipe 2020-04-07 16:06:12 +02:00
Daniel Schwabeneder 7f835cf7cb Merge pull request #2546 from isentropic/isen-dev
new aliases
2020-04-07 13:51:10 +02:00
Daniel Schwabeneder 43571e6d40 Merge pull request #2547 from Libbum/empty-fix
Fix 'isempy' check
2020-04-07 13:50:55 +02:00
Tim DuBois dd3666b6d3 Fix 'isempy' check 2020-04-07 13:33:16 +02:00
Simon Christ f98b191616 Layouting (#2486)
* distances very big

* better default

* respect margins

* remove failing example
2020-04-07 10:44:22 +02:00
Zhanibek 9fdcf0db74 new aliases 2020-04-07 17:23:05 +09:00
Daniel Schwabeneder 7abab41f2b add letter in surface type recipe 2020-04-07 09:52:32 +02:00
Josef Heinen a05d31a472 Merge remote-tracking branch 'upstream/master' 2020-04-07 09:21:52 +02:00
Daniel Schwabeneder 45569c0d4d Merge pull request #2544 from JeffBezanson/jb/optlevel1
use -O1 for code in Plots.jl
2020-04-06 22:59:32 +02:00
Simon Christ ca653c5bec Merge pull request #2541 from JuliaPlots/bbs/sani
special case LaTeXStrings in sanitation
2020-04-06 22:33:13 +02:00
Simon Christ 6c0539bac6 Merge pull request #2545 from JuliaPlots/bbs/annotations
fix annotations
2020-04-06 22:28:06 +02:00
Simon Christ 65c9a46c8d rely on require 2020-04-06 22:00:51 +02:00
Simon Christ 7bd97558dc fix annotations 2020-04-06 21:41:11 +02:00
Jeff Bezanson 6807f53a7e use -O1 for code in Plots.jl 2020-04-06 15:01:33 -04:00
Daniel Schwabeneder ae396ef576 Merge pull request #2543 from daschw/typesurface
allow type recipes for numbers in surfaces
2020-04-06 18:06:56 +02:00
Daniel Schwabeneder 3dd1c500cf fix ambiguity 2020-04-06 17:40:31 +02:00
Daniel Schwabeneder 59e4129194 skip maybestrings 2020-04-06 17:25:18 +02:00
Daniel Schwabeneder 54d441b62a bump versio 2020-04-06 17:10:27 +02:00
Daniel Schwabeneder f8d36363b4 allow type recipes for numbers in surfaces 2020-04-06 17:09:00 +02:00
Daniel Schwabeneder c32c7fda58 Merge pull request #2542 from harryscholes/splatted_annotations
Fix #2487
2020-04-06 16:32:44 +02:00
harryscholes 87b220e747 Fix #2487 2020-04-06 15:15:55 +01:00
Simon Christ 57a6f4303b special case LaTeXStrings 2020-04-06 15:44:06 +02:00
Daniel Schwabeneder 407456eac5 Merge pull request #2540 from daschw/link
Fix link
2020-04-06 14:44:31 +02:00
Daniel Schwabeneder f118d0cd35 bump version 2020-04-06 14:27:03 +02:00
Daniel Schwabeneder 1b0fb95583 add axes linking test 2020-04-06 14:26:22 +02:00
Daniel Schwabeneder 0daed24020 fix link 2020-04-06 14:12:46 +02:00
Daniel Schwabeneder 92eaa95127 Merge pull request #2539 from daschw/numbers
Allow type recipes for `Number`s, not for `Real`s
2020-04-06 10:35:55 +02:00
Daniel Schwabeneder a9d22cfb60 update comment 2020-04-06 10:11:00 +02:00
Daniel Schwabeneder b433b76a84 allow type recipes for numbers but not for reals 2020-04-06 09:56:47 +02:00
Daniel Schwabeneder 5ff0a419dd bump version 2020-04-06 09:23:14 +02:00
Daniel Schwabeneder 6b7be5ae12 apply type recipe for AbstractVector{<:Number} 2020-04-06 09:22:52 +02:00
Josef Heinen 9f7474476a Merge remote-tracking branch 'upstream/master' 2020-04-05 17:09:49 +02:00
Daniel Schwabeneder 8d1f44a0c5 Merge pull request #2533 from daschw/release
release 1.0 🎉
2020-04-05 15:39:21 +02:00
Daniel Schwabeneder 0c101e7d88 release 1.0 🎉 2020-04-05 13:10:36 +02:00
Josef Heinen 5f744dac68 Merge remote-tracking branch 'upstream/master' 2020-04-02 17:31:20 +02:00
Daniel Schwabeneder 6e3c218d89 Merge pull request #2456 from JuliaPlots/as/remove-tryrange
move tryrange to PlotUtils
2020-04-01 23:08:28 +02:00
Daniel Schwabeneder fe436cf54f Merge branch 'master' into as/remove-tryrange 2020-04-01 22:38:12 +02:00
Daniel Schwabeneder efdfec13ed Merge pull request #2524 from daschw/surface-type-recipe
Apply type recipes also for plots with surfaces
2020-04-01 19:45:10 +02:00
Daniel Schwabeneder 52522fde48 splat args in warn_on_recipe_alias 2020-04-01 19:27:41 +02:00
Daniel Schwabeneder 2a4882d92e Merge pull request #2525 from daschw/letter
fix type recipes for other backends
2020-04-01 18:16:55 +02:00
Daniel Schwabeneder bf98db67a3 fix type recipes for other backends 2020-04-01 17:32:31 +02:00
Daniel Schwabeneder 9f375aef09 fix single argument recipe 2020-04-01 14:24:34 +02:00
Daniel Schwabeneder 1faa858690 fix typo 2020-04-01 13:28:32 +02:00
Daniel Schwabeneder 5e7d2fb8ba add test example 2020-04-01 13:26:01 +02:00
Daniel Schwabeneder 974c6e5950 update precompile.jl 2020-04-01 13:13:06 +02:00
Daniel Schwabeneder e53f27c224 add axes function for surfaces 2020-04-01 12:54:40 +02:00
Daniel Schwabeneder d98262bc08 apply type recipes also for series 2020-04-01 12:08:32 +02:00
Daniel Schwabeneder 945874ca7d clean up series.jl 2020-03-31 22:13:27 +02:00
Daniel Schwabeneder 4c839837d3 Merge pull request #2522 from wfrgra/time_annotation
Fix for #2349. Converts TimeTypes in annotations to numbers to allow their placement
2020-03-31 12:06:41 +02:00
Michael Krabbe Borregaard f230dee72c Merge pull request #2521 from wfrgra/inline_legend
Add ability to place series legend labels next to final series datapoint in GR
2020-03-31 12:01:06 +02:00
wfrgra 11a859e83e have legend appear away from other data for any xflip and ymirror combination 2020-03-31 20:54:07 +11:00
Josef Heinen c3a9eb8866 Merge remote-tracking branch 'upstream/master' 2020-03-31 11:50:03 +02:00
wfrgra 9029e37671 Turn TimeType annotation locations to float fix errors 2020-03-31 20:05:21 +11:00
wfrgra 2a4b74d0ff renamed new legend setting from :label to :inline 2020-03-31 19:38:47 +11:00
Daniel Schwabeneder 0af25a2684 Merge pull request #2520 from daschw/any
remove some more `Any` in `series.jl`
2020-03-31 00:14:21 +02:00
Daniel Schwabeneder f62f4806c3 remove some more Any in series.jl 2020-03-30 23:49:29 +02:00
Daniel Schwabeneder 22496535d1 Merge pull request #2518 from JuliaPlots/pr-2273/travigd/patch-1
Patch PR 2273
2020-03-30 12:45:35 +02:00
Daniel Schwabeneder cb8df169c7 fix typo 2020-03-30 12:37:26 +02:00
Daniel Schwabeneder ac25972873 Merge pull request #2517 from daschw/unpin
Try not to pin FixedPointNumbers in tests.
2020-03-30 12:03:07 +02:00
Daniel Schwabeneder f1f9d807e6 don't pin FixedPointNumbers in tests 2020-03-30 11:39:50 +02:00
Daniel Schwabeneder 7d34b9bb46 fix julia versions in tests 2020-03-30 11:39:01 +02:00
wfrgra ae009cd53f Add ability to place legend label next to final datapoint in GR via legend=:label 2020-03-30 20:02:06 +11:00
Josef Heinen a6aa39e3cc Merge remote-tracking branch 'upstream/master' 2020-03-30 10:37:53 +02:00
Daniel Schwabeneder dd36d17fa6 Merge pull request #2516 from yha/animation-verbose
Show errors from ffmpeg by default, verbose flag
2020-03-29 23:42:20 +02:00
Daniel Schwabeneder 9843921503 Merge pull request #2515 from daschw/axis-args
fix `default` with axis args
2020-03-29 23:41:56 +02:00
Daniel Schwabeneder f6feb4b9eb Merge pull request #2511 from daschw/convert
Don't convert to any vector.
2020-03-29 23:41:31 +02:00
Daniel Schwabeneder 246632abca ok, don't use latex in tests 2020-03-29 17:39:56 +02:00
yha 5da33d7de8 Verbose flag for animations 2020-03-29 18:17:31 +03:00
Daniel Schwabeneder 9243de6ce9 add texlive-base 2020-03-29 17:00:57 +02:00
Daniel Schwabeneder a5b6c27bec add new test 2020-03-29 16:25:23 +02:00
Daniel Schwabeneder 076de033fd fix default with axis args 2020-03-29 15:39:44 +02:00
Josef Heinen 1c66f4bf90 Merge remote-tracking branch 'upstream/master' 2020-03-29 10:45:41 +02:00
Daniel Schwabeneder a1116f8e3b Merge pull request #2514 from daschw/coverage
Add CodeCov coverage
2020-03-28 23:16:16 +01:00
Daniel Schwabeneder d8c4c53567 ignore some files in coverage 2020-03-28 22:54:49 +01:00
Daniel Schwabeneder df9217e3b9 move /src/backends/template.jl to /templates/backends.jl for coverage 2020-03-28 21:52:03 +01:00
Daniel Schwabeneder c5aebf1309 add Codecov coverage 2020-03-28 21:00:06 +01:00
github-actions[bot] dc5ae4ef96 CompatHelper: bump compat for "StatsBase" to "0.33" 2020-03-28 15:04:45 +00:00
Daniel Schwabeneder d4ecf468b1 Merge pull request #2509 from JuliaPlots/revert-2506-compathelper/new_version/2020-03-26-20-05-28-345-1947541166
Revert "CompatHelper: bump compat for "StatsBase" to "0.33""
2020-03-28 15:17:38 +01:00
Daniel Schwabeneder aa2cee405a Revert "CompatHelper: bump compat for "StatsBase" to "0.33"" 2020-03-27 19:52:46 +01:00
Daniel Schwabeneder 5fc02f7f58 Merge pull request #2507 from daschw/no-aliases
Warn on aliases in recipes
2020-03-27 19:39:16 +01:00
Daniel Schwabeneder 3d5ae2fc3b update precompile.jl 2020-03-27 18:46:34 +01:00
Daniel Schwabeneder eecfdab590 don't convertToAnyVector 2020-03-27 18:39:18 +01:00
Daniel Schwabeneder e9ef5c852b fix magic axis args for recipes 2020-03-27 17:09:07 +01:00
Daniel Schwabeneder 8594926b67 move replaceAliases! back to preprocessArgs! 2020-03-27 16:31:06 +01:00
Daniel Schwabeneder 74a37b7817 Merge pull request #2503 from daschw/typerecipes
Make type recipes aware of current axes.
2020-03-27 16:18:30 +01:00
Daniel Schwabeneder 13b47a4ee6 Merge pull request #2499 from daschw/tests
test on 1.4 and update version
2020-03-27 16:18:06 +01:00
Daniel Schwabeneder 0c2d80841c warn on recipe aliases 2020-03-27 13:44:39 +01:00
Michael Krabbe Borregaard d1465ff401 Update Project.toml 2020-03-27 07:07:13 +01:00
Michael Krabbe Borregaard ee0a428dbf Merge pull request #2506 from JuliaPlots/compathelper/new_version/2020-03-26-20-05-28-345-1947541166
CompatHelper: bump compat for "StatsBase" to "0.33"
2020-03-27 07:06:52 +01:00
github-actions[bot] f808a9fae5 CompatHelper: bump compat for "StatsBase" to "0.33" 2020-03-26 20:05:29 +00:00
Daniel Schwabeneder 932d0da73d move replaceAliases! out of recipe processing 2020-03-26 15:09:31 +01:00
Josef Heinen d9dba5091b Merge remote-tracking branch 'upstream/master' 2020-03-26 12:57:48 +01:00
Daniel Schwabeneder d9fcfbe80e Merge pull request #2504 from nilshg/master
Add LinearAlgebra to make example with `norm` work
2020-03-25 20:27:22 +01:00
Nils ca3baf3881 Add LinearAlgebra to make example with norm work 2020-03-25 18:38:20 +00:00
Simon Christ c37829eafb Update appveyor.yml 2020-03-25 16:55:53 +01:00
Simon Christ d9a48ac7de Update .travis.yml 2020-03-25 16:36:04 +01:00
Daniel Schwabeneder c684213106 fix AbstractArray{<:Missing} 2020-03-25 15:04:39 +01:00
Simon Christ 5318352929 Update appveyor.yml 2020-03-25 14:50:42 +01:00
Simon Christ dd3216489b resolve conditionally 2020-03-25 14:48:57 +01:00
Daniel Schwabeneder 17db6c01b4 keep no-letter version of _preprocess_axis_args! 2020-03-25 12:51:49 +01:00
Simon Christ 8efd0f00c4 fix pin 2020-03-25 10:45:46 +01:00
Daniel Schwabeneder b258c9d8c8 Merge pull request #2498 from daschw/copy
Don't copy input data
2020-03-24 22:38:28 +01:00
Josef Heinen c94746f3f2 Merge remote-tracking branch 'upstream/master' 2020-03-24 10:41:52 +01:00
Daniel Schwabeneder b0548ffc14 make type recipe aware of current axis 2020-03-23 23:12:13 +01:00
Daniel Schwabeneder 5194e49b4c test on 1.4 and update version 2020-03-22 18:31:08 +01:00
Daniel Schwabeneder 2083af9257 Merge pull request #2483 from daschw/vector-type-recipe
Various type recipe fixes
2020-03-22 18:00:18 +01:00
Daniel Schwabeneder 6cd8b8ff8d Merge pull request #2497 from keorn/patch-1
Allow for Plotly JSON saving
2020-03-22 17:59:38 +01:00
Daniel Schwabeneder ca9e47bc4d update precompile files 2020-03-22 10:45:02 +01:00
Josef Heinen b6dddcb15b Merge remote-tracking branch 'upstream/master' 2020-03-22 09:10:11 +01:00
Daniel Schwabeneder ed8e72c1bc don't copy series data 2020-03-21 23:02:14 +01:00
Daniel Schwabeneder 4e5e9986a1 reimplement push! (copy if series shares data with other series) 2020-03-21 22:53:58 +01:00
Daniel Schwabeneder 55bb6fda1b uniform length 4 indentation in utils.jl 2020-03-21 22:53:58 +01:00
keorn c50a5bf123 allow plotly json saving 2020-03-21 14:41:56 +01:00
Daniel Schwabeneder 2702dc6654 Merge pull request #2495 from ma-laforge/hdf5
Add simple testset for HDF5 plots.
2020-03-21 12:52:08 +01:00
MA Laforge 3fe013cd24 Add simple testset for HDF5 plots. 2020-03-20 18:03:00 -04:00
Michael Krabbe Borregaard fba6802066 Merge pull request #2492 from ma-laforge/hdf5
Fix broken hdf5 "backend".
2020-03-20 10:40:28 +01:00
MA Laforge fa8c1e3967 Fix broken hdf5 "backend".
Add support for new Attr structure.
2020-03-19 17:18:07 -04:00
Simon Christ 23b191231c more positions (#2490)
* tick label styling

* add title_location

* add legend positions

* fix commata
2020-03-19 11:57:10 +01:00
Josef Heinen 2849082f1f Merge remote-tracking branch 'upstream/master' 2020-03-19 10:35:14 +01:00
Simon Christ aa5d514d57 Merge pull request #2489 from JuliaPlots/bbs/grid-lines
put gridlines in the back
2020-03-19 01:00:41 +01:00
Daniel Schwabeneder 3fb45764b2 Merge pull request #2471 from daschw/staticarrays
fix StaticArray plotting
2020-03-18 23:07:32 +01:00
Simon Christ fcff639d0c put gridlines in the back 2020-03-18 17:05:07 +01:00
Daniel Schwabeneder 96181b8c48 only apply axis attributes in type recipes to current axis 2020-03-18 16:27:08 +01:00
Josef Heinen 038cf2b133 Merge remote-tracking branch 'upstream/master' 2020-03-18 16:16:21 +01:00
Simon Christ 62290a2a24 Fix appveyor.yml (#2484)
* Update appveyor.yml

* change parantheses

* escape inner quotation marks

* another try

* Update appveyor.yml
2020-03-18 13:49:06 +01:00
Daniel Schwabeneder bfafe91842 Merge pull request #2485 from harryscholes/fix-annotate-docstring
Fix annotate docstring
2020-03-18 10:55:05 +01:00
harryscholes cbaa8fca56 Fix annotate docstring 2020-03-18 09:53:09 +00:00
Josef Heinen a9e2a5f639 Merge remote-tracking branch 'upstream/master' 2020-03-18 08:38:35 +01:00
Daniel Schwabeneder f606f083a4 fix for vectors containing missing 2020-03-18 01:43:42 +01:00
Daniel Schwabeneder b60007cde9 update comment 2020-03-18 01:28:29 +01:00
Daniel Schwabeneder 89e271bd91 reverse type recipe order 2020-03-18 01:23:46 +01:00
Daniel Schwabeneder 3ed50bbf0f make type recipes work for vectors 2020-03-18 00:22:55 +01:00
Simon Christ 0d210843e7 add example 20 2020-03-17 21:30:48 +01:00
Simon Christ 08bed07632 Merge pull request #2476 from JuliaPlots/bbs/string-sani
sanitize strings
2020-03-17 17:38:17 +01:00
Daniel Schwabeneder c47ab69383 Merge pull request #2477 from JuliaPlots/bbs/fix-tests
pin FixedPointNumbers in tests
2020-03-17 17:29:27 +01:00
Simon Christ eb942352ce add pin to appveyor 2020-03-17 16:46:08 +01:00
Simon Christ 00379097cf change compat 2020-03-17 15:34:03 +01:00
Simon Christ 16b568840d pin FixedPointNumbers 2020-03-17 15:32:54 +01:00
Simon Christ c1dae9e9d9 sanitize strings 2020-03-17 15:26:35 +01:00
Simon Christ 8d8921fd08 bump version 2020-03-17 11:25:38 +01:00
Simon Christ d04d651e55 Merge pull request #2473 from BeastyBlacksmith/pgfplotsx
another legends fix
2020-03-17 10:17:07 +01:00
Simon Christ c771739b1b Merge pull request #2474 from JuliaPlots/bbs/markershape
reduce markershape
2020-03-17 10:01:12 +01:00
Daniel Schwabeneder a800f8d4d9 add test example 2020-03-16 22:53:33 +01:00
Daniel Schwabeneder 4b5ec3ad07 add prepareSeriesData method for AbstractRanges 2020-03-16 22:20:42 +01:00
Daniel Schwabeneder 35c68c8890 only copy for immutables 2020-03-16 16:33:25 +01:00
Simon Christ 5005120f5e change scaling factor 2020-03-16 14:46:26 +01:00
Simon Christ 7b68e7985f another legends fix 2020-03-16 14:29:49 +01:00
Simon Christ 871c7d86c7 add ticks color (#2472)
* add ticks color

* hotfix
2020-03-16 14:28:42 +01:00
Daniel Schwabeneder 79614ffcec fix StaticArray plotting 2020-03-16 09:17:15 +01:00
Michael Krabbe Borregaard 6821148e8e cap fixedpointnumbers to 0.7 2020-03-15 09:30:07 +01:00
Simon Christ 3255c5a122 Allow legend vectors (#2449)
* allow vector legends

* format file pgfplotsx.jl

* handle unmatching vectors

* add test

* format test_pgfplotsx.jl
2020-03-14 15:44:07 +01:00
Anshul Singhvi edcaab3c56 bump the PlotUtils compat
this will only work after a release
2020-03-14 11:03:23 +05:30
Josef Heinen c9421be214 Merge pull request #10 from jheinen/compathelper/new_version/2020-03-13-16-05-59-376-460145100
CompatHelper: bump compat for "GR" to "0.48"
2020-03-13 17:07:18 +01:00
github-actions[bot] 9fc707bd94 CompatHelper: bump compat for "GR" to "0.48" 2020-03-13 16:06:00 +00:00
Josef Heinen bb9d36eb7a Update Project.toml 2020-03-13 16:40:49 +01:00
Anshul Singhvi 503d741ea6 move tryrange to PlotUtils 2020-03-13 19:26:45 +05:30
Simon Christ 50984b3bd2 bump version 2020-03-13 14:41:56 +01:00
Simon Christ 7520c1dced Merge pull request #2454 from JuliaPlots/bbs/check-examples
examples to skip
2020-03-13 14:19:10 +01:00
Simon Christ bfd468fc3a Merge pull request #2453 from JuliaPlots/bbs/fix-rib-example
fix ribbon-example
2020-03-13 14:18:45 +01:00
Simon Christ 3ab0078818 add skipping examples 2020-03-13 13:53:12 +01:00
Simon Christ 1fc587559c fix ribbon-example 2020-03-13 13:50:26 +01:00
Simon Christ 4821e04145 Merge pull request #2450 from BeastyBlacksmith/pgfplotsx
recreate plot if it was shown
2020-03-12 13:13:01 +01:00
Simon Christ bb8e62769f recreate plot if it was shown 2020-03-11 16:30:51 +01:00
Josef Heinen 236cc4671e Merge remote-tracking branch 'upstream/master' 2020-03-10 12:02:49 +01:00
Yuval 15dc61feb5 Merge pull request #2444 from yha/tests-windows
Allow image comparison tests on Windows.
2020-03-09 01:39:23 +02:00
yha d0d72242cb Allow image comparison tests on Windows. 2020-03-08 18:24:39 +02:00
Josef Heinen a301366ef6 Merge remote-tracking branch 'upstream/master' 2020-03-07 10:53:02 +01:00
Daniel Schwabeneder bece5a2620 Merge pull request #2442 from JuliaPlots/release-0.29.6
Release a new version
2020-03-06 21:33:34 +01:00
Anshul Singhvi d972702d3e Update Project.toml 2020-03-06 15:01:42 -05:00
Simon Christ d08b33e696 Merge pull request #2438 from BeastyBlacksmith/pgfplotsx
Fix twinx()
2020-03-06 12:41:44 +01:00
Simon Christ da60b47af5 format backend code 2020-03-06 12:17:42 +01:00
Simon Christ 0ac63857c4 fix twinx 2020-03-06 12:17:42 +01:00
Simon Christ d08422811e format file 2020-03-06 12:17:42 +01:00
Simon Christ d5788fc1e7 Allow legend for surfaces (#2439)
* allow legends for surfaces

* remove unnecessary checks

* redo necessary checks
2020-03-06 12:12:01 +01:00
Daniel Schwabeneder 6da5bb8822 Merge pull request #2428 from JuliaPlots/compathelper/new_version/2020-03-02-11-05-30-705-1878394197
CompatHelper: bump compat for "GeometryTypes" to "0.8"
2020-03-05 14:00:17 +01:00
Daniel Schwabeneder a0a3b42872 Merge pull request #2437 from daschw/release
bump version
2020-03-05 13:59:48 +01:00
Daniel Schwabeneder caf0aa098a bump version 2020-03-05 09:31:11 +01:00
Simon Christ 55ea8b4a70 New recipe: lens (#2372)
* create recipe, add box and line

* add subplot

* improve linking

* add ghost subplot

* draw line only if inset is in subplot

* sp_bbox -> inset_bbox

* better linking

* add lens! shorthand

* apply keywords only to inset

* check bbox type

* add lens example

* fix typo and stray *

* move legend out of the plot

* move lens example down
2020-03-05 03:03:21 +01:00
Simon Christ 25c53f0e52 Merge pull request #2435 from BeastyBlacksmith/pgfplotsx
fix legends with skipped entries
2020-03-04 21:49:45 +01:00
Daniel Schwabeneder 7c3938f793 Merge pull request #2436 from daschw/gr-image
fix gr image lims
2020-03-04 20:27:29 +01:00
Daniel Schwabeneder 20a10fcf7d fix gr image lims 2020-03-04 19:28:43 +01:00
Simon Christ 6ee49f8430 fix legends with skipped entries 2020-03-04 18:18:06 +01:00
Daniel Schwabeneder 0768c8a658 Merge pull request #2433 from daschw/cache
cache artifacts in CI services
2020-03-04 17:26:39 +01:00
Michael Krabbe Borregaard 89b2be4ecb Merge pull request #2434 from BeastyBlacksmith/bbs/format
Format changes to examples.jl and recipes.jl
2020-03-04 16:23:28 +01:00
Simon Christ 369d510b3e format examples.jl 2020-03-04 15:55:45 +01:00
Simon Christ d1dbb1bf2a format file 2020-03-04 15:55:22 +01:00
Daniel Schwabeneder ccf726ddcb cache artifacts in CI services 2020-03-04 12:53:24 +01:00
github-actions[bot] 5b2cd1b8ee CompatHelper: bump compat for "GeometryTypes" to "0.8" 2020-03-02 11:05:31 +00:00
Daniel Schwabeneder 16b8d01b88 Merge pull request #2427 from daschw/ratio
use equal aspect ratio by default for images
2020-03-01 08:52:41 +01:00
Daniel Schwabeneder 8d7b07eab3 bump version 2020-03-01 01:57:22 +01:00
Daniel Schwabeneder 599d6a2ec8 add commented code to update refernce images 2020-03-01 01:56:46 +01:00
Daniel Schwabeneder 345f5654a3 use equal aspect ratio by default for images 2020-03-01 01:37:34 +01:00
Daniel Schwabeneder 77e04c45db Merge pull request #2426 from daschw/gr-scatter
Fix shape markers for GR
2020-02-29 21:38:49 +01:00
Daniel Schwabeneder ad4d7214e4 increase test accuracy 2020-02-29 21:00:51 +01:00
Daniel Schwabeneder 4051d016b9 Fix Shape markers for GR 2020-02-29 20:44:56 +01:00
Josef Heinen c1959cb993 Merge remote-tracking branch 'upstream/master' 2020-02-29 12:16:47 +01:00
Daniel Schwabeneder 8610a82a88 Merge pull request #2421 from daschw/release
bump version
2020-02-28 09:29:44 +01:00
Daniel Schwabeneder 4832d870dd bump version 2020-02-28 09:27:35 +01:00
Daniel Schwabeneder ace915fbfb Merge pull request #2420 from JuliaPlots/compathelper/new_version/2020-02-27-15-06-02-666-763792840
CompatHelper: bump compat for "GR" to "0.47"
2020-02-28 09:23:52 +01:00
Josef Heinen c6593423f5 Update Project.toml 2020-02-28 09:09:54 +01:00
Josef Heinen ecf71a43d3 Merge pull request #8 from jheinen/compathelper/new_version/2020-02-27-15-07-43-107-2999057488
CompatHelper: bump compat for "GR" to "0.47"
2020-02-27 17:51:30 +01:00
github-actions[bot] b65dfc6767 CompatHelper: bump compat for "GR" to "0.47" 2020-02-27 15:07:44 +00:00
github-actions[bot] f69daa8cfe CompatHelper: bump compat for "GR" to "0.47" 2020-02-27 15:06:03 +00:00
Travis DePrato fa6f0a6b00 Fix PlotlyJS integration with WebIO
Fixes #2272.
2019-11-26 12:55:27 -05:00
38 changed files with 3360 additions and 3106 deletions
+2
View File
@@ -8,3 +8,5 @@ deps/plotly-latest.min.js
deps/build.log
deps/deps.jl
Manifest.toml
dev/
test/tmpplotsave.hdf5
+11 -1
View File
@@ -4,8 +4,8 @@ os:
- linux
# - osx
julia:
- 1.0
- 1
- 1.3
- nightly
matrix:
@@ -20,6 +20,13 @@ addons:
- xauth
- xvfb
env:
- GKS_ENCODING="utf8"
cache:
directories:
- $HOME/.julia/artifacts
sudo: required
before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pwd ; fi
@@ -28,6 +35,9 @@ before_install:
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
+13 -9
View File
@@ -1,7 +1,7 @@
name = "Plots"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
author = ["Tom Breloff (@tbreloff)"]
version = "0.29.2"
version = "1.0.11"
[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
@@ -22,6 +22,7 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
RecipesPipeline = "01d81517-befc-4cb6-b9ec-a95719d0359c"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
Showoff = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
@@ -34,36 +35,39 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
Contour = "0.5"
FFMPEG = "0.2, 0.3"
FixedPointNumbers = "0.6, 0.7, 0.8"
GR = "0.46"
GeometryTypes = "0.7"
GR = "0.46, 0.47, 0.48"
GeometryTypes = "0.7, 0.8"
JSON = "0.21"
Measures = "0.3"
NaNMath = "0.3"
PGFPlotsX = "1.2.0"
PlotThemes = "1"
PlotUtils = "0.6.1"
RecipesBase = "0.8"
PlotUtils = "0.6.5"
RecipesBase = "1"
RecipesPipeline = "0.1.1"
Reexport = "0.2"
Requires = "0.5, 1.0"
Requires = "0.5, 1"
Showoff = "0.3.1"
StatsBase = "0.32"
StatsBase = "0.32, 0.33"
julia = "1"
[extras]
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"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
PGFPlotsX = "8314cec4-20b6-5062-9cdb-752b83310925"
RDatasets = "ce6b1742-4840-55fa-b093-852dadbb1d8b"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92"
[targets]
test = ["FileIO", "GeometryTypes", "Gtk", "ImageMagick", "Images", "LaTeXStrings", "LibGit2", "PGFPlotsX", "Random", "RDatasets", "StatsPlots", "Test", "UnicodePlots", "VisualRegressionTests"]
test = ["FileIO", "GeometryTypes", "Gtk", "ImageMagick", "Images", "LibGit2", "OffsetArrays", "PGFPlotsX", "HDF5", "Random", "RDatasets", "StaticArrays", "StatsPlots", "Test", "UnicodePlots", "VisualRegressionTests"]
+1
View File
@@ -20,6 +20,7 @@
[![][pkgeval-img]][pkgeval-url]
[![][gitter-img]][gitter-url]
[![][docs-img]][docs-url]
[![Codecov](https://codecov.io/gh/JuliaPlots/Plots.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaPlots/Plots.jl)
#### Created by Tom Breloff (@tbreloff)
+5 -3
View File
@@ -1,8 +1,7 @@
environment:
matrix:
# - julia_version: 0.7
- julia_version: 1.0
- julia_version: 1
- julia_version: 1.3
- julia_version: nightly
platform:
@@ -20,6 +19,9 @@ branches:
- master
- /release-.*/
cache:
- '%USERPROFILE%\.julia\artifacts'
notifications:
- provider: Email
on_build_success: false
@@ -30,7 +32,7 @@ install:
- ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1"))
build_script:
- echo "%JL_BUILD_SCRIPT%"
- echo "%JL_TEST_SCRIPT%"
- C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%"
test_script:
+10
View File
@@ -0,0 +1,10 @@
ignore:
- "src/backends/inspectdr.jl"
- "src/backends/orca.jl"
- "src/backends/pgfplots.jl"
- "src/backends/plotly.jl"
- "src/backends/plotlyjs.jl"
- "src/backends/pyplot.jl"
- "src/backends/web.jl"
- "src/fileio.jl"
- "src/ijulia.jl"
+4 -2
View File
@@ -26,13 +26,15 @@
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 log_path begin
SnoopCompile.@snoopc project_flag log_path begin
using Plots
Plots.test_examples(:gr, disp=true)
Plots.test_examples(:gr)
Plots.test_examples(:plotly, skip = Plots._backend_skips[:plotly])
end
# precompile calls containing the following strings are dropped
+24 -1
View File
@@ -1,5 +1,9 @@
module Plots
if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@optlevel"))
@eval Base.Experimental.@optlevel 1
end
const _current_plots_version = VersionNumber(split(first(filter(line -> occursin("version", line), readlines(normpath(@__DIR__, "..", "Project.toml")))), "\"")[2])
using Reexport
@@ -163,6 +167,26 @@ using .PlotMeasures
import .PlotMeasures: Length, AbsoluteLength, Measure, width, height
# ---------------------------------------------------------
import RecipesPipeline
import RecipesPipeline: SliceIt,
DefaultsDict,
Formatted,
AbstractSurface,
Surface,
Volume,
is3d,
is_surface,
needs_3d_axes,
group_as_matrix, # for StatsPlots
reset_kw!,
pop_kw!,
scale_func,
inverse_scale_func,
unzip,
dateformatter,
datetimeformatter,
timeformatter
include("types.jl")
include("utils.jl")
include("components.jl")
@@ -171,7 +195,6 @@ include("args.jl")
include("themes.jl")
include("plot.jl")
include("pipeline.jl")
include("series.jl")
include("layouts.jl")
include("subplots.jl")
include("recipes.jl")
+8 -4
View File
@@ -71,6 +71,7 @@ function buildanimation(anim::Animation, fn::AbstractString,
is_animated_gif::Bool=true;
fps::Real = 20, loop::Integer = 0,
variable_palette::Bool=false,
verbose=false,
show_msg::Bool=true)
if length(anim.frames) == 0
throw(ArgumentError("Cannot build empty animations"))
@@ -79,20 +80,23 @@ function buildanimation(anim::Animation, fn::AbstractString,
fn = abspath(expanduser(fn))
animdir = anim.dir
framerate = ffmpeg_framerate(fps)
verbose_level = (verbose isa Int ? verbose :
verbose ? 32 # "info"
: 16) # "error"
if is_animated_gif
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 0 -framerate $framerate -loop $loop -i $(animdir)/%06d.png -lavfi "$palette" -y $fn`)
ffmpeg_exe(`-v $verbose_level -framerate $framerate -loop $loop -i $(animdir)/%06d.png -lavfi "$palette" -y $fn`)
else
# generate a colorpalette first so ffmpeg does not have to guess it
ffmpeg_exe(`-v 0 -i $(animdir)/%06d.png -vf "palettegen=stats_mode=diff" -y "$(animdir)/palette.bmp"`)
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 0 -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 -loop $loop -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -y $fn`)
end
else
ffmpeg_exe(`-v 0 -framerate $framerate -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`)
ffmpeg_exe(`-v $verbose_level -framerate $framerate -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`)
end
show_msg && @info("Saved animation to ", fn)
+1 -1
View File
@@ -89,7 +89,7 @@ const _arg_desc = KW(
: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 Symbol (legend position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` (note: only some may be supported in each backend)",
:legend => "Bool (show the legend?) or Symbol (legend position). 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",
+75 -124
View File
@@ -86,13 +86,10 @@ const _surface_like = [:contour, :contourf, :contour3d, :heatmap, :surface, :wir
like_histogram(seriestype::Symbol) = seriestype in _histogram_like
like_line(seriestype::Symbol) = seriestype in _line_like
like_surface(seriestype::Symbol) = seriestype in _surface_like
like_surface(seriestype::Symbol) = RecipesPipeline.is_surface(seriestype)
is3d(seriestype::Symbol) = seriestype in _3dTypes
is3d(series::Series) = is3d(series.plotattributes)
is3d(plotattributes::AKW) = trueOrAllTrue(is3d, Symbol(plotattributes[:seriestype]))
is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d"
RecipesPipeline.is3d(series::Series) = RecipesPipeline.is3d(series.plotattributes)
RecipesPipeline.is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d"
ispolar(sp::Subplot) = string(sp.attr[:projection]) == "polar"
ispolar(series::Series) = ispolar(series.plotattributes[:subplot])
@@ -267,6 +264,7 @@ const _series_defaults = KW(
: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...)`
@@ -345,7 +343,7 @@ const _subplot_defaults = KW(
:legendtitlefontcolor => :match,
:annotations => [], # annotation tuples... list of (x,y,annotation)
:projection => :none, # can also be :polar or :3d
:aspect_ratio => :none, # choose from :none or :equal
:aspect_ratio => :auto, # choose from :none or :equal
:margin => 1mm,
:left_margin => :match,
:top_margin => :match,
@@ -450,15 +448,33 @@ const _initial_fontsizes = Dict(:titlefontsize => _subplot_defaults[:titlefonts
:tickfontsize => _axis_defaults[:tickfontsize],
:guidefontsize => _axis_defaults[:guidefontsize])
const _all_args = sort(collect(union(map(keys, _all_defaults)...)))
const _internal_args =
[:plot_object, :series_plotindex, :markershape_to_add, :letter, :idxfilter]
is_subplot_attr(k) = haskey(_subplot_defaults, k)
is_series_attr(k) = haskey(_series_defaults, k)
#is_axis_attr(k) = haskey(_axis_defaults_byletter, k)
is_axis_attr(k) = haskey(_axis_defaults, Symbol(chop(string(k); head=1, tail=0)))
is_axis_attr_noletter(k) = haskey(_axis_defaults, k)
const _axis_args = sort(union(collect(keys(_axis_defaults))))
const _series_args = sort(union(collect(keys(_series_defaults))))
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_series_args = [:line, :marker, :fill]
const _all_axis_args = sort(union([_axis_args; _magic_axis_args]))
const _all_subplot_args = sort(union([_subplot_args; _magic_subplot_args]))
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])
is_subplot_attr(k) = k in _all_subplot_args
is_series_attr(k) = k in _all_series_args
is_axis_attr(k) = Symbol(chop(string(k); head=1, tail=0)) in _all_axis_args
is_axis_attr_noletter(k) = k in _all_axis_args
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)
# -----------------------------------------------------------------------------
@@ -550,17 +566,18 @@ add_aliases(:bins, :bin, :nbin, :nbins, :nb)
add_aliases(:ribbon, :rib)
add_aliases(:annotations, :ann, :anns, :annotate, :annotation)
add_aliases(:xguide, :xlabel, :xlab, :xl)
add_aliases(:xlims, :xlim, :xlimit, :xlimits)
add_aliases(:xlims, :xlim, :xlimit, :xlimits, :xrange)
add_aliases(:xticks, :xtick)
add_aliases(:xrotation, :xrot, :xr)
add_aliases(:yguide, :ylabel, :ylab, :yl)
add_aliases(:ylims, :ylim, :ylimit, :ylimits)
add_aliases(:ylims, :ylim, :ylimit, :ylimits, :yrange)
add_aliases(:yticks, :ytick)
add_aliases(:yrotation, :yrot, :yr)
add_aliases(:zguide, :zlabel, :zlab, :zl)
add_aliases(:zlims, :zlim, :zlimit, :zlimits)
add_aliases(:zticks, :ztick)
add_aliases(:zrotation, :zrot, :zr)
add_aliases(:guidefontsize, :labelfontsize)
add_aliases(:fill_z, :fillz, :fz, :surfacecolor, :surfacecolour, :sc, :surfcolor, :surfcolour)
add_aliases(:legend, :leg, :key)
add_aliases(:legendtitle, :legend_title, :labeltitle, :label_title, :leg_title, :key_title)
@@ -575,6 +592,7 @@ add_aliases(:color_palette, :palette)
add_aliases(:overwrite_figure, :clf, :clearfig, :overwrite, :reuse)
add_aliases(:xerror, :xerr, :xerrorbar)
add_aliases(:yerror, :yerr, :yerrorbar, :err, :errorbar)
add_aliases(:zerror, :zerr, :zerrorbar)
add_aliases(:quiver, :velocity, :quiver2d, :gradient, :vectorfield)
add_aliases(:normalize, :norm, :normed, :normalized)
add_aliases(:show_empty_bins, :showemptybins, :showempty, :show_empty)
@@ -638,6 +656,7 @@ function default(k::Symbol)
letter, key = axis_k
return _axis_defaults_byletter[letter][key]
end
k == :letter && return k # for type recipe processing
k in _suppress_warnings || error("Unknown key: ", k)
end
@@ -663,7 +682,7 @@ end
function default(; kw...)
kw = KW(kw)
preprocessArgs!(kw)
RecipesPipeline.preprocess_attributes!(kw)
for (k,v) in kw
default(k, v)
end
@@ -916,27 +935,11 @@ function _add_markershape(plotattributes::AKW)
end
"Handle all preprocessing of args... break out colors/sizes/etc and replace aliases."
function preprocessArgs!(plotattributes::AKW)
function RecipesPipeline.preprocess_attributes!(plotattributes::AKW)
replaceAliases!(plotattributes, _keyAliases)
# clear all axis stuff
# if haskey(plotattributes, :axis) && plotattributes[:axis] in (:none, nothing, false)
# plotattributes[:ticks] = nothing
# plotattributes[:foreground_color_border] = RGBA(0,0,0,0)
# plotattributes[:foreground_color_axis] = RGBA(0,0,0,0)
# plotattributes[:grid] = false
# delete!(plotattributes, :axis)
# end
# for letter in (:x, :y, :z)
# asym = Symbol(letter, :axis)
# if haskey(plotattributes, asym) || plotattributes[asym] in (:none, nothing, false)
# plotattributes[Symbol(letter, :ticks)] = nothing
# plotattributes[Symbol(letter, :foreground_color_border)] = RGBA(0,0,0,0)
# end
# end
# handle axis args common to all axis
args = pop_kw!(plotattributes, :axis, ())
args = RecipesPipeline.pop_kw!(plotattributes, :axis, ())
for arg in wraptuple(args)
for letter in (:x, :y, :z)
process_axis_arg!(plotattributes, arg, letter)
@@ -945,7 +948,7 @@ function preprocessArgs!(plotattributes::AKW)
# handle axis args
for letter in (:x, :y, :z)
asym = Symbol(letter, :axis)
args = pop_kw!(plotattributes, asym, ())
args = RecipesPipeline.pop_kw!(plotattributes, asym, ())
if !(typeof(args) <: Axis)
for arg in wraptuple(args)
process_axis_arg!(plotattributes, arg, letter)
@@ -963,7 +966,7 @@ function preprocessArgs!(plotattributes::AKW)
end
# handle grid args common to all axes
args = pop_kw!(plotattributes, :grid, ())
args = RecipesPipeline.pop_kw!(plotattributes, :grid, ())
for arg in wraptuple(args)
for letter in (:x, :y, :z)
processGridArg!(plotattributes, arg, letter)
@@ -972,13 +975,13 @@ function preprocessArgs!(plotattributes::AKW)
# handle individual axes grid args
for letter in (:x, :y, :z)
gridsym = Symbol(letter, :grid)
args = pop_kw!(plotattributes, gridsym, ())
args = RecipesPipeline.pop_kw!(plotattributes, gridsym, ())
for arg in wraptuple(args)
processGridArg!(plotattributes, arg, letter)
end
end
# handle minor grid args common to all axes
args = pop_kw!(plotattributes, :minorgrid, ())
args = RecipesPipeline.pop_kw!(plotattributes, :minorgrid, ())
for arg in wraptuple(args)
for letter in (:x, :y, :z)
processMinorGridArg!(plotattributes, arg, letter)
@@ -987,21 +990,14 @@ function preprocessArgs!(plotattributes::AKW)
# handle individual axes grid args
for letter in (:x, :y, :z)
gridsym = Symbol(letter, :minorgrid)
args = pop_kw!(plotattributes, gridsym, ())
args = RecipesPipeline.pop_kw!(plotattributes, gridsym, ())
for arg in wraptuple(args)
processMinorGridArg!(plotattributes, arg, letter)
end
end
# fonts
for fontname in (:titlefont, :legendfont, :legendtitlefont)
args = pop_kw!(plotattributes, fontname, ())
for arg in wraptuple(args)
processFontArg!(plotattributes, fontname, arg)
end
end
# handle font args common to all axes
for fontname in (:tickfont, :guidefont)
args = pop_kw!(plotattributes, fontname, ())
args = RecipesPipeline.pop_kw!(plotattributes, fontname, ())
for arg in wraptuple(args)
for letter in (:x, :y, :z)
processFontArg!(plotattributes, Symbol(letter, fontname), arg)
@@ -1011,15 +1007,35 @@ function preprocessArgs!(plotattributes::AKW)
# handle individual axes font args
for letter in (:x, :y, :z)
for fontname in (:tickfont, :guidefont)
args = pop_kw!(plotattributes, Symbol(letter, fontname), ())
args = RecipesPipeline.pop_kw!(plotattributes, Symbol(letter, fontname), ())
for arg in wraptuple(args)
processFontArg!(plotattributes, Symbol(letter, fontname), arg)
end
end
end
# handle axes args
for k in _axis_args
if haskey(plotattributes, k) && k !== :link
v = plotattributes[k]
for letter in (:x, :y, :z)
lk = Symbol(letter, k)
if !is_explicit(plotattributes, lk)
plotattributes[lk] = v
end
end
end
end
# fonts
for fontname in (:titlefont, :legendfont, :legendtitlefont)
args = RecipesPipeline.pop_kw!(plotattributes, fontname, ())
for arg in wraptuple(args)
processFontArg!(plotattributes, fontname, arg)
end
end
# handle line args
for arg in wraptuple(pop_kw!(plotattributes, :line, ()))
for arg in wraptuple(RecipesPipeline.pop_kw!(plotattributes, :line, ()))
processLineArg(plotattributes, arg)
end
@@ -1033,7 +1049,7 @@ function preprocessArgs!(plotattributes::AKW)
processMarkerArg(plotattributes, arg)
anymarker = true
end
reset_kw!(plotattributes, :marker)
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
@@ -1047,7 +1063,7 @@ function preprocessArgs!(plotattributes::AKW)
for arg in wraptuple(get(plotattributes, :fill, ()))
processFillArg(plotattributes, arg)
end
reset_kw!(plotattributes, :fill)
RecipesPipeline.reset_kw!(plotattributes, :fill)
# handle series annotations
if haskey(plotattributes, :series_annotations)
@@ -1096,75 +1112,13 @@ function preprocessArgs!(plotattributes::AKW)
return
end
# -----------------------------------------------------------------------------
"A special type that will break up incoming data into groups, and allow for easier creation of grouped plots"
mutable struct GroupBy
groupLabels::Vector # length == numGroups
groupIds::Vector{Vector{Int}} # list of indices for each group
end
# this is when given a vector-type of values to group by
function extractGroupArgs(v::AVec, args...; legendEntry = string)
groupLabels = sort(collect(unique(v)))
n = length(groupLabels)
if n > 100
@warn("You created n=$n groups... Is that intended?")
end
groupIds = Vector{Int}[filter(i -> v[i] == glab, eachindex(v)) for glab in groupLabels]
GroupBy(map(legendEntry, groupLabels), groupIds)
end
legendEntryFromTuple(ns::Tuple) = join(ns, ' ')
# this is when given a tuple of vectors of values to group by
function extractGroupArgs(vs::Tuple, args...)
isempty(vs) && return GroupBy([""], [axes(args[1],1)])
v = map(tuple, vs...)
extractGroupArgs(v, args...; legendEntry = legendEntryFromTuple)
end
# allow passing NamedTuples for a named legend entry
legendEntryFromTuple(ns::NamedTuple) =
join(["$k = $v" for (k, v) in pairs(ns)], ", ")
function extractGroupArgs(vs::NamedTuple, args...)
isempty(vs) && return GroupBy([""], [axes(args[1],1)])
v = map(NamedTuple{keys(vs)}tuple, values(vs)...)
extractGroupArgs(v, args...; legendEntry = legendEntryFromTuple)
end
# expecting a mapping of "group label" to "group indices"
function extractGroupArgs(idxmap::Dict{T,V}, args...) where {T, V<:AVec{Int}}
groupLabels = sortedkeys(idxmap)
groupIds = Vector{Int}[collect(idxmap[k]) for k in groupLabels]
GroupBy(groupLabels, groupIds)
end
filter_data(v::AVec, idxfilter::AVec{Int}) = v[idxfilter]
filter_data(v, idxfilter) = v
function filter_data!(plotattributes::AKW, idxfilter)
for s in (:x, :y, :z)
plotattributes[s] = filter_data(get(plotattributes, s, nothing), idxfilter)
end
end
function _filter_input_data!(plotattributes::AKW)
idxfilter = pop!(plotattributes, :idxfilter, nothing)
if idxfilter !== nothing
filter_data!(plotattributes, idxfilter)
end
end
# -----------------------------------------------------------------------------
const _already_warned = Dict{Symbol,Set{Symbol}}()
const _to_warn = Set{Symbol}()
function warnOnUnsupported_args(pkg::AbstractBackend, plotattributes)
function warn_on_unsupported_args(pkg::AbstractBackend, plotattributes)
empty!(_to_warn)
bend = backend_name(pkg)
already_warned = get!(_already_warned, bend, Set{Symbol}())
@@ -1188,7 +1142,7 @@ end
# _markershape_supported(pkg::AbstractBackend, shape::Shape) = Shape in supported_markers(pkg)
# _markershape_supported(pkg::AbstractBackend, shapes::AVec) = all([_markershape_supported(pkg, shape) for shape in shapes])
function warnOnUnsupported(pkg::AbstractBackend, plotattributes)
function warn_on_unsupported(pkg::AbstractBackend, plotattributes)
if !is_seriestype_supported(pkg, plotattributes[:seriestype])
@warn("seriestype $(plotattributes[:seriestype]) is unsupported with $pkg. Choose from: $(supported_seriestypes(pkg))")
end
@@ -1200,7 +1154,7 @@ function warnOnUnsupported(pkg::AbstractBackend, plotattributes)
end
end
function warnOnUnsupported_scales(pkg::AbstractBackend, plotattributes::AKW)
function warn_on_unsupported_scales(pkg::AbstractBackend, plotattributes::AKW)
for k in (:xscale, :yscale, :zscale, :scale)
if haskey(plotattributes, k)
v = plotattributes[k]
@@ -1219,7 +1173,7 @@ function convertLegendValue(val::Symbol)
:best
elseif val in (:no, :none)
:none
elseif val in (:right, :left, :top, :bottom, :inside, :best, :legend, :topright, :topleft, :bottomleft, :bottomright, :outertopright, :outertopleft, :outertop, :outerright, :outerleft, :outerbottomright, :outerbottomleft, :outerbottom)
elseif val in (:right, :left, :top, :bottom, :inside, :best, :legend, :topright, :topleft, :bottomleft, :bottomright, :outertopright, :outertopleft, :outertop, :outerright, :outerleft, :outerbottomright, :outerbottomleft, :outerbottom, :inline)
val
else
error("Invalid symbol for legend: $val")
@@ -1258,7 +1212,7 @@ function slice_arg!(plotattributes_in, plotattributes_out,
v
end
if remove_pair
reset_kw!(plotattributes_in, k)
RecipesPipeline.reset_kw!(plotattributes_in, k)
end
return
end
@@ -1471,12 +1425,9 @@ function _update_axis(plt::Plot, sp::Subplot, plotattributes_in::AKW, letter::Sy
end
function _update_axis(axis::Axis, plotattributes_in::AKW, letter::Symbol, subplot_index::Int)
# grab magic args (for example `xaxis = (:flip, :log)`)
args = wraptuple(get(plotattributes_in, Symbol(letter, :axis), ()))
# build the KW of arguments from the letter version (i.e. xticks --> ticks)
kw = KW()
for k in keys(_axis_defaults)
for k in _all_axis_args
# first get the args without the letter: `tickfont = font(10)`
# note: we don't pop because we want this to apply to all axes! (delete after all have finished)
if haskey(plotattributes_in, k)
@@ -1491,7 +1442,7 @@ function _update_axis(axis::Axis, plotattributes_in::AKW, letter::Symbol, subplo
end
# update the axis
attr!(axis, args...; kw...)
attr!(axis; kw...)
return
end
@@ -1522,7 +1473,7 @@ end
# update a subplots args and axes
function _update_subplot_args(plt::Plot, sp::Subplot, plotattributes_in, subplot_index::Int, remove_pair::Bool)
anns = pop_kw!(sp.attr, :annotations)
anns = RecipesPipeline.pop_kw!(sp.attr, :annotations)
# # grab those args which apply to this subplot
for k in keys(_subplot_defaults)
+23 -42
View File
@@ -17,7 +17,7 @@ function Axis(sp::Subplot, letter::Symbol, args...; kw...)
:show => true, # show or hide the axis? (useful for linked subplots)
)
attr = Attr(explicit, _axis_defaults_byletter[letter])
attr = DefaultsDict(explicit, _axis_defaults_byletter[letter])
# update the defaults
attr!(Axis([sp], attr), args...; kw...)
@@ -84,6 +84,9 @@ function attr!(axis::Axis, args...; kw...)
process_axis_arg!(plotattributes, arg)
end
# then preprocess keyword arguments
RecipesPipeline.preprocess_attributes!(KW(kw))
# then override for any keywords... only those keywords that already exists in plotattributes
for (k,v) in kw
if haskey(plotattributes, k)
@@ -114,33 +117,11 @@ Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.plotattributes, v,
Base.haskey(axis::Axis, k::Symbol) = haskey(axis.plotattributes, k)
ignorenan_extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax))
const _scale_funcs = Dict{Symbol,Function}(
:log10 => log10,
:log2 => log2,
:ln => log,
)
const _inv_scale_funcs = Dict{Symbol,Function}(
:log10 => exp10,
:log2 => exp2,
:ln => exp,
)
# const _label_func = Dict{Symbol,Function}(
# :log10 => x -> "10^$x",
# :log2 => x -> "2^$x",
# :ln => x -> "e^$x",
# )
const _label_func = Dict{Symbol,Function}(
:log10 => x -> "10^$x",
:log2 => x -> "2^$x",
:ln => x -> "e^$x",
)
scalefunc(scale::Symbol) = x -> get(_scale_funcs, scale, identity)(Float64(x))
invscalefunc(scale::Symbol) = x -> get(_inv_scale_funcs, scale, identity)(Float64(x))
labelfunc(scale::Symbol, backend::AbstractBackend) = get(_label_func, scale, string)
function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
@@ -148,7 +129,7 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
# scale the limits
scale = axis[:scale]
sf = scalefunc(scale)
sf = RecipesPipeline.scale_func(scale)
# If the axis input was a Date or DateTime use a special logic to find
# "round" Date(Time)s as ticks
@@ -158,7 +139,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] == dateformatter
if axis[: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,
@@ -169,7 +150,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] == datetimeformatter
elseif axis[:formatter] == RecipesPipeline.datetimeformatter
return optimize_datetime_ticks(amin, amax; k_min = 2, k_max = 4)
end
end
@@ -193,11 +174,11 @@ 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(invscalefunc(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(invscalefunc(scale), scaled_ticks)
unscaled_ticks = map(RecipesPipeline.inverse_scale_func(scale), scaled_ticks)
labels = if any(isfinite, unscaled_ticks)
formatter = axis[:formatter]
@@ -302,7 +283,7 @@ function get_minor_ticks(sp, axis, ticks)
minorticks = typeof(ticks[1])[]
for (i,hi) in enumerate(ticks[2:end])
lo = ticks[i]
if isfinite(lo) && hi > lo
if isfinite(lo) && isfinite(hi) && hi > lo
append!(minorticks,collect(lo + (hi-lo)/n :(hi-lo)/n: hi - (hi-lo)/2n))
end
end
@@ -397,7 +378,7 @@ function expand_extrema!(sp::Subplot, plotattributes::AKW)
if fr === nothing && plotattributes[:seriestype] == :bar
fr = 0.0
end
if fr !== nothing && !all3D(plotattributes)
if fr !== nothing && !RecipesPipeline.is3d(plotattributes)
axis = sp.attr[vert ? :yaxis : :xaxis]
if typeof(fr) <: Tuple
for fri in fr
@@ -442,7 +423,7 @@ end
# push the limits out slightly
function widen(lmin, lmax, scale = :identity)
f, invf = scalefunc(scale), invscalefunc(scale)
f, invf = RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale)
span = f(lmax) - f(lmin)
# eps = NaNMath.max(1e-16, min(1e-2span, 1e-10))
eps = NaNMath.max(1e-16, 0.03span)
@@ -511,7 +492,7 @@ function axis_limits(sp, letter, should_widen = default_should_widen(sp[Symbol(l
amin, amax
end
if !has_user_lims && consider_aspect && letter in (:x, :y) && !(sp[:aspect_ratio] in (:none, :auto) || is3d(:sp))
if !has_user_lims && consider_aspect && letter in (:x, :y) && !(sp[:aspect_ratio] in (:none, :auto) || RecipesPipeline.is3d(:sp))
aspect_ratio = isa(sp[:aspect_ratio], Number) ? sp[:aspect_ratio] : 1
plot_ratio = height(plotarea(sp)) / width(plotarea(sp))
dist = amax - amin
@@ -645,8 +626,8 @@ function axis_drawing_info(sp::Subplot)
sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2), (xmax, y2)) # top spine
end
if !(xaxis[:ticks] in (:none, nothing, false))
f = scalefunc(yaxis[:scale])
invf = invscalefunc(yaxis[:scale])
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)
@@ -699,8 +680,8 @@ function axis_drawing_info(sp::Subplot)
sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin), (x2, ymax)) # right spine
end
if !(yaxis[:ticks] in (:none, nothing, false))
f = scalefunc(xaxis[:scale])
invf = invscalefunc(xaxis[:scale])
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)
@@ -791,8 +772,8 @@ function axis_drawing_info_3d(sp::Subplot)
sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2, z2), (xmax, y2, z2)) # top spine
end
if !(xaxis[:ticks] in (:none, nothing, false))
f = scalefunc(yaxis[:scale])
invf = invscalefunc(yaxis[:scale])
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)
@@ -866,8 +847,8 @@ function axis_drawing_info_3d(sp::Subplot)
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 = scalefunc(xaxis[:scale])
invf = invscalefunc(xaxis[:scale])
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)
@@ -941,8 +922,8 @@ function axis_drawing_info_3d(sp::Subplot)
sp[:framestyle] in (:semi, :box) && push!(zborder_segs, (x2, y2, zmin), (x2, y2, zmax))
end
if !(zaxis[:ticks] in (:none, nothing, false))
f = scalefunc(xaxis[:scale])
invf = invscalefunc(xaxis[:scale])
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)
+110 -32
View File
@@ -104,7 +104,7 @@ end
# Set the (left, top, right, bottom) minimum padding around the plot area
# to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot)
# TODO: something different when `is3d(sp) == true`
# TODO: something different when `RecipesPipeline.is3d(sp) == true`
leftpad = tick_padding(sp, sp[:yaxis]) + sp[:left_margin] + guide_padding(sp[:yaxis])
toppad = sp[:top_margin] + title_padding(sp)
rightpad = sp[:right_margin]
@@ -211,7 +211,7 @@ const _base_supported_args = [
:seriestype,
:seriescolor, :seriesalpha,
:smooth,
:xerror, :yerror,
:xerror, :yerror, :zerror,
:subplot,
:x, :y, :z,
:show, :size,
@@ -673,37 +673,86 @@ const _inspectdr_scale = [:identity, :ln, :log2, :log10]
const _pgfplotsx_attr = merge_with_base_supported([
:annotations,
:background_color_legend, :background_color_inside, :background_color_outside,
:foreground_color_legend, :foreground_color_grid, :foreground_color_axis,
:foreground_color_text, :foreground_color_border,
:background_color_legend,
:background_color_inside,
:background_color_outside,
:foreground_color_legend,
:foreground_color_grid,
:foreground_color_axis,
:foreground_color_text,
:foreground_color_border,
:label,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
:fillrange, :fillcolor, :fillalpha,
:seriescolor,
:seriesalpha,
:linecolor,
:linestyle,
:linewidth,
:linealpha,
:markershape,
:markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokealpha,
:fillrange,
:fillcolor,
:fillalpha,
:bins,
:layout,
:title, :window_title,
:guide, :lims, :ticks, :scale, :flip,
:title,
:window_title,
:guide,
:lims,
:ticks,
:scale,
:flip,
:match_dimensions,
:titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign,
:titlefontrotation, :titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfonthalign, :legendfontvalign,
:legendfontrotation, :legendfontcolor,
:tickfontfamily, :tickfontsize, :tickfonthalign, :tickfontvalign,
:tickfontrotation, :tickfontcolor,
:guidefontfamily, :guidefontsize, :guidefonthalign, :guidefontvalign,
:guidefontrotation, :guidefontcolor,
:grid, :gridalpha, :gridstyle, :gridlinewidth,
:legend, :legendtitle, :colorbar, :colorbar_title, :colorbar_entry,
:fill_z, :line_z, :marker_z, :levels,
:ribbon, :quiver,
:titlefontfamily,
:titlefontsize,
:titlefonthalign,
:titlefontvalign,
:titlefontrotation,
:titlefontcolor,
:legendfontfamily,
:legendfontsize,
:legendfonthalign,
:legendfontvalign,
:legendfontrotation,
:legendfontcolor,
:tickfontfamily,
:tickfontsize,
:tickfonthalign,
:tickfontvalign,
:tickfontrotation,
:tickfontcolor,
:guidefontfamily,
:guidefontsize,
:guidefonthalign,
:guidefontvalign,
:guidefontrotation,
:guidefontcolor,
:grid,
:gridalpha,
:gridstyle,
:gridlinewidth,
:legend,
:legendtitle,
:colorbar,
:colorbar_title,
:colorbar_entry,
:fill_z,
:line_z,
:marker_z,
:levels,
:ribbon,
:quiver,
:orientation,
:overwrite_figure,
:polar,
:aspect_ratio,
:normalize, :weights,
:normalize,
:weights,
:inset_subplots,
:bar_width,
:arrow,
@@ -712,13 +761,42 @@ const _pgfplotsx_attr = merge_with_base_supported([
:camera,
:contour_labels,
])
const _pgfplotsx_seriestype =
[:path, :scatter, :straightline,
:path3d, :scatter3d, :surface, :wireframe,
:heatmap, :contour, :contour3d,
:shape,
:steppre, :stepmid, :steppost, :ysticks, :xsticks]
const _pgfplotsx_seriestype = [
:path,
:scatter,
:straightline,
:path3d,
:scatter3d,
:surface,
:wireframe,
:heatmap,
:contour,
:contour3d,
:shape,
:steppre,
:stepmid,
:steppost,
:ysticks,
:xsticks,
]
const _pgfplotsx_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
const _pgfplotsx_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :ltriangle, :rtriangle, :cross, :xcross, :star5, :pentagon, :hline, :vline, Shape]
const _pgfplotsx_marker = [
:none,
:auto,
:circle,
:rect,
:diamond,
:utriangle,
:dtriangle,
:ltriangle,
:rtriangle,
:cross,
:xcross,
:star5,
:pentagon,
:hline,
:vline,
Shape,
]
const _pgfplotsx_scale = [:identity, :ln, :log2, :log10]
is_marker_supported(::PGFPlotsXBackend, shape::Shape) = true
+72 -29
View File
@@ -67,6 +67,11 @@ const gr_font_family = Dict(
"palatino" => 26
)
const gr_vector_font = Dict(
"serif-roman" => 232,
"sans-serif" => 233
)
# --------------------------------------------------------------------------------------
gr_color(c) = gr_color(c, color_type(c))
@@ -305,15 +310,27 @@ end
# ---------------------------------------------------------
# draw ONE Shape
function gr_draw_marker(xi, yi, msize, shape::Shape)
function gr_draw_marker(series, xi, yi, clims, i, msize, shape::Shape)
sx, sy = coords(shape)
# convert to ndc coords (percentages of window)
GR.selntran(0)
w, h = gr_plot_size
f = msize / (w + h)
xi, yi = GR.wctondc(xi, yi)
GR.fillarea(xi .+ sx .* f,
yi .+ sy .* f)
xs = xi .+ sx .* f
ys = yi .+ sy .* f
# draw the interior
mc = get_markercolor(series, clims, i)
gr_set_fill(mc)
gr_set_transparency(mc, get_markeralpha(series, i))
GR.fillarea(xs, ys)
# draw the shapes
msc = get_markerstrokecolor(series, i)
gr_set_line(get_markerstrokewidth(series, i), :solid, msc)
gr_set_transparency(msc, get_markerstrokealpha(series, i))
GR.polyline(xs, ys)
GR.selntran(1)
end
@@ -323,7 +340,11 @@ function nominal_size()
end
# draw ONE symbol marker
function gr_draw_marker(xi, yi, msize::Number, shape::Symbol)
function gr_draw_marker(series, xi, yi, clims, i, msize::Number, shape::Symbol)
GR.setborderwidth(series[:markerstrokewidth]);
gr_set_bordercolor(get_markerstrokecolor(series, i));
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())
GR.polymarker([xi], [yi])
@@ -341,13 +362,7 @@ function gr_draw_markers(series::Series, x, y, clims, msize = series[:markersize
for i=eachindex(x)
msi = _cycle(msize, i)
shape = _cycle(shapes, i)
i
GR.setborderwidth(series[:markerstrokewidth]);
gr_set_bordercolor(get_markerstrokecolor(series, i));
gr_set_markercolor(get_markercolor(series, clims, i));
gr_set_transparency(get_markeralpha(series, i))
gr_draw_marker(x[i], y[i], msi, shape)
gr_draw_marker(series, x[i], y[i], clims, i, msi, shape)
end
end
end
@@ -380,6 +395,8 @@ function gr_set_font(f::Font; halign = f.halign, valign = f.valign,
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)
end
gr_set_textcolor(color)
GR.settextalign(gr_halign[halign], gr_valign[valign])
@@ -418,7 +435,7 @@ function gr_viewport_from_bbox(sp::Subplot{GRBackend}, bb::BoundingBox, w, h, vi
viewport[3] = viewport_canvas[4] * (1.0 - bottom(bb) / h)
viewport[4] = viewport_canvas[4] * (1.0 - top(bb) / h)
if hascolorbar(sp)
viewport[2] -= gr_colorbar_ratio * (1 + is3d(sp) / 2)
viewport[2] -= gr_colorbar_ratio * (1 + RecipesPipeline.is3d(sp) / 2)
end
viewport
end
@@ -426,8 +443,8 @@ end
# change so we're focused on the viewport area
function gr_set_viewport_cmap(sp::Subplot)
GR.setviewport(
viewport_plotarea[2] + (is3d(sp) ? 0.07 : 0.02),
viewport_plotarea[2] + (is3d(sp) ? 0.10 : 0.05),
viewport_plotarea[2] + (RecipesPipeline.is3d(sp) ? 0.07 : 0.02),
viewport_plotarea[2] + (RecipesPipeline.is3d(sp) ? 0.10 : 0.05),
viewport_plotarea[3],
viewport_plotarea[4]
)
@@ -805,6 +822,7 @@ end
function _update_min_padding!(sp::Subplot{GRBackend})
dpi = sp.plt[:thickness_scaling]
ENV["GKS_ENCODING"] = "utf8"
if !haskey(ENV, "GKSwstype")
if isijulia()
ENV["GKSwstype"] = "svg"
@@ -823,7 +841,7 @@ function _update_min_padding!(sp::Subplot{GRBackend})
toppad += h
end
if is3d(sp)
if RecipesPipeline.is3d(sp)
xaxis, yaxis, zaxis = sp[:xaxis], sp[:yaxis], sp[:zaxis]
xticks, yticks, zticks = get_ticks(sp, xaxis), get_ticks(sp, yaxis), get_ticks(sp, zaxis)
# Add margin for x and y ticks
@@ -971,7 +989,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
data_lims = gr_xy_axislims(sp)
xy_lims = data_lims
ratio = sp[:aspect_ratio]
ratio = get_aspect_ratio(sp)
if ratio != :none
if ratio == :equal
ratio = 1
@@ -1033,10 +1051,17 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
viewport_plotarea[3] += legendh + 0.04
end
end
if sp[:legend] == :inline
if sp[:yaxis][:mirror]
viewport_plotarea[1] += legendw
else
viewport_plotarea[2] -= legendw
end
end
# fill in the plot area background
bg = plot_color(sp[:background_color_inside])
is3d(sp) || gr_fill_viewport(viewport_plotarea, bg)
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?
@@ -1050,11 +1075,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
if st == :pie
draw_axes = false
end
if st == :heatmap
if st in (:heatmap, :image)
outside_ticks = true
for ax in (sp[:xaxis], sp[:yaxis])
v = series[ax[:letter]]
end
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)
@@ -1110,7 +1132,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
gr_set_font(tickfont(xaxis))
GR.setlinewidth(sp.plt[:thickness_scaling])
if is3d(sp)
if RecipesPipeline.is3d(sp)
zmin, zmax = axis_limits(sp, :z)
GR.setspace(zmin, zmax, round.(Int, sp[:camera])...)
@@ -1121,7 +1143,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
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 = unzip(GR.wc3towc.(plot_area_x, plot_area_y, plot_area_z))
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
@@ -1410,14 +1432,16 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
end
# border
intensity = sp[:framestyle] == :semi ? 0.5 : 1.0
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])
gr_set_transparency(xaxis[:foreground_color_border], intensity)
gr_polyline(coords(xborder_segs)...)
gr_set_line(intensity, :solid, yaxis[:foreground_color_border])
gr_set_transparency(yaxis[:foreground_color_border], intensity)
gr_polyline(coords(yborder_segs)...)
GR.setclip(1)
end
end
# end
@@ -1440,7 +1464,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
GR.settextalign(halign, GR.TEXT_VALIGN_TOP)
gr_text(xpos, viewport_subplot[4], sp[:title])
end
if is3d(sp)
if RecipesPipeline.is3d(sp)
if xaxis[:guide] != ""
gr_set_font(
guidefont(xaxis),
@@ -1697,7 +1721,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
# draw markers
if st == :scatter3d || series[:markershape] != :none
x2, y2 = unzip(map(GR.wc3towc, x, y, z))
x2, y2 = RecipesPipeline.unzip(map(GR.wc3towc, x, y, z))
gr_draw_markers(series, x2, y2, clims)
end
@@ -1776,8 +1800,10 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
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(series[:x]); ymin, ymax = ignorenan_extrema(series[:y])
xmin, xmax = ignorenan_extrema(x)
ymin, ymax = ignorenan_extrema(y)
rgba = gr_color.(z)
GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba)
end
@@ -1789,6 +1815,21 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
gr_text(GR.wctondc(xi, yi)..., str)
end
if sp[:legend] == :inline && should_add_to_legend(series)
gr_set_font(legendfont(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()
end
@@ -1796,7 +1837,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
hascolorbar(sp) && gr_draw_colorbar(cbar, sp, get_clims(sp))
# add the legend
if sp[:legend] != :none
if !(sp[:legend] in(:none, :inline))
GR.savestate()
GR.selntran(0)
GR.setscale(0)
@@ -1879,7 +1920,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
end
for ann in sp[:annotations]
x, y, val = locate_annotation(sp, ann...)
x, y = if is3d(sp)
x, y = if RecipesPipeline.is3d(sp)
gr_w3tondc(x, y, z)
else
GR.wctondc(x, y)
@@ -1905,6 +1946,7 @@ gr_set_output(wstype::String) = (_gr_wstype[] = wstype)
for (mime, fmt) in _gr_mimeformats
@eval function _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GRBackend})
ENV["GKS_ENCODING"] = "utf8"
GR.emergencyclosegks()
filepath = tempname() * "." * $fmt
env = get(ENV, "GKSwstype", "0")
@@ -1923,6 +1965,7 @@ for (mime, fmt) in _gr_mimeformats
end
function _display(plt::Plot{GRBackend})
ENV["GKS_ENCODING"] = "utf8"
if plt[:display_type] == :inline
GR.emergencyclosegks()
filepath = tempname() * ".pdf"
+66 -36
View File
@@ -83,6 +83,7 @@ if length(HDF5PLOT_MAP_TELEM2STR) < 1
"ARRAY" => Array, #Dict won't allow Array to be key in HDF5PLOT_MAP_TELEM2STR
#Sub-structure types:
"DEFAULTSDICT" => DefaultsDict,
"FONT" => Font,
"BOUNDINGBOX" => BoundingBox,
"GRIDLAYOUT" => GridLayout,
@@ -109,7 +110,7 @@ _hdf5_datapath(subpath::String) = "$_hdf5_dataroot/$subpath"
_hdf5_map_str2telem(k::String) = HDF5PLOT_MAP_STR2TELEM[k]
_hdf5_map_str2telem(v::Vector) = HDF5PLOT_MAP_STR2TELEM[v[1]]
function _hdf5_merge!(dest, src)
function _hdf5_merge!(dest::AKW, src::AKW)
for (k, v) in src
if isa(v, Axis)
_hdf5_merge!(dest[k].plotattributes, v.plotattributes)
@@ -263,6 +264,15 @@ function _hdf5plot_gwrite(grp, k::String, v::Array{T}) where T<:Number #Default
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
@@ -295,7 +305,8 @@ function _hdf5plot_gwrite(grp, k::String, v::Tuple)
end
#NOTE: _hdf5plot_overwritetype overwrites "Array" type with "Tuple".
end
function _hdf5plot_gwrite(grp, k::String, plotattributes::AKW)
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)
@@ -376,13 +387,22 @@ function _hdf5plot_gwrite(grp, k::String, v::Subplot)
_hdf5plot_writetype(grp, Subplot)
return
end
function _hdf5plot_write(grp, plotattributes::AKW)
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:
# ----------------------------------------------------------------
@@ -445,20 +465,14 @@ _hdf5plot_convert(T::Type{Extrema}, v) = Extrema(v[1], v[2])
# Read data structures:
# ----------------------------------------------------------------
function _hdf5plot_read(grp, k::String, T::Type, dtid)
function _hdf5plot_read(grp, k::String, T::Type)
v = HDF5.d_read(grp, k)
return _hdf5plot_convert(T, v)
end
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
# Read more complex data structures:
# ----------------------------------------------------------------
function _hdf5plot_read(grp, k::String, T::Type{Font}, dtid)
function _hdf5plot_read(grp, k::String, T::Type{Font})
grp = HDF5.g_open(grp, k)
family = _hdf5plot_read(grp, "family")
@@ -469,7 +483,7 @@ function _hdf5plot_read(grp, k::String, T::Type{Font}, dtid)
color = _hdf5plot_read(grp, "color")
return Font(family, pointsize, halign, valign, rotation, color)
end
function _hdf5plot_read(grp, k::String, T::Type{Array}, dtid) #ANY
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
@@ -488,18 +502,18 @@ function _hdf5plot_read(grp, k::String, T::Type{Array}, dtid) #ANY
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}, dtid)
v = _hdf5plot_read(grp, k, Array, dtid)
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}, dtid)
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}, dtid)
function _hdf5plot_read(grp, k::String, T::Type{SeriesAnnotations})
grp = HDF5.g_open(grp, k)
strs = _hdf5plot_read(grp, "strs")
@@ -508,29 +522,29 @@ function _hdf5plot_read(grp, k::String, T::Type{SeriesAnnotations}, dtid)
scalefactor = _hdf5plot_read(grp, "scalefactor")
return SeriesAnnotations(strs, font, baseshape, scalefactor)
end
function _hdf5plot_read(grp, k::String, T::Type{Shape}, dtid)
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}, dtid)
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}, dtid)
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}, dtid) = RootLayout()
function _hdf5plot_read(grp, k::String, T::Type{GridLayout}, dtid)
_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")
@@ -544,22 +558,36 @@ parent = RootLayout()
return GridLayout(parent, minpad, bbox, grid, widths, heights, attr)
end
function _hdf5plot_read(grp, k::String, T::Type{Axis}, dtid)
grp = HDF5.g_open(grp, k)
kwlist = KW()
_hdf5plot_read(grp, kwlist)
return Axis([], kwlist)
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{Surface}, dtid)
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}, dtid)
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], _hdf5plot_datatypeid)
T = _hdf5_map_str2telem(dtid) #expect exception
@@ -567,7 +595,7 @@ function _hdf5plot_read(grp, k::String)
end
#Read in values in group to populate plotattributes:
function _hdf5plot_read(grp, plotattributes::AKW)
function _hdf5plot_readattr(grp, plotattributes::AbstractDict)
gnames = names(grp)
for k in gnames
try
@@ -581,6 +609,8 @@ function _hdf5plot_read(grp, plotattributes::AKW)
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:
# ----------------------------------------------------------------
@@ -593,17 +623,17 @@ function _hdf5plot_read(sp::Subplot, subpath::String, f)
for i in 1:nseries
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list/series$i"))
kwlist = KW()
_hdf5plot_read(grp, kwlist)
plot!(sp, kwlist[:x], kwlist[:y]) #Add data & create data structures
_hdf5_merge!(sp.series_list[end].plotattributes, kwlist)
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"))
kwlist = KW()
_hdf5plot_read(grp, kwlist)
_hdf5_merge!(sp.attr, kwlist)
attr = DefaultsDict(KW(), _plot_defaults)
_hdf5plot_read(grp, attr)
_hdf5_merge!(sp.attr, attr)
return
end
+5 -5
View File
@@ -166,7 +166,7 @@ function pgf_series(sp::Subplot, series::Series)
# function args
args = if st == :contour
plotattributes[:z].surf, plotattributes[:x], plotattributes[:y]
elseif is3d(st)
elseif RecipesPipeline.is3d(st)
plotattributes[:x], plotattributes[:y], plotattributes[:z]
elseif st == :straightline
straightline_data(series)
@@ -271,7 +271,7 @@ function pgf_fillrange_series(series, i, fillrange, args...)
push!(style, _pgf_series_extrastyle[st])
end
kw[:style] = join(style, ',')
func = is3d(series) ? PGFPlots.Linear3 : PGFPlots.Linear
func = RecipesPipeline.is3d(series) ? PGFPlots.Linear3 : PGFPlots.Linear
return func(pgf_fillrange_args(fillrange, args...)...; kw...)
end
@@ -444,7 +444,7 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
# add to style/kw for each axis
for letter in (:x, :y, :z)
if letter != :z || is3d(sp)
if letter != :z || RecipesPipeline.is3d(sp)
axisstyle, axiskw = pgf_axis(sp, letter)
append!(style, axisstyle)
merge!(kw, axiskw)
@@ -470,7 +470,7 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
push!(style, string("title style = {font = ", pgf_font(sp[:titlefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", sp[:titlefontrotation], "}"))
end
if sp[:aspect_ratio] in (1, :equal)
if get_aspect_ratio(sp) in (1, :equal)
kw[:axisEqual] = "true"
end
@@ -494,7 +494,7 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
if any(s[:seriestype] == :contour for s in series_list(sp))
kw[:view] = "{0}{90}"
kw[:colorbar] = !(sp[:colorbar] in (:none, :off, :hide, false))
elseif is3d(sp)
elseif RecipesPipeline.is3d(sp)
azim, elev = sp[:camera]
kw[:view] = "{$(azim)}{$(elev)}"
end
+344 -166
View File
@@ -29,11 +29,20 @@ Base.@kwdef mutable struct PGFPlotsXPlot
PGFPlotsX.push_preamble!(
pgfx_plot.the_plot,
raw"""
\pgfplotsset{
/pgfplots/layers/axis on top/.define layer set={
background, axis background,pre main,main,axis grid,axis ticks,axis lines,axis tick labels,
axis descriptions,axis foreground
}{/pgfplots/layers/standard},
\pgfplotsset{%
layers/standard/.define layer set={%
background,axis background,axis grid,axis ticks,axis lines,axis tick labels,pre main,main,axis descriptions,axis foreground%
}{grid style= {/pgfplots/on layer=axis grid},%
tick style= {/pgfplots/on layer=axis ticks},%
axis line style= {/pgfplots/on layer=axis lines},%
label style= {/pgfplots/on layer=axis descriptions},%
legend style= {/pgfplots/on layer=axis descriptions},%
title style= {/pgfplots/on layer=axis descriptions},%
colorbar style= {/pgfplots/on layer=axis descriptions},%
ticklabel style= {/pgfplots/on layer=axis tick labels},%
axis background@ style={/pgfplots/on layer=axis background},%
3d box foreground style={/pgfplots/on layer=axis foreground},%
},
}
""",
)
@@ -51,7 +60,7 @@ function pgfx_preamble(pgfx_plot::Plot{PGFPlotsXBackend})
old_flag = pgfx_plot.attr[:tex_output_standalone]
pgfx_plot.attr[:tex_output_standalone] = true
fulltext = String(repr("application/x-tex", pgfx_plot))
preamble = fulltext[1:first(findfirst("\\begin{document}", fulltext))-1]
preamble = fulltext[1:(first(findfirst("\\begin{document}", fulltext)) - 1)]
pgfx_plot.attr[:tex_output_standalone] = old_flag
preamble
end
@@ -75,10 +84,11 @@ function Base.push!(pgfx_plot::PGFPlotsXPlot, item)
end
function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
if !pgfx_plot.is_created
if !pgfx_plot.is_created || pgfx_plot.was_shown
pgfx_sanitize_plot!(plt)
the_plot = PGFPlotsX.TikzPicture(PGFPlotsX.Options())
bgc = plt.attr[:background_color_outside] == :match ?
plt.attr[:background_color] : plt.attr[:background_color_outside]
plt.attr[:background_color] : plt.attr[:background_color_outside]
if bgc isa Colors.Colorant
cstr = plot_color(bgc)
a = alpha(cstr)
@@ -94,27 +104,43 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
end
for sp in plt.subplots
bb = bbox(sp)
sp_width = width(bb)
sp_height = height(bb)
dx, dy = bb.x0
# TODO: does this hold at every scale?
if sp[:legend] in (:outertopright, nothing)
dx *= 1.2
end
bb1 = sp.plotarea
bb2 = bbox(sp)
sp_width = width(bb2)
sp_height = height(bb2)
dx, dy = bb2.x0
lpad = leftpad(sp) + sp[:left_margin]
rpad = rightpad(sp) + sp[:right_margin]
tpad = toppad(sp) + sp[:top_margin]
bpad = bottompad(sp) + sp[:bottom_margin]
dx += lpad
dy += tpad
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[:title_location]
bgc_inside = plot_color(sp[:background_color_inside])
bgc_inside_a = alpha(bgc_inside)
axis_opt = PGFPlotsX.Options(
"title" => sp[:title],
"title style" => PGFPlotsX.Options(
"font" => pgfx_font(
sp[:titlefontsize],
pgfx_thickness_scaling(sp),
"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,
"font" => pgfx_font(
sp[:titlefontsize],
pgfx_thickness_scaling(sp),
),
"color" => title_cstr,
"draw opacity" => title_a,
@@ -129,7 +155,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
) => nothing,
"fill" => cstr,
"fill opacity" => a,
"text opacity" => alpha(plot_color(sp[:legendfontcolor])),
"text opacity" => alpha(plot_color(sp[:legendfontcolor])),
"font" => pgfx_font(
sp[:legendfontsize],
pgfx_thickness_scaling(sp),
@@ -139,16 +165,14 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
"fill" => bgc_inside,
"opacity" => bgc_inside_a,
),
"axis on top" => nothing,
# "framed" => nothing,
# These are for layouting
"anchor" => "north west",
"xshift" => string(dx),
"yshift" => string(-dy),
)
sp_width > 0 * mm ? push!(axis_opt, "width" => string(sp_width)) :
sp_width > 0 * mm ? push!(axis_opt, "width" => string(axis_width)) :
nothing
sp_height > 0 * mm ? push!(axis_opt, "height" => string(sp_height)) :
sp_height > 0 * mm ? push!(axis_opt, "height" => string(axis_height)) :
nothing
# legend position
if sp[:legend] isa Tuple
@@ -156,16 +180,12 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
push!(axis_opt["legend style"], "at={($x, $y)}")
else
push!(
axis_opt,
"legend pos" => get(
_pgfplotsx_legend_pos,
sp[:legend],
"outer north east",
),
axis_opt["legend style"],
get(_pgfplotsx_legend_pos, sp[:legend], ("at" => string((1.02, 1)), "anchor" => "north west"))...
)
end
for letter in (:x, :y, :z)
if letter != :z || is3d(sp)
if letter != :z || RecipesPipeline.is3d(sp)
pgfx_axis!(axis_opt, sp, letter)
end
end
@@ -185,8 +205,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
PGFPlotsX.push_preamble!(
pgfx_plot.the_plot,
"""\\pgfplotsset{
colormap={plots$(sp.attr[:subplot_index])}{$(pgfx_colormap(series.plotattributes[col]))},
}""",
colormap={plots$(sp.attr[:subplot_index])}{$(pgfx_colormap(series.plotattributes[col]))},
}""",
)
push!(
axis_opt,
@@ -204,22 +224,32 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
axis_opt,
"colorbar style" => PGFPlotsX.Options(
"title" => sp[:colorbar_title],
"point meta max" => get_clims(sp)[2],
"point meta min" => get_clims(sp)[1],
),
"point meta max" => get_clims(sp)[2],
"point meta min" => get_clims(sp)[1],
)
if is3d(sp)
if RecipesPipeline.is3d(sp)
azim, elev = sp[:camera]
push!(axis_opt, "view" => (azim, elev))
end
axisf = if sp[:projection] == :polar
# push!(axis_opt, "xmin" => 90)
# push!(axis_opt, "xmax" => 450)
# push!(axis_opt, "xmin" => 90)
# push!(axis_opt, "xmax" => 450)
PGFPlotsX.PolarAxis
else
PGFPlotsX.Axis
end
axis = axisf(axis_opt)
if sp[:legendtitle] !== nothing
push!(axis, PGFPlotsX.Options("\\addlegendimage{empty legend}" => nothing))
push!(
axis,
PGFPlotsX.LegendEntry(
string("\\hspace{-.6cm}{\\textbf{", sp[:legendtitle], "}}"),
false,
),
)
end
for (series_index, series) in enumerate(series_list(sp))
# give each series a uuid for fillbetween
series_id = uuid4()
@@ -229,20 +259,21 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
sf = series[:fillrange]
series_opt = PGFPlotsX.Options(
"color" => single_color(opt[:linecolor]),
"name path" => string(series_id)
"name path" => string(series_id),
)
if is3d(series) || st == :heatmap
if RecipesPipeline.is3d(series) || st == :heatmap
series_func = PGFPlotsX.Plot3
else
series_func = PGFPlotsX.Plot
end
if sf !== nothing && !isfilledcontour(series) && series[:ribbon] === nothing
if sf !== nothing &&
!isfilledcontour(series) && series[:ribbon] === nothing
push!(series_opt, "area legend" => nothing)
end
if st == :heatmap
push!(axis.options, "view" => "{0}{90}")
end
# treat segments
# treat segments
segments =
if st in (:wireframe, :heatmap, :contour, :surface, :contour3d)
iter_segments(surface_to_vecs(
@@ -261,10 +292,13 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
if marker isa Shape
x = marker.x
y = marker.y
scale_factor = 0.025
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)],
[
"($(x[i] * mark_size), $(y[i] * mark_size))"
for i in eachindex(x)
],
" -- ",
)
c = get_markercolor(series, i)
@@ -285,38 +319,44 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
segment_opt = merge(segment_opt, pgfx_fillstyle(opt, i))
end
# add fillrange
if sf !== nothing && !isfilledcontour(series) && series[:ribbon] === nothing
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)
pgfx_fillrange_series!(
axis,
series,
series_func,
i,
_cycle(sf, rng),
rng,
)
end
if i == 1 && opt[:label] != "" && sp[:legend] != :none && should_add_to_legend(series)
if i == 1 &&
sp[:legend] != :none && pgfx_should_add_to_legend(series)
pgfx_filllegend!(series_opt, opt)
end
end
# series
#
coordinates = pgfx_series_coordinates!(
sp,
series,
segment_opt,
opt,
rng,
)
segment_plot = series_func(
merge(series_opt, segment_opt),
coordinates,
)
coordinates =
pgfx_series_coordinates!(sp, series, segment_opt, opt, rng)
segment_plot =
series_func(merge(series_opt, segment_opt), coordinates)
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))])]"
))
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?
# add ribbons?
ribbon = series[:ribbon]
if ribbon !== nothing
pgfx_add_ribbons!(
@@ -327,34 +367,51 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
series_index,
)
end
# add to legend?
if i == 1 &&
opt[:label] != "" &&
sp[:legend] != :none && should_add_to_legend(series)
leg_opt = PGFPlotsX.Options()
if ribbon !== nothing
pgfx_filllegend!(axis.contents[end-3].options, opt)
# 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
legend = PGFPlotsX.LegendEntry(leg_opt, opt[:label], false)
push!(axis, legend)
end
# add series annotations
anns = series[:series_annotations]
for (xi, yi, str, fnt) in EachAnn(anns, series[:x], series[:y])
pgfx_add_annotation!(
axis,
xi,
yi,
PlotText(str, fnt),
pgfx_thickness_scaling(series),
)
end
end
# add subplot annotations
for ann in sp[:annotations]
pgfx_add_annotation!(axis, locate_annotation(sp, ann...)..., pgfx_thickness_scaling(sp))
end # for segments
# add series annotations
anns = series[:series_annotations]
for (xi, yi, str, fnt) in EachAnn(anns, series[:x], series[:y])
pgfx_add_annotation!(
axis,
xi,
yi,
PlotText(str, fnt),
pgfx_thickness_scaling(series),
)
end
end # for series
# add subplot annotations
for ann in sp[:annotations]
pgfx_add_annotation!(
axis,
locate_annotation(sp, ann...)...,
pgfx_thickness_scaling(sp),
)
end
push!(the_plot, axis)
if length(plt.o.the_plot.elements) > 0
plt.o.the_plot.elements[1] = the_plot
@@ -363,6 +420,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
end
end # for subplots
pgfx_plot.is_created = true
pgfx_plot.was_shown = false
end # if
return pgfx_plot
end
@@ -374,7 +432,7 @@ end
opt[:x], opt[:y], Array(opt[:z])'
elseif st in (:heatmap, :surface, :wireframe)
surface_to_vecs(opt[:x], opt[:y], opt[:z])
elseif is3d(st)
elseif RecipesPipeline.is3d(st)
opt[:x], opt[:y], opt[:z]
elseif st == :straightline
straightline_data(series)
@@ -495,7 +553,7 @@ function pgfx_series_coordinates!(
)
push!(
segment_opt,
"contour prepared" => PGFPlotsX.Options("labels" => opt[:contour_labels],),
"contour prepared" => PGFPlotsX.Options("labels" => opt[:contour_labels]),
)
return PGFPlotsX.Table(Contour.contours(args..., opt[:levels]))
end
@@ -508,7 +566,7 @@ function pgfx_series_coordinates!(
xs, ys, zs = collect(args)
push!(
segment_opt,
"contour filled" => PGFPlotsX.Options("labels" => opt[:contour_labels],),
"contour filled" => PGFPlotsX.Options("labels" => opt[:contour_labels]),
"point meta" => "explicit",
"shader" => "flat",
)
@@ -519,10 +577,9 @@ function pgfx_series_coordinates!(
end
cs = join(
[join(["($x, $y) [$(zs[j, i])]" for (j, x) in enumerate(xs)], " ") for (
i,
y,
) in enumerate(ys)],
[
join(["($x, $y) [$(zs[j, i])]" for (j, x) in enumerate(xs)], " ") for (i, y) in enumerate(ys)
],
"\n\n",
)
"""
@@ -563,11 +620,22 @@ const _pgfplotsx_markers = KW(
)
const _pgfplotsx_legend_pos = KW(
:bottomleft => "south west",
:bottomright => "south east",
:topright => "north east",
:topleft => "north west",
:outertopright => "outer north east",
:top => ("at" => string((0.5, 0.98)), "anchor" => "north"),
:bottom => ("at" => string((0.5, 0.02)), "anchor" => "south"),
:left => ("at" => string((0.02, 0.5)), "anchor" => "west"),
:right => ("at" => string((0.98, 0.5)), "anchor" => "east"),
:bottomleft => ("at" => string((0.02, 0.02)), "anchor" => "south west"),
:bottomright => ("at" => string((0.98, 0.02)), "anchor" => "south east"),
:topright => ("at" => string((0.98, 0.98)), "anchor" => "north east"),
:topleft => ("at" => string((-0.02, 0.98)), "anchor" => "north west"),
:outertop => ("at" => string((0.5, 1.02)), "anchor" => "south"),
:outerbottom => ("at" => string((0.5, -0.02)), "anchor" => "north"),
:outerleft => ("at" => string((-0.02, 0.5)), "anchor" => "east"),
:outerright => ("at" => string((1.02, 0.5)), "anchor" => "west"),
:outerbottomleft => ("at" => string((-0.02, -0.02)), "anchor" => "north east"),
:outerbottomright => ("at" => string((1.02, -0.02)), "anchor" => "north west"),
:outertopright => ("at" => string((1.02, 1)), "anchor" => "north west"),
:outertopleft => ("at" => string((-0.02, 1)), "anchor" => "north east"),
)
const _pgfx_framestyles = [:box, :axes, :origin, :zerolines, :grid, :none]
@@ -575,11 +643,8 @@ const _pgfx_framestyle_defaults = Dict(:semi => :box)
# we use the anchors to define orientations for example to align left
# one needs to use the right edge as anchor
const _pgfx_annotation_halign = KW(
:center => "",
:left => "right",
:right => "left",
)
const _pgfx_annotation_halign =
KW(:center => "", :left => "right", :right => "left")
## --------------------------------------------------------------------------------------
# Generates a colormap for pgfplots based on a ColorGradient
pgfx_arrow(::Nothing) = "every arrow/.append style={-}"
@@ -607,12 +672,9 @@ function pgfx_filllegend!(series_opt, opt)
io = IOBuffer()
PGFPlotsX.print_tex(io, pgfx_fillstyle(opt))
style = strip(String(take!(io)), ['[', ']', ' '])
push!(
series_opt,
"legend image code/.code" => """{
\\draw[$style] (0cm,-0.1cm) rectangle (0.6cm,0.1cm);
}""",
)
push!(series_opt, "legend image code/.code" => """{
\\draw[$style] (0cm,-0.1cm) rectangle (0.6cm,0.1cm);
}""")
end
function pgfx_colormap(grad::ColorGradient)
@@ -631,9 +693,7 @@ function pgfx_framestyle(style::Symbol)
return style
else
default_style = get(_pgfx_framestyle_defaults, style, :axes)
@warn(
"Framestyle :$style is not (yet) supported by the PGFPlotsX backend. :$default_style was cosen instead.",
)
@warn( "Framestyle :$style is not (yet) supported by the PGFPlotsX backend. :$default_style was cosen instead.",)
default_style
end
end
@@ -675,6 +735,25 @@ function pgfx_font(fontsize, thickness_scaling = 1, font = "\\selectfont")
return string("{\\fontsize{", fs, " pt}{", 1.3fs, " pt}", font, "}")
end
function pgfx_should_add_to_legend(series::Series)
series.plotattributes[:primary] &&
!(
series.plotattributes[:seriestype] in (
:hexbin,
:bins2d,
:histogram2d,
:hline,
:vline,
:contour,
:contourf,
:contour3d,
:heatmap,
:pie,
:image,
)
)
end
function pgfx_marker(plotattributes, i = 1)
shape = _cycle(plotattributes[:markershape], i)
cstr = plot_color(
@@ -687,19 +766,22 @@ function pgfx_marker(plotattributes, i = 1)
get_markerstrokealpha(plotattributes, i),
)
a_stroke = alpha(cstr_stroke)
mark_size = pgfx_thickness_scaling(plotattributes) * 0.5 *
_cycle(plotattributes[:markersize], i)
mark_size =
pgfx_thickness_scaling(plotattributes) *
0.75 *
_cycle(plotattributes[:markersize], i)
return PGFPlotsX.Options(
"mark" => shape isa Shape ? "PlotsShape$i" :
get(_pgfplotsx_markers, shape, "*"),
"mark" =>
shape isa Shape ? "PlotsShape$i" : get(_pgfplotsx_markers, shape, "*"),
"mark size" => "$mark_size pt",
"mark options" => PGFPlotsX.Options(
"color" => cstr_stroke,
"draw opacity" => a_stroke,
"fill" => cstr,
"fill opacity" => a,
"line width" => pgfx_thickness_scaling(plotattributes) *
_cycle(plotattributes[:markerstrokewidth], i),
"line width" =>
pgfx_thickness_scaling(plotattributes) * 0.75 *
_cycle(plotattributes[:markerstrokewidth], i),
"rotate" => if shape == :dtriangle
180
elseif shape == :rtriangle
@@ -725,15 +807,15 @@ function pgfx_add_annotation!(o, x, y, val, thickness_scaling = 1)
push!(
o,
[
"\\node",
PGFPlotsX.Options(
get(_pgfx_annotation_halign, val.font.halign, "") => nothing,
"color" => cstr,
"draw opacity" => convert(Float16, a),
"rotate" => val.font.rotation,
"font" => pgfx_font(val.font.pointsize, thickness_scaling),
),
" at (axis cs:$x, $y) {$(val.str)};",
"\\node",
PGFPlotsX.Options(
get(_pgfx_annotation_halign, val.font.halign, "") => nothing,
"color" => cstr,
"draw opacity" => convert(Float16, a),
"rotate" => val.font.rotation,
"font" => pgfx_font(val.font.pointsize, thickness_scaling),
),
" at (axis cs:$x, $y) {$(val.str)};",
],
)
end
@@ -802,8 +884,8 @@ function pgfx_fillrange_series!(axis, series, series_func, i, fillrange, rng)
fillrange_opt = merge(fillrange_opt, pgfx_marker(series, i))
push!(fillrange_opt, "forget plot" => nothing)
opt = series.plotattributes
args = is3d(series) ? (opt[:x][rng], opt[:y][rng], opt[:z][rng]) :
(opt[:x][rng], opt[:y][rng])
args = RecipesPipeline.is3d(series) ? (opt[:x][rng], opt[:y][rng], opt[:z][rng]) :
(opt[:x][rng], opt[:y][rng])
push!(
axis,
PGFPlotsX.PlotInc(fillrange_opt, pgfx_fillrange_args(fillrange, args...)),
@@ -825,6 +907,57 @@ function pgfx_fillrange_args(fillrange, x, y, z)
z_fill = [z; _cycle(fillrange, n:-1:1); z[1]]
return PGFPlotsX.Coordiantes(x_fill, y_fill, z_fill)
end
function pgfx_sanitize_string(p::PlotText)
PlotText(pgfx_sanitize_string(p.str), p.font)
end
function pgfx_sanitize_string(s::AbstractString)
s = replace(s, r"\\?\#" => "\\#")
s = replace(s, r"\\?\%" => "\\%")
s = replace(s, r"\\?\_" => "\\_")
s = replace(s, r"\\?\&" => "\\&")
s = replace(s, r"\\?\{" => "\\{")
s = replace(s, r"\\?\}" => "\\}")
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"\\?\%" => "\\%")
end
end
function pgfx_sanitize_plot!(plt)
for (key, value) in plt.attr
if value isa Union{AbstractString, AbstractVector{<:AbstractString}}
plt.attr[key] = pgfx_sanitize_string.(value)
end
end
for subplot in plt.subplots
for (key, value) in subplot.attr
if key == :annotations && subplot.attr[:annotations] !== nothing
old_ann = subplot.attr[key]
for i in eachindex(old_ann)
subplot.attr[key][i] = (old_ann[i][1], old_ann[i][2], pgfx_sanitize_string(old_ann[i][3]))
end
elseif value isa Union{AbstractString, AbstractVector{<:AbstractString}}
subplot.attr[key] = pgfx_sanitize_string.(value)
end
end
end
for series in plt.series_list
for (key, value) in series.plotattributes
if key == :series_annotations && series.plotattributes[:series_annotations] !== nothing
old_ann = series.plotattributes[key].strs
for i in eachindex(old_ann)
series.plotattributes[key].strs[i] = pgfx_sanitize_string(old_ann[i])
end
elseif value isa Union{AbstractString, AbstractVector{<:AbstractString}}
series.plotattributes[key] = pgfx_sanitize_string.(value)
end
end
end
##
end
# --------------------------------------------------------------------------------------
function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
axis = sp[Symbol(letter, :axis)]
@@ -835,6 +968,21 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
"scaled $(letter) ticks" => "false",
string(letter, :label) => axis[:guide],
)
tick_color = plot_color(axis[:foreground_color_axis])
push!(opt,
"$(letter) tick style" => PGFPlotsX.Options(
"color" => color(tick_color),
"opacity" => alpha(tick_color),
),
)
tick_label_color = plot_color(axis[:tickfontcolor])
push!(opt,
"$(letter) tick label style" => PGFPlotsX.Options(
"color" => color(tick_color),
"opacity" => alpha(tick_color),
"rotate" => axis[:rotation]
),
)
# set to supported framestyle
framestyle = pgfx_framestyle(sp[:framestyle] == false ? :none : sp[:framestyle])
@@ -854,7 +1002,7 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
opt,
string(letter, "label style") => PGFPlotsX.Options(
labelpos => nothing,
"font" => pgfx_font(axis[:guidefontsize], pgfx_thickness_scaling(sp)),
"font" => pgfx_font(axis[:guidefontsize], pgfx_thickness_scaling(sp)),
"color" => cstr,
"draw opacity" => α,
"rotate" => axis[:guidefontrotation],
@@ -868,15 +1016,15 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
scale = axis[:scale]
if scale in (:log2, :ln, :log10)
push!(opt, string(letter, :mode) => "log")
scale == :ln || push!(
opt,
"log basis $letter" => "$(scale == :log2 ? 2 : 10)",
)
scale == :ln ||
push!(opt, "log basis $letter" => "$(scale == :log2 ? 2 : 10)")
end
# ticks on or off
if axis[:ticks] in (nothing, false, :none) || framestyle == :none
push!(opt, "$(letter)majorticks" => "false")
elseif framestyle in (:grid, :zerolines)
push!(opt, "$letter tick style" => PGFPlotsX.Options("draw" => "none"))
end
# grid on or off
@@ -888,14 +1036,16 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
# limits
lims = ispolar(sp) && letter == :x ? rad2deg.(axis_limits(sp, :x)) :
axis_limits(sp, letter)
axis_limits(sp, letter)
push!(opt, string(letter, :min) => lims[1], string(letter, :max) => lims[2])
if !(axis[:ticks] in (nothing, false, :none, :native)) && framestyle != :none
# ticks
ticks = get_ticks(sp, axis)
#pgf plot ignores ticks with angle below 90 when xmin = 90 so shift values
tick_values = ispolar(sp) && letter == :x ?
[rad2deg.(ticks[1])[3:end]..., 360, 405] : ticks[1]
tick_values =
ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] :
ticks[1]
push!(
opt,
string(letter, "tick") => string("{", join(tick_values, ","), "}"),
@@ -911,44 +1061,37 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
end
push!(
opt,
string(letter, "ticklabels") => string(
"{\$",
join(tick_labels, "\$,\$"),
"\$}",
),
string(letter, "ticklabels") =>
string("{\$", join(tick_labels, "\$,\$"), "\$}"),
)
elseif axis[:showaxis]
tick_labels = ispolar(sp) && letter == :x ?
[ticks[2][3:end]..., "0", "45"] : ticks[2]
tick_labels =
ispolar(sp) && letter == :x ? [ticks[2][3:end]..., "0", "45"] :
ticks[2]
if axis[:formatter] in (:scientific, :auto)
tick_labels = string.("\$", convert_sci_unicode.(tick_labels), "\$")
tick_labels = replace.(tick_labels, Ref("×" => "\\times"))
end
push!(
opt,
string(letter, "ticklabels") => string(
"{",
join(tick_labels, ","),
"}",
),
string(letter, "ticklabels") =>
string("{", join(tick_labels, ","), "}"),
)
else
push!(opt, string(letter, "ticklabels") => "{}")
end
push!(
opt,
string(letter, "tick align") => (axis[:tick_direction] == :out ?
"outside" : "inside"),
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),
),
"font" =>
pgfx_font(axis[:tickfontsize], pgfx_thickness_scaling(sp)),
"color" => cstr,
"draw opacity" => α,
"rotate" => axis[:tickfontrotation],
@@ -963,11 +1106,43 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
axis[:gridstyle],
),
)
# minor ticks
# NOTE: PGFPlots would provide "minor x ticks num", but this only places minor ticks
# between major ticks and not outside first and last tick to the axis limits.
# Hence, we hack around with extra ticks. Unfortunately this conflicts with
# `:zerolines` framestyle hack. So minor ticks are not working with
# `:zerolines`.
minor_ticks = get_minor_ticks(sp, axis, ticks)
if minor_ticks !== nothing
minor_ticks =
ispolar(sp) && letter == :x ? [rad2deg.(minor_ticks)[3:end]..., 360, 405] :
minor_ticks
push!(
opt,
string("extra ", letter, " ticks") => string("{", join(minor_ticks, ","), "}"),
)
push!(opt, string("extra ", letter, " tick labels") => "")
push!(
opt,
string("extra ", letter, " tick style") => PGFPlotsX.Options(
"grid" => axis[:minorgrid] ? "major" : "none",
string(letter, " grid style") => pgfx_linestyle(
pgfx_thickness_scaling(sp) * axis[:minorgridlinewidth],
axis[:foreground_color_minor_grid],
axis[:minorgridalpha],
axis[:minorgridstyle],
),
"major tick length" => typeof(axis[:minorticks]) <: Integer && axis[:minorticks] > 1 || axis[:minorticks] ? "0.1cm" : "0"
),
)
end
end
# framestyle
if framestyle in (:axes, :origin)
axispos = framestyle == :axes ? "left" : "middle"
axispos = axis[:mirror] ? "right" : framestyle == :axes ? "left" : "middle"
if axis[:draw_arrow]
push!(opt, string("axis ", letter, " line") => axispos)
else
@@ -983,7 +1158,7 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
opt,
string("extra ", letter, " tick style") => PGFPlotsX.Options(
"grid" => "major",
"major grid style" => pgfx_linestyle(
string(letter, " grid style") => pgfx_linestyle(
pgfx_thickness_scaling(sp),
axis[:foreground_color_border],
1.0,
@@ -1013,12 +1188,12 @@ end
# Set the (left, top, right, bottom) minimum padding around the plot area
# to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot{PGFPlotsXBackend})
# TODO: make padding more intelligent
# TODO: how to include margins properly?
# sp.minpad = (50mm + sp[:left_margin],
# 0mm + sp[:top_margin],
# 50mm + sp[:right_margin],
# 0mm + sp[:bottom_margin])
leg = sp[:legend]
if leg in (:best, :outertopright, :outerright, :outerbottomright) || (leg isa Tuple && leg[1] >= 1)
sp.minpad = (0mm, 0mm, 5mm, 0mm)
else
sp.minpad = (0mm, 0mm, 0mm, 0mm)
end
end
function _create_backend_figure(plt::Plot{PGFPlotsXBackend})
@@ -1039,6 +1214,7 @@ for mime in ("application/pdf", "image/png", "image/svg+xml")
mime::MIME{Symbol($mime)},
plt::Plot{PGFPlotsXBackend},
)
plt.o.was_shown = true
show(io, mime, plt.o.the_plot)
end
end
@@ -1048,6 +1224,7 @@ function _show(
mime::MIME{Symbol("application/x-tex")},
plt::Plot{PGFPlotsXBackend},
)
plt.o.was_shown = true
PGFPlotsX.print_tex(
io,
plt.o.the_plot,
@@ -1056,5 +1233,6 @@ function _show(
end
function _display(plt::Plot{PGFPlotsXBackend})
plt.o.was_shown = true
display(PGFPlotsX.PGFPlotsXDisplay(), plt.o.the_plot)
end
+7 -7
View File
@@ -108,7 +108,7 @@ function shrink_by(lo, sz, ratio)
end
function plotly_apply_aspect_ratio(sp::Subplot, plotarea, pcts)
aspect_ratio = sp[:aspect_ratio]
aspect_ratio = get_aspect_ratio(sp)
if aspect_ratio != :none
if aspect_ratio == :equal
aspect_ratio = 1.0
@@ -162,7 +162,7 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot)
if letter in (:x,:y)
ax[:domain] = plotly_domain(sp, letter)
if is3d(sp)
if RecipesPipeline.is3d(sp)
# don't link 3d axes for synchronized interactivity
x_idx = y_idx = sp[:subplot_index]
else
@@ -176,7 +176,7 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot)
lims = axis_limits(sp, letter)
if axis[:ticks] != :native || axis[:lims] != :auto
ax[:range] = map(scalefunc(axis[:scale]), lims)
ax[:range] = map(RecipesPipeline.scale_func(axis[:scale]), lims)
end
if !(axis[:ticks] in (nothing, :none, false))
@@ -263,8 +263,8 @@ function plotly_layout(plt::Plot)
# set to supported framestyle
sp[:framestyle] = _plotly_framestyle(sp[:framestyle])
# if any(is3d, seriesargs)
if is3d(sp)
# 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(
@@ -748,11 +748,11 @@ function plotly_colorbar_hack(series::Series, plotattributes_base::KW, sym::Symb
plotattributes_out = deepcopy(plotattributes_base)
cmin, cmax = get_clims(series[:subplot])
plotattributes_out[:showlegend] = false
plotattributes_out[:type] = is3d(series) ? :scatter3d : :scatter
plotattributes_out[:type] = RecipesPipeline.is3d(series) ? :scatter3d : :scatter
plotattributes_out[:hoverinfo] = :none
plotattributes_out[:mode] = :markers
plotattributes_out[:x], plotattributes_out[:y] = [series[:x][1]], [series[:y][1]]
if is3d(series)
if RecipesPipeline.is3d(series)
plotattributes_out[:z] = [series[:z][1]]
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)
+2 -2
View File
@@ -42,8 +42,8 @@ _display(plt::Plot{PlotlyJSBackend}) = display(plotlyjs_syncplot(plt))
@require WebIO = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29" begin
function WebIO.render(plt::Plot{PlotlyJSBackend})
prepare_output(plt)
WebIO.render(plt.o)
plt_html = sprint(show, MIME("text/html"), plt)
return WebIO.render(WebIO.dom"div"(innerHTML=plt_html))
end
end
+26 -21
View File
@@ -229,11 +229,6 @@ function fix_xy_lengths!(plt::Plot{PyPlotBackend}, series::Series)
end
end
py_linecolor(series::Series) = py_color(series[:linecolor])
py_markercolor(series::Series) = py_color(series[:markercolor])
py_markerstrokecolor(series::Series) = py_color(series[:markerstrokecolor])
py_fillcolor(series::Series) = py_color(series[:fillcolor])
py_linecolormap(series::Series) = py_colormap(series[:linecolor])
py_markercolormap(series::Series) = py_colormap(series[:markercolor])
py_fillcolormap(series::Series) = py_colormap(series[:fillcolor])
@@ -448,7 +443,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
# :norm => pycolors["Normalize"](; extrakw...)
# )
# lz = _cycle(series[:line_z], 1:n)
# handle = if is3d(st)
# handle = if RecipesPipeline.is3d(st)
# line_segments = [[(x[j], y[j], z[j]) for j in rng] for rng in segments]
# lc = pyart3d["Line3DCollection"](line_segments; kw...)
# lc[:set_array](lz)
@@ -478,7 +473,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
# end
a = series[:arrow]
if a !== nothing && !is3d(st) # TODO: handle 3d later
if a !== nothing && !RecipesPipeline.is3d(st) # TODO: handle 3d later
if typeof(a) != Arrow
@warn("Unexpected type for arrow: $(typeof(a))")
else
@@ -486,8 +481,8 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
:arrowstyle => "simple,head_length=$(a.headlength),head_width=$(a.headwidth)",
:shrinkA => 0,
:shrinkB => 0,
:edgecolor => py_linecolor(series),
:facecolor => py_linecolor(series),
:edgecolor => py_color(get_linecolor(series)),
:facecolor => py_color(get_linecolor(series)),
:linewidth => py_thickness_scale(plt, get_linewidth(series)),
:linestyle => py_linestyle(st, get_linestyle(series)),
)
@@ -533,16 +528,19 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
handle = []
x,y = xyargs
shapes = series[:markershape]
msc = py_markerstrokecolor(series)
msc = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series))
lw = py_thickness_scale(plt, series[:markerstrokewidth])
for i=eachindex(y)
extrakw[:c] = _cycle(markercolor, i)
if series[:marker_z] !== nothing
extrakw[:c] = [py_color(get_markercolor(series, i), get_markercoloralpha(series, i))]
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...
@@ -582,7 +580,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
zorder = series[:series_plotindex] + 0.5,
marker = prev_marker,
s = cur_scale_list,
edgecolors = py_markerstrokecolor(series),
edgecolors = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)), # Do we need include i?
linewidths = py_thickness_scale(plt, series[:markerstrokewidth]),
facecolors = cur_color_list,
extrakw...
@@ -603,7 +601,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
zorder = series[:series_plotindex] + 0.5,
marker = prev_marker,
s = cur_scale_list,
edgecolors = py_markerstrokecolor(series),
edgecolors = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)),
linewidths = py_thickness_scale(plt, series[:markerstrokewidth]),
facecolors = cur_color_list,
extrakw...
@@ -618,7 +616,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
zorder = series[:series_plotindex] + 0.5,
marker = py_marker(series[:markershape]),
s = py_thickness_scale(plt, series[:markersize] .^ 2),
edgecolors = py_markerstrokecolor(series),
edgecolors = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)),
linewidths = py_thickness_scale(plt, series[:markerstrokewidth]),
extrakw...
)
@@ -632,7 +630,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
zorder = series[:series_plotindex],
gridsize = series[:bins],
linewidths = py_thickness_scale(plt, series[:linewidth]),
edgecolors = py_linecolor(series),
edgecolors = py_color(get_linecolor(series)),
cmap = py_fillcolormap(series), # applies to the pcolorfast object
extrakw...
)
@@ -705,7 +703,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
rstride = series[:stride][1],
cstride = series[:stride][2],
linewidth = py_thickness_scale(plt, series[:linewidth]),
edgecolor = py_linecolor(series),
edgecolor = py_color(get_linecolor(series)),
extrakw...
)
push!(handles, handle)
@@ -731,7 +729,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
zorder = series[:series_plotindex],
cmap = py_fillcolormap(series),
linewidth = py_thickness_scale(plt, series[:linewidth]),
edgecolor = py_linecolor(series),
edgecolor = py_color(get_linecolor(series)),
extrakw...
)
push!(handles, handle)
@@ -1050,7 +1048,7 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
end
# framestyle
if !ispolar(sp) && !is3d(sp)
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))
if sp[:framestyle] == :semi
@@ -1110,6 +1108,13 @@ 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 (letter == :y && !RecipesPipeline.is3d(sp))
pyaxis."label"."set_rotation"(axis[:guidefontrotation] + 90)
else
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])
@@ -1155,13 +1160,13 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
end
# aspect ratio
aratio = sp[:aspect_ratio]
aratio = get_aspect_ratio(sp)
if aratio != :none
ax."set_aspect"(isa(aratio, Symbol) ? string(aratio) : aratio, anchor = "C")
end
#camera/view angle
if is3d(sp)
if RecipesPipeline.is3d(sp)
#convert azimuthal to match GR behaviour
#view_init(elevation, azimuthal) so reverse :camera args
ax."view_init"((sp[:camera].-(90,0))[end:-1:1]...)
@@ -1310,7 +1315,7 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
color = py_color(single_color(get_linecolor(series, clims)), get_linealpha(series)),
linewidth = py_thickness_scale(plt, clamp(get_linewidth(series), 0, 5)),
linestyle = py_linestyle(:path, get_linestyle(series)),
marker = py_marker(first(series[:markershape])),
marker = py_marker(_cycle(series[:markershape], 1)), # Need to adjust markersize to match the legend box size
markeredgecolor = py_color(single_color(get_markerstrokecolor(series)), get_markerstrokealpha(series)),
markerfacecolor = py_color(single_color(get_markercolor(series, clims)), get_markeralpha(series))
)
+1 -1
View File
@@ -126,7 +126,7 @@ function addUnicodeSeries!(o, plotattributes, addlegend::Bool, xlim, ylim)
color = plotattributes[:linecolor] in UnicodePlots.color_cycle ? plotattributes[:linecolor] : :auto
# add the series
x, y = Plots.unzip(collect(Base.Iterators.filter(xy->isfinite(xy[1])&&isfinite(xy[2]), zip(x,y))))
x, y = RecipesPipeline.unzip(collect(Base.Iterators.filter(xy->isfinite(xy[1])&&isfinite(xy[2]), zip(x,y))))
func(o, x, y; color = color, name = label)
end
+4 -48
View File
@@ -29,7 +29,7 @@ end
Construct a polygon to be plotted
"""
Shape(verts::AVec) = Shape(unzip(verts)...)
Shape(verts::AVec) = Shape(RecipesPipeline.unzip(verts)...)
Shape(s::Shape) = deepcopy(s)
get_xs(shape::Shape) = shape.x
@@ -594,7 +594,9 @@ function process_annotation(sp::Subplot, xs, ys, labs, font = font())
xlength = length(methods(length, (typeof(xs),))) == 0 ? 1 : length(xs)
ylength = length(methods(length, (typeof(ys),))) == 0 ? 1 : length(ys)
for i in 1:max(xlength, ylength, length(labs))
x, y, lab = _cycle(xs, i), _cycle(ys, i), _cycle(labs, i)
x, y, lab = _cycle(xs, i), _cycle(ys, i), _cycle(labs, i)
x = typeof(x) <: TimeType ? Dates.value(x) : x
y = typeof(y) <: TimeType ? Dates.value(y) : y
if lab == :auto
alphabet = "abcdefghijklmnopqrstuvwxyz"
push!(anns, (x, y, text(string("(", alphabet[sp[:subplot_index]], ")"), font)))
@@ -650,23 +652,6 @@ end
# -----------------------------------------------------------------------
abstract type AbstractSurface end
"represents a contour or surface mesh"
struct Surface{M<:AMat} <: AbstractSurface
surf::M
end
Surface(f::Function, x, y) = Surface(Float64[f(xi,yi) for yi in y, xi in x])
Base.Array(surf::Surface) = surf.surf
for f in (:length, :size)
@eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...)
end
Base.copy(surf::Surface) = Surface(copy(surf.surf))
Base.eltype(surf::Surface{T}) where {T} = eltype(T)
function expand_extrema!(a::Axis, surf::Surface)
ex = a[:extrema]
for vi in surf.surf
@@ -686,28 +671,7 @@ end
# # I don't want to clash with ValidatedNumerics, but this would be nice:
# ..(a::T, b::T) = (a,b)
struct Volume{T}
v::Array{T,3}
x_extents::Tuple{T,T}
y_extents::Tuple{T,T}
z_extents::Tuple{T,T}
end
default_extents(::Type{T}) where {T} = (zero(T), one(T))
function Volume(v::Array{T,3},
x_extents = default_extents(T),
y_extents = default_extents(T),
z_extents = default_extents(T)) where T
Volume(v, x_extents, y_extents, z_extents)
end
Base.Array(vol::Volume) = vol.v
for f in (:length, :size)
@eval Base.$f(vol::Volume, args...) = $f(vol.v, args...)
end
Base.copy(vol::Volume{T}) where {T} = Volume{T}(copy(vol.v), vol.x_extents, vol.y_extents, vol.z_extents)
Base.eltype(vol::Volume{T}) where {T} = T
# -----------------------------------------------------------------------
@@ -771,14 +735,6 @@ function add_arrows(func::Function, x::AVec, y::AVec)
end
end
# -----------------------------------------------------------------------
"Represents data values with formatting that should apply to the tick labels."
struct Formatted{T}
data::T
formatter::Function
end
# -----------------------------------------------------------------------
"create a BezierCurve for plotting"
mutable struct BezierCurve{T <: GeometryTypes.Point}
+1020 -518
View File
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -3,7 +3,7 @@ using REPL
function _plots_defaults()
if isdefined(Main, :PLOTS_DEFAULTS)
Dict{Symbol,Any}(Main.PLOTS_DEFAULTS)
copy(Dict{Symbol,Any}(Main.PLOTS_DEFAULTS))
else
Dict{Symbol,Any}()
end
@@ -13,11 +13,9 @@ end
function __init__()
user_defaults = _plots_defaults()
if haskey(user_defaults, :theme)
theme(user_defaults[:theme])
end
for (k,v) in user_defaults
k == :theme || default(k, v)
theme(pop!(user_defaults, :theme))
end
default(; user_defaults...)
insert!(Base.Multimedia.displays, findlast(x -> x isa Base.TextDisplay || x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1, PlotsDisplay())
+9
View File
@@ -52,6 +52,14 @@ function tex(plt::Plot, fn::AbstractString)
end
tex(fn::AbstractString) = tex(current(), fn)
function json(plt::Plot, fn::AbstractString)
fn = addExtension(fn, "json")
io = open(fn, "w")
show(io, MIME("application/vnd.plotly.v1+json"), plt)
close(io)
end
json(fn::AbstractString) = json(current(), fn)
function html(plt::Plot, fn::AbstractString)
fn = addExtension(fn, "html")
io = open(fn, "w")
@@ -78,6 +86,7 @@ const _savemap = Dict(
"ps" => ps,
"eps" => eps,
"tex" => tex,
"json" => json,
"html" => html,
"tikz" => tex,
"txt" => txt,
+141 -219
View File
@@ -1,105 +1,74 @@
# RecipesPipeline API
## Warnings
# ------------------------------------------------------------------
# preprocessing
function series_idx(kw_list::AVec{KW}, kw::AKW)
Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1
end
function _expand_seriestype_array(plotattributes::AKW, args)
sts = get(plotattributes, :seriestype, :path)
if typeof(sts) <: AbstractArray
reset_kw!(plotattributes, :seriestype)
rd = Vector{RecipeData}(undef, size(sts, 1))
for r in axes(sts, 1)
dc = copy(plotattributes)
dc[:seriestype] = sts[r:r,:]
rd[r] = RecipeData(dc, args)
end
rd
else
RecipeData[RecipeData(copy(plotattributes), args)]
end
end
function _preprocess_args(plotattributes::AKW, args, still_to_process::Vector{RecipeData})
# the grouping mechanism is a recipe on a GroupBy object
# we simply add the GroupBy object to the front of the args list to allow
# the recipe to be applied
if haskey(plotattributes, :group)
args = (extractGroupArgs(plotattributes[:group], args...), args...)
end
# if we were passed a vector/matrix of seriestypes and there's more than one row,
# we want to duplicate the inputs, once for each seriestype row.
if !isempty(args)
append!(still_to_process, _expand_seriestype_array(plotattributes, args))
end
# remove subplot and axis args from plotattributes... they will be passed through in the kw_list
if !isempty(args)
for (k,v) in plotattributes
for defdict in (_subplot_defaults,
_axis_defaults,
_axis_defaults_byletter[:x],
_axis_defaults_byletter[:y],
_axis_defaults_byletter[:z])
if haskey(defdict, k)
reset_kw!(plotattributes, k)
end
function RecipesPipeline.warn_on_recipe_aliases!(
plt::Plot,
plotattributes,
recipe_type,
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`."
end
plotattributes[dk] = RecipesPipeline.pop_kw!(plotattributes, k)
end
end
args
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
# ------------------------------------------------------------------
# user recipes
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)"
function _process_userrecipes(plt::Plot, plotattributes::AKW, args)
still_to_process = RecipeData[]
args = _preprocess_args(plotattributes, args, still_to_process)
## Grouping
# for plotting recipes, swap out the args and update the parameter dictionary
# we are keeping a stack of series that still need to be processed.
# each pass through the loop, we pop one off and apply the recipe.
# the recipe will return a list a Series objects... the ones that are
# finished (no more args) get added to the kw_list, the ones that are not
# are placed on top of the stack and are then processed further.
kw_list = KW[]
while !isempty(still_to_process)
# grab the first in line to be processed and either add it to the kw_list or
# pass it through apply_recipe to generate a list of RecipeData objects (data + attributes)
# for further processing.
next_series = popfirst!(still_to_process)
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
if !(typeof(next_series) <: RecipeData)
error("Inputs couldn't be processed... expected RecipeData but got: $next_series")
end
if isempty(next_series.args)
_process_userrecipe(plt, kw_list, next_series)
else
rd_list = RecipesBase.apply_recipe(next_series.plotattributes, next_series.args...)
prepend!(still_to_process,rd_list)
end
end
RecipesPipeline.splittable_attribute(plt::Plot, key, val::SeriesAnnotations, len) =
RecipesPipeline.splittable_attribute(plt, key, val.strs, len)
# don't allow something else to handle it
plotattributes[:smooth] = false
kw_list
function RecipesPipeline.split_attribute(plt::Plot, key, val::SeriesAnnotations, indices)
split_strs = _RecipesPipeline.split_attribute(key, val.strs, indices)
return SeriesAnnotations(split_strs, val.font, val.baseshape, val.scalefactor)
end
function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeData)
# when the arg tuple is empty, that means there's nothing left to recursively
# process... finish up and add to the kw_list
kw = recipedata.plotattributes
preprocessArgs!(kw)
## Preprocessing attributes
RecipesPipeline.preprocess_attributes!(plt::Plot, plotattributes) =
RecipesPipeline.preprocess_attributes!(plotattributes) # in src/args.jl
RecipesPipeline.is_axis_attribute(plt::Plot, attr) = is_axis_attr_noletter(attr) # in src/args.jl
RecipesPipeline.is_subplot_attribute(plt::Plot, attr) = is_subplot_attr(attr) # in src/args.jl
## User recipes
function RecipesPipeline.process_userrecipe!(plt::Plot, kw_list, kw)
_preprocess_userrecipe(kw)
warnOnUnsupported_scales(plt.backend, kw)
warn_on_unsupported_scales(plt.backend, kw)
# add the plot index
plt.n += 1
kw[:series_plotindex] = plt.n
@@ -113,18 +82,17 @@ end
function _preprocess_userrecipe(kw::AKW)
_add_markershape(kw)
# if there was a grouping, filter the data here
_filter_input_data!(kw)
# map marker_z if it's a Function
if isa(get(kw, :marker_z, nothing), Function)
# TODO: should this take y and/or z as arguments?
kw[:marker_z] = isa(kw[:z], Nothing) ? map(kw[:marker_z], kw[:x], kw[:y]) : map(kw[:marker_z], kw[:x], kw[:y], kw[:z])
kw[:marker_z] = isa(kw[:z], Nothing) ? map(kw[:marker_z], kw[:x], kw[:y]) :
map(kw[:marker_z], kw[:x], kw[:y], kw[:z])
end
# map line_z if it's a Function
if isa(get(kw, :line_z, nothing), Function)
kw[:line_z] = isa(kw[:z], Nothing) ? map(kw[:line_z], kw[:x], kw[:y]) : map(kw[:line_z], kw[:x], kw[:y], kw[:z])
kw[:line_z] = isa(kw[:z], Nothing) ? map(kw[:line_z], kw[:x], kw[:y]) :
map(kw[:line_z], kw[:x], kw[:y], kw[:z])
end
# convert a ribbon into a fillrange
@@ -137,7 +105,7 @@ end
function _add_errorbar_kw(kw_list::Vector{KW}, kw::AKW)
# handle error bars by creating new recipedata data... these will have
# the same recipedata index as the recipedata they are copied from
for esym in (:xerror, :yerror)
for esym in (:xerror, :yerror, :zerror)
if get(kw, esym, nothing) !== nothing
# we make a copy of the KW and apply an errorbar recipe
errkw = copy(kw)
@@ -156,58 +124,43 @@ function _add_smooth_kw(kw_list::Vector{KW}, kw::AKW)
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
sx = [ignorenan_minimum(x), ignorenan_maximum(x)]
sy = β .* sx .+ α
push!(kw_list, merge(copy(kw), KW(
:seriestype => :path,
:x => sx,
:y => sy,
:fillrange => nothing,
:label => "",
:primary => false,
)))
push!(
kw_list,
merge(
copy(kw),
KW(
:seriestype => :path,
:x => sx,
:y => sy,
:fillrange => nothing,
:label => "",
:primary => false,
),
),
)
end
end
# ------------------------------------------------------------------
# plot recipes
# Grab the first in line to be processed and pass it through apply_recipe
# to generate a list of RecipeData objects (data + attributes).
# If we applied a "plot recipe" without error, then add the returned datalist's KWs,
# otherwise we just add the original KW.
function _process_plotrecipe(plt::Plot, kw::AKW, kw_list::Vector{KW}, still_to_process::Vector{KW})
if !isa(get(kw, :seriestype, nothing), Symbol)
# seriestype was never set, or it's not a Symbol, so it can't be a plot recipe
push!(kw_list, kw)
return
end
try
st = kw[:seriestype]
st = kw[:seriestype] = get(_typeAliases, st, st)
datalist = RecipesBase.apply_recipe(kw, Val{st}, plt)
for data in datalist
preprocessArgs!(data.plotattributes)
if data.plotattributes[:seriestype] == st
error("Plot recipe $st returned the same seriestype: $(data.plotattributes)")
end
push!(still_to_process, data.plotattributes)
end
catch err
if isa(err, MethodError)
push!(kw_list, kw)
else
rethrow()
end
end
return
RecipesPipeline.get_axis_limits(plt::Plot, f, letter) = axis_limits(plt[1], letter)
## Plot recipes
RecipesPipeline.type_alias(plt::Plot) = get(_typeAliases, st, st)
## Plot setup
function RecipesPipeline.plot_setup!(plt::Plot, plotattributes, kw_list)
_plot_setup(plt, plotattributes, kw_list)
_subplot_setup(plt, plotattributes, kw_list)
end
# ------------------------------------------------------------------
# setup plot and subplot
# 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
for kw in kw_list, (k,v) in kw
for kw in kw_list, (k, v) in kw
haskey(_plot_defaults, k) && (plotattributes[k] = pop!(kw, k))
end
@@ -218,7 +171,7 @@ function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
# create the layout and subplots from the inputs
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
for (idx,sp) in enumerate(plt.subplots)
for (idx, sp) in enumerate(plt.subplots)
sp.plt = plt
sp.attr[:subplot_index] = idx
end
@@ -243,7 +196,7 @@ function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
else
parent = plt.layout
end
sp = Subplot(backend(), parent=parent)
sp = Subplot(backend(), parent = parent)
sp.plt = plt
push!(plt.subplots, sp)
push!(plt.inset_subplots, sp)
@@ -259,28 +212,34 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
# Subplot/Axis attributes set by a user/series recipe apply only to the
# Subplot object which they belong to.
# TODO: allow matrices to still apply to all subplots
sp_attrs = Dict{Subplot,Any}()
sp_attrs = Dict{Subplot, Any}()
for kw in kw_list
# get the Subplot object to which the series belongs.
sps = get(kw, :subplot, :auto)
sp = get_subplot(plt, _cycle(sps == :auto ? plt.subplots : plt.subplots[sps], series_idx(kw_list,kw)))
sp = get_subplot(
plt,
_cycle(
sps == :auto ? plt.subplots : plt.subplots[sps],
series_idx(kw_list, kw),
),
)
kw[:subplot] = sp
# extract subplot/axis attributes from kw and add to sp_attr
attr = KW()
for (k,v) in collect(kw)
for (k, v) in collect(kw)
if is_subplot_attr(k) || is_axis_attr(k)
attr[k] = pop!(kw, k)
end
if is_axis_attr_noletter(k)
v = pop!(kw, k)
for letter in (:x,:y,:z)
attr[Symbol(letter,k)] = v
for letter in (:x, :y, :z)
attr[Symbol(letter, k)] = v
end
end
for k in (:scale,), letter in (:x,:y,:z)
for k in (:scale,), letter in (:x, :y, :z)
# Series recipes may need access to this information
lk = Symbol(letter,k)
lk = Symbol(letter, k)
if haskey(attr, lk)
kw[lk] = attr[lk]
end
@@ -290,7 +249,7 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
end
# override subplot/axis args. `sp_attrs` take precendence
for (idx,sp) in enumerate(plt.subplots)
for (idx, sp) in enumerate(plt.subplots)
attr = if !haskey(plotattributes, :subplot) || plotattributes[:subplot] == idx
merge(plotattributes, get(sp_attrs, sp, KW()))
else
@@ -303,9 +262,34 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
link_axes!(plt.layout, plt[:link])
end
function series_idx(kw_list::AVec{KW}, kw::AKW)
Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1
end
## Series recipes
function RecipesPipeline.slice_series_attributes!(plt::Plot, kw_list, kw)
sp::Subplot = kw[:subplot]
# 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))
end
RecipesPipeline.series_defaults(plt::Plot) = _series_defaults # in args.jl
RecipesPipeline.is_seriestype_supported(plt::Plot, st) = is_seriestype_supported(st)
function RecipesPipeline.add_series!(plt::Plot, plotattributes)
sp = _prepare_subplot(plt, plotattributes)
_expand_subplot_extrema(sp, plotattributes, plotattributes[:seriestype])
_update_series_attributes!(plotattributes, plt, sp)
_add_the_series(plt, sp, plotattributes)
end
# getting ready to add the series... last update to subplot from anything
# that might have been added during series recipes
function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where T
function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where {T}
st::Symbol = plotattributes[:seriestype]
sp::Subplot{T} = plotattributes[:subplot]
sp_idx = get_subplot_index(plt, sp)
@@ -314,7 +298,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 is3d(st)
if RecipesPipeline.needs_3d_axes(st)
sp.attr[:projection] = "3d"
end
@@ -326,14 +310,12 @@ function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where T
sp
end
# ------------------------------------------------------------------
# series types
function _override_seriestype_check(plotattributes::AKW, st::Symbol)
# do we want to override the series type?
if !is3d(st) && !(st in (:contour,:contour3d))
if !RecipesPipeline.is3d(st) && !(st in (:contour, :contour3d))
z = plotattributes[:z]
if !isa(z, Nothing) && (size(plotattributes[:x]) == size(plotattributes[:y]) == size(z))
if !isa(z, Nothing) &&
(size(plotattributes[:x]) == size(plotattributes[:y]) == size(z))
st = (st == :scatter ? :scatter3d : :path3d)
plotattributes[:seriestype] = st
end
@@ -341,27 +323,11 @@ function _override_seriestype_check(plotattributes::AKW, st::Symbol)
st
end
function _prepare_annotations(sp::Subplot, plotattributes::AKW)
# strip out series annotations (those which are based on series x/y coords)
# and add them to the subplot attr
sp_anns = annotations(sp[:annotations])
# series_anns = annotations(pop!(plotattributes, :series_annotations, []))
# if isa(series_anns, SeriesAnnotations)
# series_anns.x = plotattributes[:x]
# series_anns.y = plotattributes[:y]
# elseif length(series_anns) > 0
# x, y = plotattributes[:x], plotattributes[:y]
# nx, ny, na = map(length, (x,y,series_anns))
# n = max(nx, ny, na)
# series_anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(series_anns[mod1(i,na)])) for i=1:n]
# end
# sp.attr[:annotations] = vcat(sp_anns, series_anns)
end
function _expand_subplot_extrema(sp::Subplot, plotattributes::AKW, st::Symbol)
# adjust extrema and discrete info
if st == :image
xmin, xmax = ignorenan_extrema(plotattributes[:x]); ymin, ymax = ignorenan_extrema(plotattributes[:y])
xmin, xmax = ignorenan_extrema(plotattributes[:x])
ymin, ymax = ignorenan_extrema(plotattributes[:y])
expand_extrema!(sp[:xaxis], (xmin, xmax))
expand_extrema!(sp[:yaxis], (ymin, ymax))
elseif !(st in (:pie, :histogram, :bins2d, :histogram2d))
@@ -375,54 +341,10 @@ function _expand_subplot_extrema(sp::Subplot, plotattributes::AKW, st::Symbol)
end
function _add_the_series(plt, sp, plotattributes)
warnOnUnsupported_args(plt.backend, plotattributes)
warnOnUnsupported(plt.backend, plotattributes)
warn_on_unsupported_args(plt.backend, plotattributes)
warn_on_unsupported(plt.backend, plotattributes)
series = Series(plotattributes)
push!(plt.series_list, series)
push!(sp.series_list, series)
_series_added(plt, series)
end
# -------------------------------------------------------------------------------
# this method recursively applies series recipes when the seriestype is not supported
# natively by the backend
function _process_seriesrecipe(plt::Plot, plotattributes::AKW)
#println("process $(typeof(plotattributes))")
# replace seriestype aliases
st = Symbol(plotattributes[:seriestype])
st = plotattributes[:seriestype] = get(_typeAliases, st, st)
# shapes shouldn't have fillrange set
if plotattributes[:seriestype] == :shape
plotattributes[:fillrange] = nothing
end
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
if is_seriestype_supported(st)
sp = _prepare_subplot(plt, plotattributes)
_prepare_annotations(sp, plotattributes)
_expand_subplot_extrema(sp, plotattributes, st)
_update_series_attributes!(plotattributes, plt, sp)
_add_the_series(plt, sp, plotattributes)
else
# get a sub list of series for this seriestype
datalist = RecipesBase.apply_recipe(plotattributes, Val{st}, plotattributes[:x], plotattributes[:y], plotattributes[:z])
# assuming there was no error, recursively apply the series recipes
for data in datalist
if isa(data, RecipeData)
preprocessArgs!(data.plotattributes)
if data.plotattributes[:seriestype] == st
error("The seriestype didn't change in series recipe $st. This will cause a StackOverflow.")
end
_process_seriesrecipe(plt, data.plotattributes)
else
@warn("Unhandled recipe: $(data)")
break
end
end
end
nothing
end
+6 -84
View File
@@ -49,7 +49,7 @@ as a String to look up its docstring; e.g. `plotattr("seriestype")`.
function plot(args...; kw...)
# this creates a new plot with args/kw and sets it to be the current plot
plotattributes = KW(kw)
preprocessArgs!(plotattributes)
RecipesPipeline.preprocess_attributes!(plotattributes)
# create an empty Plot then process
plt = Plot()
@@ -61,7 +61,7 @@ end
# note: we split into plt1 and plts_tail so we can dispatch correctly
function plot(plt1::Plot, plts_tail::Plot...; kw...)
plotattributes = KW(kw)
preprocessArgs!(plotattributes)
RecipesPipeline.preprocess_attributes!(plotattributes)
# build our plot vector from the args
n = length(plts_tail) + 1
@@ -153,7 +153,7 @@ end
# this adds to a specific plot... most plot commands will flow through here
function plot!(plt::Plot, args...; kw...)
plotattributes = KW(kw)
preprocessArgs!(plotattributes)
RecipesPipeline.preprocess_attributes!(plotattributes)
# merge!(plt.user_attr, plotattributes)
_plot!(plt, plotattributes, args)
end
@@ -163,87 +163,11 @@ end
# this is the core plotting function. recursively apply recipes to build
# 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::AKW, args::Tuple)
plotattributes[:plot_object] = plt
if !isempty(args) && !isdefined(Main, :StatsPlots) &&
first(split(string(typeof(args[1])), ".")) == "DataFrames"
@warn("You're trying to plot a DataFrame, but this functionality is provided by StatsPlots")
end
# --------------------------------
# "USER RECIPES"
# --------------------------------
kw_list = _process_userrecipes(plt, plotattributes, args)
# @info(1)
# map(DD, kw_list)
# --------------------------------
# "PLOT RECIPES"
# --------------------------------
# "plot recipe", which acts like a series type, and is processed before
# the plot layout is created, which allows for setting layouts and other plot-wide attributes.
# we get inputs which have been fully processed by "user recipes" and "type recipes",
# so we can expect standard vectors, surfaces, etc. No defaults have been set yet.
still_to_process = kw_list
kw_list = KW[]
while !isempty(still_to_process)
next_kw = popfirst!(still_to_process)
_process_plotrecipe(plt, next_kw, kw_list, still_to_process)
end
# @info(2)
# map(DD, kw_list)
# --------------------------------
# Plot/Subplot/Layout setup
# --------------------------------
_plot_setup(plt, plotattributes, kw_list)
_subplot_setup(plt, plotattributes, kw_list)
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!!
# !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
# --------------------------------
# "SERIES RECIPES"
# --------------------------------
# @info(3)
# map(DD, kw_list)
for kw in kw_list
sp::Subplot = kw[:subplot]
# 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))
series_attr = Attr(kw, _series_defaults)
# now we have a fully specified series, with colors chosen. we must recursively handle
# series recipes, which dispatch on seriestype. If a backend does not natively support a seriestype,
# we check for a recipe that will convert that series type into one made up of lower-level components.
# For example, a histogram is just a bar plot with binned data, a bar plot is really a filled step plot,
# and a step plot is really just a path. So any backend that supports drawing a path will implicitly
# be able to support step, bar, and histogram plots (and any recipes that use those components).
_process_seriesrecipe(plt, series_attr)
end
# --------------------------------
function _plot!(plt::Plot, plotattributes, args)
RecipesPipeline.recipe_pipeline!(plt, plotattributes, args)
current(plt)
# do we want to force display?
# if plt[:show]
# gui(plt)
# end
_do_plot_show(plt, plt[:show])
plt
return plt
end
@@ -288,5 +212,3 @@ function plot!(sp::Subplot, args...; kw...)
plt = sp.plt
plot!(plt, args...; kw..., subplot = findfirst(isequal(sp), plt.subplots))
end
# --------------------------------------------------------------------
+339 -387
View File
File diff suppressed because it is too large Load Diff
+466 -159
View File
File diff suppressed because it is too large Load Diff
-651
View File
@@ -1,651 +0,0 @@
# create a new "build_series_args" which converts all inputs into xs = Any[xitems], ys = Any[yitems].
# Special handling for: no args, xmin/xmax, parametric, dataframes
# Then once inputs have been converted, build the series args, map functions, etc.
# This should cut down on boilerplate code and allow more focused dispatch on type
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now
const FuncOrFuncs{F} = Union{F, Vector{F}, Matrix{F}}
const MaybeNumber = Union{Number, Missing}
const MaybeString = Union{AbstractString, Missing}
const DataPoint = Union{MaybeNumber, MaybeString}
prepareSeriesData(x) = error("Cannot convert $(typeof(x)) to series data for plotting")
prepareSeriesData(::Nothing) = nothing
prepareSeriesData(t::Tuple{T, T}) where {T<:Number} = t
prepareSeriesData(f::Function) = f
prepareSeriesData(a::AbstractArray{<:MaybeNumber}) = replace!(
x -> ismissing(x) || isinf(x) ? NaN : x,
map(float,a))
prepareSeriesData(a::AbstractArray{<:MaybeString}) = replace(x -> ismissing(x) ? "" : x, a)
prepareSeriesData(s::Surface{<:AMat{<:MaybeNumber}}) = Surface(prepareSeriesData(s.surf))
prepareSeriesData(s::Surface) = s # non-numeric Surface, such as an image
prepareSeriesData(v::Volume) = Volume(prepareSeriesData(v.v), v.x_extents, v.y_extents, v.z_extents)
# default: assume x represents a single series
convertToAnyVector(x, plotattributes) = Any[prepareSeriesData(x)]
# fixed number of blank series
convertToAnyVector(n::Integer, plotattributes) = Any[zeros(0) for i in 1:n]
# vector of data points is a single series
convertToAnyVector(v::AVec{<:DataPoint}, plotattributes) = Any[prepareSeriesData(v)]
# list of things (maybe other vectors, functions, or something else)
function convertToAnyVector(v::AVec, plotattributes)
if all(x -> x isa MaybeNumber, v)
convertToAnyVector(Vector{MaybeNumber}(v), plotattributes)
elseif all(x -> x isa MaybeString, v)
convertToAnyVector(Vector{MaybeString}(v), plotattributes)
else
vcat((convertToAnyVector(vi, plotattributes) for vi in v)...)
end
end
# Matrix is split into columns
function convertToAnyVector(v::AMat{<:DataPoint}, plotattributes)
if all3D(plotattributes)
Any[prepareSeriesData(Surface(v))]
else
Any[prepareSeriesData(v[:, i]) for i in axes(v, 2)]
end
end
# --------------------------------------------------------------------
# Fillranges & ribbons
process_fillrange(range::Number, plotattributes) = [range]
process_fillrange(range, plotattributes) = convertToAnyVector(range, plotattributes)
process_ribbon(ribbon::Number, plotattributes) = [ribbon]
process_ribbon(ribbon, plotattributes) = convertToAnyVector(ribbon, plotattributes)
# ribbon as a tuple: (lower_ribbons, upper_ribbons)
process_ribbon(ribbon::Tuple{Any,Any}, plotattributes) = collect(zip(convertToAnyVector(ribbon[1], plotattributes),
convertToAnyVector(ribbon[2], plotattributes)))
# --------------------------------------------------------------------
# TODO: can we avoid the copy here? one error that crops up is that mapping functions over the same array
# result in that array being shared. push!, etc will add too many items to that array
compute_x(x::Nothing, y::Nothing, z) = axes(z,1)
compute_x(x::Nothing, y, z) = axes(y,1)
compute_x(x::Function, y, z) = map(x, y)
compute_x(x, y, z) = copy(x)
# compute_y(x::Void, y::Function, z) = error()
compute_y(x::Nothing, y::Nothing, z) = axes(z,2)
compute_y(x, y::Function, z) = map(y, x)
compute_y(x, y, z) = copy(y)
compute_z(x, y, z::Function) = map(z, x, y)
compute_z(x, y, z::AbstractMatrix) = Surface(z)
compute_z(x, y, z::Nothing) = nothing
compute_z(x, y, z) = copy(z)
nobigs(v::AVec{BigFloat}) = map(Float64, v)
nobigs(v::AVec{BigInt}) = map(Int64, v)
nobigs(v) = v
@noinline function compute_xyz(x, y, z)
x = compute_x(x,y,z)
y = compute_y(x,y,z)
z = compute_z(x,y,z)
nobigs(x), nobigs(y), nobigs(z)
end
# not allowed
compute_xyz(x::Nothing, y::FuncOrFuncs{F}, z) where {F<:Function} = error("If you want to plot the function `$y`, you need to define the x values!")
compute_xyz(x::Nothing, y::Nothing, z::FuncOrFuncs{F}) where {F<:Function} = error("If you want to plot the function `$z`, you need to define x and y values!")
compute_xyz(x::Nothing, y::Nothing, z::Nothing) = error("x/y/z are all nothing!")
# --------------------------------------------------------------------
# we are going to build recipes to do the processing and splitting of the args
# ensure we dispatch to the slicer
struct SliceIt end
# the catch-all recipes
@recipe function f(::Type{SliceIt}, x, y, z)
# handle data with formatting attached
if typeof(x) <: Formatted
xformatter := x.formatter
x = x.data
end
if typeof(y) <: Formatted
yformatter := y.formatter
y = y.data
end
if typeof(z) <: Formatted
zformatter := z.formatter
z = z.data
end
xs = convertToAnyVector(x, plotattributes)
ys = convertToAnyVector(y, plotattributes)
zs = convertToAnyVector(z, plotattributes)
fr = pop!(plotattributes, :fillrange, nothing)
fillranges = process_fillrange(fr, plotattributes)
mf = length(fillranges)
rib = pop!(plotattributes, :ribbon, nothing)
ribbons = process_ribbon(rib, plotattributes)
mr = length(ribbons)
# @show zs
mx = length(xs)
my = length(ys)
mz = length(zs)
if mx > 0 && my > 0 && mz > 0
for i in 1:max(mx, my, mz)
# add a new series
di = copy(plotattributes)
xi, yi, zi = xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]
di[:x], di[:y], di[:z] = compute_xyz(xi, yi, zi)
# handle fillrange
fr = fillranges[mod1(i,mf)]
di[:fillrange] = isa(fr, Function) ? map(fr, di[:x]) : fr
# handle ribbons
rib = ribbons[mod1(i,mr)]
di[:ribbon] = isa(rib, Function) ? map(rib, di[:x]) : rib
push!(series_list, RecipeData(di, ()))
end
end
nothing # don't add a series for the main block
end
# this is the default "type recipe"... just pass the object through
@recipe f(::Type{T}, v::T) where {T<:Any} = v
# this should catch unhandled "series recipes" and error with a nice message
@recipe f(::Type{V}, x, y, z) where {V<:Val} = error("The backend must not support the series type $V, and there isn't a series recipe defined.")
_apply_type_recipe(plotattributes, v) = RecipesBase.apply_recipe(plotattributes, typeof(v), v)[1].args[1]
# Handle type recipes when the recipe is defined on the elements.
# This sort of recipe should return a pair of functions... one to convert to number,
# and one to format tick values.
function _apply_type_recipe(plotattributes, v::AbstractArray)
isempty(skipmissing(v)) && return Float64[]
x = first(skipmissing(v))
args = RecipesBase.apply_recipe(plotattributes, typeof(x), x)[1].args
if length(args) == 2 && typeof(args[1]) <: Function && typeof(args[2]) <: Function
numfunc, formatter = args
Formatted(map(numfunc, v), formatter)
else
v
end
end
# # special handling for Surface... need to properly unwrap and re-wrap
# function _apply_type_recipe(plotattributes, v::Surface)
# T = eltype(v.surf)
# @show T
# if T <: Integer || T <: AbstractFloat
# v
# else
# ret = _apply_type_recipe(plotattributes, v.surf)
# if typeof(ret) <: Formatted
# Formatted(Surface(ret.data), ret.formatter)
# else
# v
# end
# end
# end
# don't do anything for ints or floats
_apply_type_recipe(plotattributes, v::AbstractArray{T}) where {T<:Union{Integer,AbstractFloat}} = v
# handle "type recipes" by converting inputs, and then either re-calling or slicing
@recipe function f(x, y, z)
did_replace = false
newx = _apply_type_recipe(plotattributes, x)
x === newx || (did_replace = true)
newy = _apply_type_recipe(plotattributes, y)
y === newy || (did_replace = true)
newz = _apply_type_recipe(plotattributes, z)
z === newz || (did_replace = true)
if did_replace
newx, newy, newz
else
SliceIt, x, y, z
end
end
@recipe function f(x, y)
did_replace = false
newx = _apply_type_recipe(plotattributes, x)
x === newx || (did_replace = true)
newy = _apply_type_recipe(plotattributes, y)
y === newy || (did_replace = true)
if did_replace
newx, newy
else
SliceIt, x, y, nothing
end
end
@recipe function f(y)
newy = _apply_type_recipe(plotattributes, y)
if y !== newy
newy
else
SliceIt, nothing, y, nothing
end
end
# if there's more than 3 inputs, it can't be passed directly to SliceIt
# so we'll apply_type_recipe to all of them
@recipe function f(v1, v2, v3, v4, vrest...)
did_replace = false
newargs = map(v -> begin
newv = _apply_type_recipe(plotattributes, v)
if newv !== v
did_replace = true
end
newv
end, (v1, v2, v3, v4, vrest...))
if !did_replace
error("Couldn't process recipe args: $(map(typeof, (v1, v2, v3, v4, vrest...)))")
end
newargs
end
# # --------------------------------------------------------------------
# # 1 argument
# # --------------------------------------------------------------------
# helper function to ensure relevant attributes are wrapped by Surface
function wrap_surfaces(plotattributes::AKW)
if haskey(plotattributes, :fill_z)
v = plotattributes[:fill_z]
if !isa(v, Surface)
plotattributes[:fill_z] = Surface(v)
end
end
end
@recipe f(n::Integer) = is3d(get(plotattributes,:seriestype,:path)) ? (SliceIt, n, n, n) : (SliceIt, n, n, nothing)
all3D(plotattributes) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image, :plots_heatmap), get(plotattributes, :seriestype, :none))
# return a surface if this is a 3d plot, otherwise let it be sliced up
@recipe function f(mat::AMat{T}) where T<:Union{Integer,AbstractFloat,Missing}
if all3D(plotattributes)
n,m = axes(mat)
wrap_surfaces(plotattributes)
SliceIt, m, n, Surface(mat)
else
SliceIt, nothing, mat, nothing
end
end
# if a matrix is wrapped by Formatted, do similar logic, but wrap data with Surface
@recipe function f(fmt::Formatted{T}) where T<:AbstractMatrix
if all3D(plotattributes)
mat = fmt.data
n,m = axes(mat)
wrap_surfaces(plotattributes)
SliceIt, m, n, Formatted(Surface(mat), fmt.formatter)
else
SliceIt, nothing, fmt, nothing
end
end
# assume this is a Volume, so construct one
@recipe function f(vol::AbstractArray{T,3}, args...) where T<:Union{Number,Missing}
seriestype := :volume
SliceIt, nothing, Volume(vol, args...), nothing
end
# # images - grays
function clamp_greys!(mat::AMat{T}) where T<:Gray
for i in eachindex(mat)
mat[i].val < 0 && (mat[i] = Gray(0))
mat[i].val > 1 && (mat[i] = Gray(1))
end
mat
end
@recipe function f(mat::AMat{T}) where T<:Gray
n, m = axes(mat)
if is_seriestype_supported(:image)
seriestype := :image
yflip --> true
SliceIt, m, n, Surface(clamp_greys!(mat))
else
seriestype := :heatmap
yflip --> true
cbar --> false
fillcolor --> ColorGradient([:black, :white])
SliceIt, m, n, Surface(clamp!(convert(Matrix{Float64}, mat), 0., 1.))
end
end
# # images - colors
@recipe function f(mat::AMat{T}) where T<:Colorant
n, m = axes(mat)
if is_seriestype_supported(:image)
seriestype := :image
yflip --> true
SliceIt, m, n, Surface(mat)
else
seriestype := :heatmap
yflip --> true
cbar --> false
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
SliceIt, m, n, Surface(z)
end
end
#
# # plotting arbitrary shapes/polygons
@recipe function f(shape::Shape)
seriestype --> :shape
coords(shape)
end
@recipe function f(shapes::AVec{Shape})
seriestype --> :shape
coords(shapes)
end
@recipe function f(shapes::AMat{Shape})
seriestype --> :shape
for j in axes(shapes,2)
@series coords(vec(shapes[:,j]))
end
end
# Dicts: each entry is a data point (x,y)=(key,value)
@recipe f(d::AbstractDict) = collect(keys(d)), collect(values(d))
# function without range... use the current range of the x-axis
@recipe function f(f::FuncOrFuncs{F}) where F<:Function
plt = plotattributes[:plot_object]
xmin, xmax = if haskey(plotattributes, :xlims)
plotattributes[:xlims]
else
try
axis_limits(plt[1], :x)
catch
xinv = invscalefunc(get(plotattributes, :xscale, :identity))
xm = tryrange(f, xinv.([-5,-1,0,0.01]))
xm, tryrange(f, filter(x->x>xm, xinv.([5,1,0.99, 0, -0.01])))
end
end
f, xmin, xmax
end
# try some intervals over which the function may be defined
function tryrange(F::AbstractArray, vec)
rets = [tryrange(f, vec) for f in F] # get the preferred for each
maxind = maximum(indexin(rets, vec)) # get the last attempt that succeeded (most likely to fit all)
rets .= [tryrange(f, vec[maxind:maxind]) for f in F] # ensure that all functions compute there
rets[1]
end
function tryrange(F, vec)
for v in vec
try
tmp = F(v)
return v
catch
end
end
error("$F is not a Function, or is not defined at any of the values $vec")
end
#
# # --------------------------------------------------------------------
# # 2 arguments
# # --------------------------------------------------------------------
#
#
# # if functions come first, just swap the order (not to be confused with parametric functions...
# # as there would be more than one function passed in)
@recipe function f(f::FuncOrFuncs{F}, x) where F<:Function
F2 = typeof(x)
@assert !(F2 <: Function || (F2 <: AbstractArray && F2.parameters[1] <: Function)) # otherwise we'd hit infinite recursion here
x, f
end
#
# # --------------------------------------------------------------------
# # 3 arguments
# # --------------------------------------------------------------------
#
#
# # 3d line or scatter
@recipe function f(x::AVec, y::AVec, z::AVec)
# st = get(plotattributes, :seriestype, :none)
# if st == :scatter
# plotattributes[:seriestype] = :scatter3d
# elseif !is3d(st)
# plotattributes[:seriestype] = :path3d
# end
SliceIt, x, y, z
end
@recipe function f(x::AMat, y::AMat, z::AMat)
# st = get(plotattributes, :seriestype, :none)
# if size(x) == size(y) == size(z)
# if !is3d(st)
# seriestype := :path3d
# end
# end
wrap_surfaces(plotattributes)
SliceIt, x, y, z
end
#
# # surface-like... function
@recipe function f(x::AVec, y::AVec, zf::Function)
# x = X <: Number ? sort(x) : x
# y = Y <: Number ? sort(y) : y
wrap_surfaces(plotattributes)
SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
end
#
# # surface-like... matrix grid
@recipe function f(x::AVec, y::AVec, z::AMat)
if !like_surface(get(plotattributes, :seriestype, :none))
plotattributes[:seriestype] = :contour
end
wrap_surfaces(plotattributes)
SliceIt, x, y, Surface(z)
end
# # images - grays
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Gray
if is_seriestype_supported(:image)
seriestype := :image
yflip --> true
SliceIt, x, y, Surface(mat)
else
seriestype := :heatmap
yflip --> true
cbar --> false
fillcolor --> ColorGradient([:black, :white])
SliceIt, x, y, Surface(convert(Matrix{Float64}, mat))
end
end
# # images - colors
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Colorant
if is_seriestype_supported(:image)
seriestype := :image
yflip --> true
SliceIt, x, y, Surface(mat)
else
seriestype := :heatmap
yflip --> true
cbar --> false
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
SliceIt, x, y, Surface(z)
end
end
#
#
# # --------------------------------------------------------------------
# # Parametric functions
# # --------------------------------------------------------------------
#
# # special handling... xmin/xmax with parametric function(s)
@recipe function f(f::Function, xmin::Number, xmax::Number)
xscale, yscale = [get(plotattributes, sym, :identity) for sym=(:xscale,:yscale)]
_scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
end
@recipe function f(fs::AbstractArray{F}, xmin::Number, xmax::Number) where F<:Function
xscale, yscale = [get(plotattributes, sym, :identity) for sym=(:xscale,:yscale)]
xs = Array{Any}(undef, length(fs))
ys = Array{Any}(undef, length(fs))
for (i, (x, y)) in enumerate(_scaled_adapted_grid(f, xscale, yscale, xmin, xmax) for f in fs)
xs[i] = x
ys[i] = y
end
xs, ys
end
@recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, u::AVec) where {F<:Function,G<:Function} = mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u)
@recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, umin::Number, umax::Number, n = 200) where {F<:Function,G<:Function} = fx, fy, range(umin, stop = umax, length = n)
function _scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
(xf, xinv), (yf, yinv) = ((scalefunc(s),invscalefunc(s)) for s in (xscale,yscale))
xs, ys = adapted_grid(yf∘f∘xinv, xf.((xmin, xmax)))
xinv.(xs), yinv.(ys)
end
#
# # special handling... 3D parametric function(s)
@recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, u::AVec) where {F<:Function,G<:Function,H<:Function}
mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u)
end
@recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, umin::Number, umax::Number, numPoints = 200) where {F<:Function,G<:Function,H<:Function}
fx, fy, fz, range(umin, stop = umax, length = numPoints)
end
#
#
# # --------------------------------------------------------------------
# # Lists of tuples and GeometryTypes.Points
# # --------------------------------------------------------------------
#
@recipe f(v::AVec{<:Tuple}) = unzip(v)
@recipe f(v::AVec{<:GeometryTypes.Point}) = unzip(v)
@recipe f(tup::Tuple) = [tup]
@recipe f(p::GeometryTypes.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] : unzip(xyuv)
#
# # --------------------------------------------------------------------
# # handle grouping
# # --------------------------------------------------------------------
# @recipe function f(groupby::GroupBy, args...)
# for (i,glab) in enumerate(groupby.groupLabels)
# # create a new series, with the label of the group, and an idxfilter (to be applied in slice_and_dice)
# # TODO: use @series instead
# @show i, glab, groupby.groupIds[i]
# di = copy(plotattributes)
# get!(di, :label, string(glab))
# get!(di, :idxfilter, groupby.groupIds[i])
# push!(series_list, RecipeData(di, args))
# end
# nothing
# end
splittable_kw(key, val, lengthGroup) = false
splittable_kw(key, val::AbstractArray, lengthGroup) = !(key in (:group, :color_palette)) && length(axes(val,1)) == lengthGroup
splittable_kw(key, val::Tuple, lengthGroup) = all(splittable_kw.(key, val, lengthGroup))
splittable_kw(key, val::SeriesAnnotations, lengthGroup) = splittable_kw(key, val.strs, lengthGroup)
split_kw(key, val::AbstractArray, indices) = val[indices, fill(Colon(), ndims(val)-1)...]
split_kw(key, val::Tuple, indices) = Tuple(split_kw(key, v, indices) for v in val)
function split_kw(key, val::SeriesAnnotations, indices)
split_strs = split_kw(key, val.strs, indices)
return SeriesAnnotations(split_strs, val.font, val.baseshape, val.scalefactor)
end
function groupedvec2mat(x_ind, x, y::AbstractArray, groupby, def_val = y[1])
y_mat = Array{promote_type(eltype(y), typeof(def_val))}(undef, length(keys(x_ind)), length(groupby.groupLabels))
fill!(y_mat, def_val)
for i in eachindex(groupby.groupLabels)
xi = x[groupby.groupIds[i]]
yi = y[groupby.groupIds[i]]
y_mat[getindex.(Ref(x_ind), xi), i] = yi
end
return y_mat
end
groupedvec2mat(x_ind, x, y::Tuple, groupby) = Tuple(groupedvec2mat(x_ind, x, v, groupby) for v in y)
group_as_matrix(t) = false
# split the group into 1 series per group, and set the label and idxfilter for each
@recipe function f(groupby::GroupBy, args...)
lengthGroup = maximum(union(groupby.groupIds...))
if !(group_as_matrix(args[1]))
for (i,glab) in enumerate(groupby.groupLabels)
@series begin
label --> string(glab)
idxfilter --> groupby.groupIds[i]
for (key,val) in plotattributes
if splittable_kw(key, val, lengthGroup)
:($key) := split_kw(key, val, groupby.groupIds[i])
end
end
args
end
end
else
g = args[1]
if length(g.args) == 1
x = zeros(Int, lengthGroup)
for indexes in groupby.groupIds
x[indexes] = eachindex(indexes)
end
last_args = g.args
else
x = g.args[1]
last_args = g.args[2:end]
end
x_u = unique(sort(x))
x_ind = Dict(zip(x_u, eachindex(x_u)))
for (key,val) in plotattributes
if splittable_kw(key, val, lengthGroup)
:($key) := groupedvec2mat(x_ind, x, val, groupby)
end
end
label --> reshape(groupby.groupLabels, 1, :)
typeof(g)((x_u, (groupedvec2mat(x_ind, x, arg, groupby, NaN) for arg in last_args)...))
end
end
+4 -3
View File
@@ -271,6 +271,7 @@ Draw a 3D surface plot.
# Example
```julia-repl
julia> using LinearAlgebra
julia> x = y = range(-3, 3, length = 100)
julia> surface(x, y, (x, y) -> sinc(norm([x, y])))
```
@@ -428,8 +429,8 @@ Add annotations to an existing plot.
# Arguments
- `anns`: An `AbstractVector` of tuples of the form (x,y,text). The text object
can be an String or PlotText
- `anns`: An `AbstractVector` of tuples of the form `(x,y,text)`. The `text` object
can be a `String` or `PlotText`.
# Example
```julia-repl
@@ -437,7 +438,7 @@ julia> plot(1:10)
julia> annotate!([(7,3,"(7,3)"),(3,7,text("hey", 14, :left, :top, :green))])
```
"""
annotate!(anns...; kw...) = plot!(; annotation = anns, kw...)
annotate!(anns...; kw...) = plot!(; annotation = collect(anns), kw...)
annotate!(anns::AVec{T}; kw...) where {T<:Tuple} = plot!(; annotation = anns, kw...)
"Flip the current plots' x axis"
+1 -1
View File
@@ -7,7 +7,7 @@ function Subplot(::T; parent = RootLayout()) where T<:AbstractBackend
(20mm, 5mm, 2mm, 10mm),
defaultbox,
defaultbox,
Attr(KW(), _subplot_defaults),
DefaultsDict(KW(), _subplot_defaults),
nothing,
nothing
)
+5 -59
View File
@@ -18,64 +18,10 @@ end
wrap(obj::T) where {T} = InputWrapper{T}(obj)
Base.isempty(wrapper::InputWrapper) = false
# -----------------------------------------------------------
struct Attr <: AbstractDict{Symbol,Any}
explicit::KW
defaults::KW
end
Base.getindex(attr::Attr, k) = haskey(attr.explicit,k) ?
attr.explicit[k] : attr.defaults[k]
Base.haskey(attr::Attr, k) = haskey(attr.explicit,k) || haskey(attr.defaults,k)
Base.get(attr::Attr, k, default) = haskey(attr, k) ? attr[k] : default
function Base.get!(attr::Attr, k, default)
v = if haskey(attr, k)
attr[k]
else
attr.defaults[k] = default
end
return v
end
function Base.delete!(attr::Attr, k)
haskey(attr.explicit, k) && delete!(attr.explicit, k)
haskey(attr.defaults, k) && delete!(attr.defaults, k)
end
Base.length(attr::Attr) = length(union(keys(attr.explicit), keys(attr.defaults)))
function Base.iterate(attr::Attr)
exp_keys = keys(attr.explicit)
def_keys = setdiff(keys(attr.defaults), exp_keys)
key_list = collect(Iterators.flatten((exp_keys, def_keys)))
iterate(attr, (key_list, 1))
end
function Base.iterate(attr::Attr, (key_list, i))
i > length(key_list) && return nothing
k = key_list[i]
(k=>attr[k], (key_list, i+1))
end
Base.copy(attr::Attr) = Attr(copy(attr.explicit), attr.defaults)
RecipesBase.is_explicit(attr::Attr, k) = haskey(attr.explicit,k)
isdefault(attr::Attr, k) = !is_explicit(attr,k) && haskey(attr.defaults,k)
Base.setindex!(attr::Attr, v, k) = attr.explicit[k] = v
# Reset to default value and return dict
reset_kw!(attr::Attr, k) = is_explicit(attr, k) ? delete!(attr.explicit, k) : attr
# Reset to default value and return old value
pop_kw!(attr::Attr, k) = is_explicit(attr, k) ? pop!(attr.explicit, k) : attr.defaults[k]
pop_kw!(attr::Attr, k, default) = is_explicit(attr, k) ? pop!(attr.explicit, k) : get(attr.defaults, k, default)
# Fallbacks for dicts without defaults
reset_kw!(d::AKW, k) = delete!(d, k)
pop_kw!(d::AKW, k) = pop!(d, k)
pop_kw!(d::AKW, k, default) = pop!(d, k, default)
# -----------------------------------------------------------
mutable struct Series
plotattributes::Attr
plotattributes::DefaultsDict
end
attr(series::Series, k::Symbol) = series.plotattributes[k]
@@ -90,7 +36,7 @@ mutable struct Subplot{T<:AbstractBackend} <: AbstractLayout
minpad::Tuple # leftpad, toppad, rightpad, bottompad
bbox::BoundingBox # the canvas area which is available to this subplot
plotarea::BoundingBox # the part where the data goes
attr::Attr # args specific to this subplot
attr::DefaultsDict # args specific to this subplot
o # can store backend-specific data... like a pyplot ax
plt # the enclosing Plot object (can't give it a type because of no forward declarations)
end
@@ -102,7 +48,7 @@ Base.show(io::IO, sp::Subplot) = print(io, "Subplot{$(sp[:subplot_index])}")
# simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place
mutable struct Axis
sps::Vector{Subplot}
plotattributes::Attr
plotattributes::DefaultsDict
end
mutable struct Extrema
@@ -121,7 +67,7 @@ const SubplotMap = Dict{Any, Subplot}
mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
backend::T # the backend type
n::Int # number of series
attr::Attr # arguments for the whole plot
attr::DefaultsDict # arguments for the whole plot
series_list::Vector{Series} # arguments for each series
o # the backend's plot object
subplots::Vector{Subplot}
@@ -132,7 +78,7 @@ mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
end
function Plot()
Plot(backend(), 0, Attr(KW(), _plot_defaults), Series[], nothing,
Plot(backend(), 0, DefaultsDict(KW(), _plot_defaults), Series[], nothing,
Subplot[], SubplotMap(), EmptyLayout(),
Subplot[], false)
end
+185 -329
View File
@@ -1,139 +1,11 @@
calcMidpoints(edges::AbstractVector) = Float64[0.5 * (edges[i] + edges[i+1]) for i in 1:length(edges)-1]
"Make histogram-like bins of data"
function binData(data, nbins)
lo, hi = ignorenan_extrema(data)
edges = collect(range(lo, stop=hi, length=nbins+1))
midpoints = calcMidpoints(edges)
buckets = Int[max(2, min(searchsortedfirst(edges, x), length(edges)))-1 for x in data]
counts = zeros(Int, length(midpoints))
for b in buckets
counts[b] += 1
end
edges, midpoints, buckets, counts
end
"""
A hacky replacement for a histogram when the backend doesn't support histograms directly.
Convert it into a bar chart with the appropriate x/y values.
"""
function histogramHack(; kw...)
plotattributes = KW(kw)
# we assume that the y kwarg is set with the data to be binned, and nbins is also defined
edges, midpoints, buckets, counts = binData(plotattributes[:y], plotattributes[:bins])
plotattributes[:x] = midpoints
plotattributes[:y] = float(counts)
plotattributes[:seriestype] = :bar
plotattributes[:fillrange] = plotattributes[:fillrange] === nothing ? 0.0 : plotattributes[:fillrange]
plotattributes
end
"""
A hacky replacement for a bar graph when the backend doesn't support bars directly.
Convert it into a line chart with fillrange set.
"""
function barHack(; kw...)
plotattributes = KW(kw)
midpoints = plotattributes[:x]
heights = plotattributes[:y]
fillrange = plotattributes[:fillrange] === nothing ? 0.0 : plotattributes[:fillrange]
# estimate the edges
dists = diff(midpoints) * 0.5
edges = zeros(length(midpoints)+1)
for i in eachindex(edges)
if i == 1
edge = midpoints[1] - dists[1]
elseif i == length(edges)
edge = midpoints[i-1] + dists[i-2]
else
edge = midpoints[i-1] + dists[i-1]
end
edges[i] = edge
end
x = Float64[]
y = Float64[]
for i in eachindex(heights)
e1, e2 = edges[i:i+1]
append!(x, [e1, e1, e2, e2])
append!(y, [fillrange, heights[i], heights[i], fillrange])
end
plotattributes[:x] = x
plotattributes[:y] = y
plotattributes[:seriestype] = :path
plotattributes[:fillrange] = fillrange
plotattributes
end
"""
A hacky replacement for a sticks graph when the backend doesn't support sticks directly.
Convert it into a line chart that traces the sticks, and a scatter that sets markers at the points.
"""
function sticksHack(; kw...)
plotattributesLine = KW(kw)
plotattributesScatter = copy(plotattributesLine)
# these are the line vertices
x = Float64[]
y = Float64[]
fillrange = plotattributesLine[:fillrange] === nothing ? 0.0 : plotattributesLine[:fillrange]
# calculate the vertices
yScatter = plotattributesScatter[:y]
for (i,xi) in enumerate(plotattributesScatter[:x])
yi = yScatter[i]
for j in 1:3 push!(x, xi) end
append!(y, [fillrange, yScatter[i], fillrange])
end
# change the line args
plotattributesLine[:x] = x
plotattributesLine[:y] = y
plotattributesLine[:seriestype] = :path
plotattributesLine[:markershape] = :none
plotattributesLine[:fillrange] = nothing
# change the scatter args
plotattributesScatter[:seriestype] = :none
plotattributesLine, plotattributesScatter
end
function regressionXY(x, y)
# regress
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
# make a line segment
regx = [ignorenan_minimum(x), ignorenan_maximum(x)]
regy = β * regx + α
regx, regy
end
function replace_image_with_heatmap(z::Array{T}) where T<:Colorant
n, m = size(z)
# idx = 0
colors = ColorGradient(vec(z))
newz = reshape(range(0, stop=1, length=n*m), n, m)
newz, colors
# newz = zeros(n, m)
# for i=1:n, j=1:m
# push!(colors, T(z[i,j]...))
# newz[i,j] = idx / (n*m-1)
# idx += 1
# end
# newz, ColorGradient(colors)
end
function imageHack(plotattributes::AKW)
is_seriestype_supported(:heatmap) || error("Neither :image or :heatmap are supported!")
plotattributes[:seriestype] = :heatmap
plotattributes[:z], plotattributes[:fillcolor] = replace_image_with_heatmap(plotattributes[:z].surf)
end
# ---------------------------------------------------------------
"Build line segments for plotting"
@@ -207,7 +79,7 @@ function iter_segments(series::Series)
end
else
segs = UnitRange{Int}[]
args = is3d(series) ? (x, y, z) : (x, y)
args = RecipesPipeline.is3d(series) ? (x, y, z) : (x, y)
for seg in iter_segments(args...)
push!(segs, seg)
end
@@ -271,32 +143,27 @@ makevec(v::T) where {T} = T[v]
maketuple(x::Real) = (x,x)
maketuple(x::Tuple{T,S}) where {T,S} = x
mapFuncOrFuncs(f::Function, u::AVec) = map(f, u)
mapFuncOrFuncs(fs::AVec{F}, u::AVec) where {F<:Function} = [map(f, u) for f in fs]
for i in 2:4
@eval begin
unzip(v::Union{AVec{<:Tuple{Vararg{T,$i} where T}},
@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)...))
end
end
end
unzip(v::Union{AVec{<:GeometryTypes.Point{N}},
RecipesPipeline.unzip(v::Union{AVec{<:GeometryTypes.Point{N}},
AVec{<:Tuple{Vararg{T,N} where T}}}) where N = error("$N-dimensional unzip not implemented.")
unzip(v::Union{AVec{<:GeometryTypes.Point},
RecipesPipeline.unzip(v::Union{AVec{<:GeometryTypes.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)
try
e1, e2 = ignorenan_extrema(x)
lims[1] = NaNMath.min(lims[1], e1)
lims[2] = NaNMath.max(lims[2], e2)
# catch err
# @warn(err)
catch
end
nothing
try
e1, e2 = ignorenan_extrema(x)
lims[1] = NaNMath.min(lims[1], e1)
lims[2] = NaNMath.max(lims[2], e2)
catch
end
nothing
end
expand_data(v, n::Integer) = [_cycle(v, i) for i=1:n]
@@ -314,21 +181,21 @@ function addOrReplace(v::AbstractVector, t::DataType, args...; kw...)
end
function replaceType(vec, val)
filter!(x -> !isa(x, typeof(val)), vec)
push!(vec, val)
filter!(x -> !isa(x, typeof(val)), vec)
push!(vec, val)
end
function replaceAlias!(plotattributes::AKW, k::Symbol, aliases::Dict{Symbol,Symbol})
if haskey(aliases, k)
plotattributes[aliases[k]] = pop_kw!(plotattributes, k)
end
if haskey(aliases, k)
plotattributes[aliases[k]] = RecipesPipeline.pop_kw!(plotattributes, k)
end
end
function replaceAliases!(plotattributes::AKW, aliases::Dict{Symbol,Symbol})
ks = collect(keys(plotattributes))
for k in ks
replaceAlias!(plotattributes, k, aliases)
end
ks = collect(keys(plotattributes))
for k in ks
replaceAlias!(plotattributes, k, aliases)
end
end
createSegments(z) = collect(repeat(reshape(z,1,:),2,1))[2:end]
@@ -347,20 +214,20 @@ const _scale_base = Dict{Symbol, Real}(
)
function _heatmap_edges(v::AVec, isedges::Bool = false)
length(v) == 1 && return v[1] .+ [-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_max = (v[end] - v[end - 1]) / 2
vcat(vmin-extra_min, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra_max)
length(v) == 1 && return v[1] .+ [-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_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)
f, invf = scalefunc(scale), invscalefunc(scale)
map(invf, _heatmap_edges(map(f,v), isedges))
f, invf = RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale)
map(invf, _heatmap_edges(map(f,v), isedges))
end
function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size::Tuple{Int, Int})
@@ -380,8 +247,8 @@ function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size:
end
function is_uniformly_spaced(v; tol=1e-6)
dv = diff(v)
maximum(dv) - minimum(dv) < tol * mean(abs.(dv))
dv = diff(v)
maximum(dv) - minimum(dv) < tol * mean(abs.(dv))
end
function convert_to_polar(theta, r, r_extrema = ignorenan_extrema(r))
@@ -393,11 +260,11 @@ function convert_to_polar(theta, r, r_extrema = ignorenan_extrema(r))
end
function fakedata(sz...)
y = zeros(sz...)
for r in 2:size(y,1)
y[r,:] = 0.95 * vec(y[r-1,:]) + randn(size(y,2))
end
y
y = zeros(sz...)
for r in 2:size(y,1)
y[r,:] = 0.95 * vec(y[r-1,:]) + randn(size(y,2))
end
y
end
isijulia() = :IJulia in nameof.(collect(values(Base.loaded_modules)))
@@ -693,6 +560,10 @@ function get_markerstrokealpha(series, i::Int = 1)
_cycle(series[:markerstrokealpha], i)
end
function get_markerstrokewidth(series, i::Int = 1)
_cycle(series[:markerstrokewidth], i)
end
function has_attribute_segments(series::Series)
# we want to check if a series needs to be split into segments just because
# of its attributes
@@ -706,6 +577,19 @@ function has_attribute_segments(series::Series)
return any((typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1) for attr in [:seriescolor, :seriesalpha, :linecolor, :linealpha, :linewidth, :linestyle, :fillcolor, :fillalpha, :markercolor, :markeralpha, :markerstrokecolor, :markerstrokealpha]) || any(typeof(series[attr]) <: AbstractArray for attr in (:line_z, :fill_z, :marker_z))
end
function get_aspect_ratio(sp)
aspect_ratio = sp[:aspect_ratio]
if aspect_ratio == :auto
aspect_ratio = :none
for series in series_list(sp)
if series[:seriestype] == :image
aspect_ratio = :equal
end
end
end
return aspect_ratio
end
# ---------------------------------------------------------------
makekw(; kw...) = KW(kw)
@@ -748,119 +632,107 @@ function with(f::Function, args...; kw...)
newdefs[:legend] = false
end
# dict to store old and new keyword args for anything that changes
olddefs = KW()
for k in keys(newdefs)
olddefs[k] = default(k)
end
# save the backend
if CURRENT_BACKEND.sym == :none
_pick_default_backend()
end
oldbackend = CURRENT_BACKEND.sym
for arg in args
# change backend?
if arg in backends()
backend(arg)
# dict to store old and new keyword args for anything that changes
olddefs = KW()
for k in keys(newdefs)
olddefs[k] = default(k)
end
# # TODO: generalize this strategy to allow args as much as possible
# # as in: with(:gr, :scatter, :legend, :grid) do; ...; end
# # TODO: can we generalize this enough to also do something similar in the plot commands??
# save the backend
if CURRENT_BACKEND.sym == :none
_pick_default_backend()
end
oldbackend = CURRENT_BACKEND.sym
# k = :seriestype
# if arg in _allTypes
# olddefs[k] = default(k)
# newdefs[k] = arg
# elseif haskey(_typeAliases, arg)
# olddefs[k] = default(k)
# newdefs[k] = _typeAliases[arg]
# end
for arg in args
k = :legend
if arg in (k, :leg)
olddefs[k] = default(k)
newdefs[k] = true
# change backend?
if arg in backends()
backend(arg)
end
# TODO: generalize this strategy to allow args as much as possible
# as in: with(:gr, :scatter, :legend, :grid) do; ...; end
# TODO: can we generalize this enough to also do something similar in the plot commands??
# k = :seriestype
# if arg in _allTypes
# olddefs[k] = default(k)
# newdefs[k] = arg
# elseif haskey(_typeAliases, arg)
# olddefs[k] = default(k)
# newdefs[k] = _typeAliases[arg]
# end
k = :legend
if arg in (k, :leg)
olddefs[k] = default(k)
newdefs[k] = true
end
k = :grid
if arg == k
olddefs[k] = default(k)
newdefs[k] = true
end
end
k = :grid
if arg == k
olddefs[k] = default(k)
newdefs[k] = true
# display(olddefs)
# display(newdefs)
# now set all those defaults
default(; newdefs...)
# call the function
ret = f()
# put the defaults back
default(; olddefs...)
# revert the backend
if CURRENT_BACKEND.sym != oldbackend
backend(oldbackend)
end
end
# display(olddefs)
# display(newdefs)
# now set all those defaults
default(; newdefs...)
# call the function
ret = f()
# put the defaults back
default(; olddefs...)
# revert the backend
if CURRENT_BACKEND.sym != oldbackend
backend(oldbackend)
end
# return the result of the function
ret
# return the result of the function
ret
end
# ---------------------------------------------------------------
# ---------------------------------------------------------------
mutable struct DebugMode
on::Bool
on::Bool
end
const _debugMode = DebugMode(false)
function debugplots(on = true)
_debugMode.on = on
_debugMode.on = on
end
debugshow(io, x) = show(io, x)
debugshow(io, x::AbstractArray) = print(io, summary(x))
function dumpdict(io::IO, plotattributes::AKW, prefix = "", alwaysshow = false)
_debugMode.on || alwaysshow || return
println(io)
if prefix != ""
println(io, prefix, ":")
end
for k in sort(collect(keys(plotattributes)))
@printf("%14s: ", k)
debugshow(io, plotattributes[k])
_debugMode.on || alwaysshow || return
println(io)
if prefix != ""
println(io, prefix, ":")
end
for k in sort(collect(keys(plotattributes)))
@printf("%14s: ", k)
debugshow(io, plotattributes[k])
println(io)
end
println(io)
end
println(io)
end
DD(io::IO, plotattributes::AKW, prefix = "") = dumpdict(io, plotattributes, prefix, true)
DD(plotattributes::AKW, prefix = "") = DD(stdout, plotattributes, prefix)
function dumpcallstack()
error() # well... you wanted the stacktrace, didn't you?!?
error() # well... you wanted the stacktrace, didn't you?!?
end
# ---------------------------------------------------------------
# ---------------------------------------------------------------
# used in updating an existing series
extendSeriesByOne(v::UnitRange{Int}, n::Int = 1) = isempty(v) ? (1:n) : (minimum(v):maximum(v)+n)
extendSeriesByOne(v::AVec, n::Integer = 1) = isempty(v) ? (1:n) : vcat(v, (1:n) + ignorenan_maximum(v))
extendSeriesData(v::AbstractRange{T}, z::Real) where {T} = extendSeriesData(float(collect(v)), z)
extendSeriesData(v::AbstractRange{T}, z::AVec) where {T} = extendSeriesData(float(collect(v)), z)
extendSeriesData(v::AVec{T}, z::Real) where {T} = (push!(v, convert(T, z)); v)
extendSeriesData(v::AVec{T}, z::AVec) where {T} = (append!(v, convert(Vector{T}, z)); v)
# -------------------------------------------------------
# NOTE: backends should implement the following methods to get/set the x/y/z data objects
@@ -904,39 +776,66 @@ Base.setindex!(plt::Plot, xy::Tuple{X,Y}, i::Integer) where {X,Y} = (setxy!(plt,
Base.setindex!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z} = (setxyz!(plt, xyz, i); plt)
# -------------------------------------------------------
# operate on individual series
function push_x!(series::Series, xi)
push!(series[:x], xi)
expand_extrema!(series[:subplot][:xaxis], xi)
return
end
function push_y!(series::Series, yi)
push!(series[:y], yi)
expand_extrema!(series[:subplot][:yaxis], yi)
return
end
function push_z!(series::Series, zi)
push!(series[:z], zi)
expand_extrema!(series[:subplot][:zaxis], zi)
return
Base.push!(series::Series, args...) = extend_series!(series, args...)
Base.append!(series::Series, args...) = extend_series!(series, args...)
function extend_series!(series::Series, yi)
y = extend_series_data!(series, yi, :y)
x = extend_to_length!(series[:x], length(y))
expand_extrema!(series[:subplot][:xaxis], x)
return x, y
end
function Base.push!(series::Series, yi)
x = extendSeriesByOne(series[:x])
expand_extrema!(series[:subplot][:xaxis], x[end])
series[:x] = x
push_y!(series, yi)
function extend_series!(series::Series, xi, yi)
x = extend_series_data!(series, xi, :x)
y = extend_series_data!(series, yi, :y)
return x, y
end
function extend_series!(series::Series, xi, yi, zi)
x = extend_series_data!(series, xi, :x)
y = extend_series_data!(series, yi, :y)
z = extend_series_data!(series, zi, :z)
return x, y, z
end
function extend_series_data!(series::Series, v, letter)
copy_series!(series, letter)
d = extend_by_data!(series[letter], v)
expand_extrema!(series[:subplot][Symbol(letter, :axis)], d)
return d
end
function copy_series!(series, letter)
plt = series[:plot_object]
for s in plt.series_list
for l in (:x, :y, :z)
if s !== series || l !== letter
if s[l] === series[letter]
series[letter] = copy(series[letter])
end
end
end
end
end
extend_to_length!(v::AbstractRange, n) = range(first(v), step = step(v), length = n)
function extend_to_length!(v::AbstractVector, n)
vmax = isempty(v) ? 0 : ignorenan_maximum(v)
extend_by_data!(v, vmax .+ (1:(n - length(v))))
end
extend_by_data!(v::AbstractVector, x) = isimmutable(v) ? vcat(v, x) : push!(v, x)
function extend_by_data!(v::AbstractVector, x::AbstractVector)
isimmutable(v) ? vcat(v, x) : append!(v, x)
end
Base.push!(series::Series, xi, yi) = (push_x!(series,xi); push_y!(series,yi))
Base.push!(series::Series, xi, yi, zi) = (push_x!(series,xi); push_y!(series,yi); push_z!(series,zi))
# -------------------------------------------------------
function attr!(series::Series; kw...)
plotattributes = KW(kw)
preprocessArgs!(plotattributes)
RecipesPipeline.preprocess_attributes!(plotattributes)
for (k,v) in plotattributes
if haskey(_series_defaults, k)
series[k] = v
@@ -950,7 +849,7 @@ end
function attr!(sp::Subplot; kw...)
plotattributes = KW(kw)
preprocessArgs!(plotattributes)
RecipesPipeline.preprocess_attributes!(plotattributes)
for (k,v) in plotattributes
if haskey(_subplot_defaults, k)
sp[k] = v
@@ -964,59 +863,16 @@ end
# -------------------------------------------------------
# push/append for one series
# push value to first series
Base.push!(plt::Plot, y::Real) = push!(plt, 1, y)
Base.push!(plt::Plot, x::Real, y::Real) = push!(plt, 1, x, y)
Base.push!(plt::Plot, x::Real, y::Real, z::Real) = push!(plt, 1, x, y, z)
# y only
function Base.push!(plt::Plot, i::Integer, y::Real)
xdata, ydata = getxy(plt, i)
setxy!(plt, (extendSeriesByOne(xdata), extendSeriesData(ydata, y)), i)
plt
end
function Base.append!(plt::Plot, i::Integer, y::AVec)
xdata, ydata = plt[i]
if !isa(xdata, UnitRange{Int})
error("Expected x is a UnitRange since you're trying to push a y value only")
end
plt[i] = (extendSeriesByOne(xdata, length(y)), extendSeriesData(ydata, y))
plt
end
# x and y
function Base.push!(plt::Plot, i::Integer, x::Real, y::Real)
xdata, ydata = getxy(plt, i)
setxy!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y)), i)
plt
end
function Base.append!(plt::Plot, i::Integer, x::AVec, y::AVec)
@assert length(x) == length(y)
xdata, ydata = getxy(plt, i)
setxy!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y)), i)
plt
end
# x, y, and z
function Base.push!(plt::Plot, i::Integer, x::Real, y::Real, z::Real)
# @show i, x, y, z
xdata, ydata, zdata = getxyz(plt, i)
# @show xdata, ydata, zdata
setxyz!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y), extendSeriesData(zdata, z)), i)
plt
end
function Base.append!(plt::Plot, i::Integer, x::AVec, y::AVec, z::AVec)
@assert length(x) == length(y) == length(z)
xdata, ydata, zdata = getxyz(plt, i)
setxyz!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y), extendSeriesData(zdata, z)), i)
plt
end
Base.push!(plt::Plot, args::Real...) = push!(plt, 1, args...)
Base.push!(plt::Plot, i::Integer, args::Real...) = push!(plt.series_list[i], args...)
Base.append!(plt::Plot, args::AbstractVector...) = append!(plt, 1, args...)
Base.append!(plt::Plot, i::Integer, args::Real...) = append!(plt.series_list[i], args...)
# tuples
Base.push!(plt::Plot, xy::Tuple{X,Y}) where {X,Y} = push!(plt, 1, xy...)
Base.push!(plt::Plot, xyz::Tuple{X,Y,Z}) where {X,Y,Z} = push!(plt, 1, xyz...)
Base.push!(plt::Plot, i::Integer, xy::Tuple{X,Y}) where {X,Y} = push!(plt, i, xy...)
Base.push!(plt::Plot, i::Integer, xyz::Tuple{X,Y,Z}) where {X,Y,Z} = push!(plt, i, xyz...)
Base.push!(plt::Plot, t::Tuple) = push!(plt, 1, t...)
Base.push!(plt::Plot, i::Integer, t::Tuple) = push!(plt, i, t...)
Base.append!(plt::Plot, t::Tuple) = append!(plt, 1, t...)
Base.append!(plt::Plot, i::Integer, t::Tuple) = append!(plt, i, t...)
# -------------------------------------------------------
# push/append for all series
@@ -1215,7 +1071,7 @@ end
function shape_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]
x, y = copy(series[:x]), copy(series[:y])
factor = 100
for i in eachindex(x)
if x[i] == -Inf
+29 -2
View File
@@ -9,6 +9,7 @@ using LibGit2
using GeometryTypes
using Dates
include("test_hdf5plots.jl")
include("test_pgfplotsx.jl")
reference_dir(args...) = joinpath(homedir(), ".julia", "dev", "PlotReferenceImages", args...)
@@ -42,7 +43,31 @@ include("imgcomp.jl")
Random.seed!(1234)
default(show=false, reuse=true)
is_ci() = get(ENV, "CI", "false") == "true"
img_tol = is_ci() ? 10e-2 : 10e-2
img_tol = is_ci() ? 1e-2 : Sys.islinux() ? 1e-3 : 0.1
## 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])
end
plotly()
@testset "Plotly" begin
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])
end
pgfplotsx()
@testset "PGFPlotsX" begin
image_comparison_facts(:pgfplotsx, tol=img_tol, skip = Plots._backend_skips[:pgfplotsx])
end
=#
##
@testset "Backends" begin
@@ -52,7 +77,9 @@ img_tol = is_ci() ? 10e-2 : 10e-2
@test gr() == Plots.GRBackend()
@test backend() == Plots.GRBackend()
@static if Sys.islinux()
@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])
end
end
+21
View File
@@ -0,0 +1,21 @@
using Plots, HDF5
@testset "HDF5_Plots" begin
fname = "tmpplotsave.hdf5"
hdf5()
x = 1:10
psrc=plot(x, x.*x); #Create some plot
Plots.hdf5plot_write(psrc, fname)
#Read back file:
gr() #Choose some fast backend likely to work in test environment.
pread = Plots.hdf5plot_read(fname)
#Make sure data made it through:
@test psrc.subplots[1].series_list[1][:x] == pread.subplots[1].series_list[1][:x]
@test psrc.subplots[1].series_list[1][:y] == pread.subplots[1].series_list[1][:y]
#display(pread) #Don't display. Regression env might not support
end #testset
+317 -155
View File
@@ -1,166 +1,328 @@
using Plots, Test
pgfplotsx()
function create_plot( args...; kwargs... )
pgfx_plot = plot(args...; kwargs...)
return pgfx_plot, repr("application/x-tex", pgfx_plot)
function create_plot(args...; kwargs...)
pgfx_plot = plot(args...; kwargs...)
return pgfx_plot, repr("application/x-tex", pgfx_plot)
end
function create_plot!( args...; kwargs... )
pgfx_plot = plot!(args...; kwargs...)
return pgfx_plot, repr("application/x-tex", pgfx_plot)
function create_plot!(args...; kwargs...)
pgfx_plot = plot!(args...; kwargs...)
return pgfx_plot, repr("application/x-tex", pgfx_plot)
end
@testset "PGFPlotsX" begin
pgfx_plot = plot(1:5)
Plots._update_plot_object(pgfx_plot)
@test pgfx_plot.o.the_plot isa PGFPlotsX.TikzDocument
@test pgfx_plot.series_list[1].plotattributes[:quiver] === nothing
axis = Plots.pgfx_axes(pgfx_plot.o)[1]
@test count( x-> x isa PGFPlotsX.Plot, axis.contents ) == 1
@test !haskey(axis.contents[1].options.dict, "fill")
pgfx_plot = plot(1:5)
Plots._update_plot_object(pgfx_plot)
@test pgfx_plot.o.the_plot isa PGFPlotsX.TikzDocument
@test pgfx_plot.series_list[1].plotattributes[:quiver] === nothing
axis = Plots.pgfx_axes(pgfx_plot.o)[1]
@test count(x -> x isa PGFPlotsX.Plot, axis.contents) == 1
@test !haskey(axis.contents[1].options.dict, "fill")
@testset "3D docs example" begin
n = 100
ts = range(0, stop=8π, length=n)
x = ts .* map(cos, ts)
y = (0.1ts) .* map(sin, ts)
z = 1:n
pl = plot(x, y, z, zcolor=reverse(z), m=(10, 0.8, :blues, Plots.stroke(0)), leg=false, cbar=true, w=5)
pgfx_plot = plot!(pl, zeros(n), zeros(n), 1:n, w=10)
Plots._update_plot_object(pgfx_plot)
if @test_nowarn(haskey(Plots.pgfx_axes(pgfx_plot.o)[1].options.dict, "colorbar") == true)
@test Plots.pgfx_axes(pgfx_plot.o)[1]["colorbar"] === nothing
end
end # testset
@testset "Color docs example" begin
y = rand(100)
plot(0:10:100, rand(11, 4), lab="lines", w=3, palette=:grays, fill=0, α=0.6)
pl = scatter!(y, zcolor=abs.(y .- 0.5), m=(:heat, 0.8, Plots.stroke(1, :green)), ms=10 * abs.(y .- 0.5) .+ 4, lab="grad")
Plots._update_plot_object(pl)
axis = Plots.pgfx_axes(pl.o)[1]
@test count( x->x isa PGFPlotsX.LegendEntry, axis.contents ) == 5
@test count( x->x isa PGFPlotsX.Plot, axis.contents ) == 108 # each marker is its own plot, fillranges create 2 plot-objects
marker = axis.contents[15]
@test marker isa PGFPlotsX.Plot
@test marker.options["mark"] == "*"
@test marker.options["mark options"]["color"] == RGBA{Float64}( colorant"green", 0.8)
@test marker.options["mark options"]["line width"] == 1
end # testset
@testset "Plot in pieces" begin
plot(rand(100) / 3, reg=true, fill=(0, :green))
scatter!(rand(100), markersize=6, c=:orange)
end # testset
@testset "Marker types" begin
markers = filter((m->begin
m in Plots.supported_markers()
end), Plots._shape_keys)
markers = reshape(markers, 1, length(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), lab=map(string, markers), bg=:linen, xlim=(0, 10), ylim=(0, 10))
end # testset
@testset "Layout" begin
plot(Plots.fakedata(100, 10), layout=4, palette=[:grays :blues :heat :lightrainbow], bg_inside=[:orange :pink :darkblue :black])
end # testset
@testset "Polar plots" begin
Θ = range(0, stop=1.5π, length=100)
r = abs.(0.1 * randn(100) + sin.(3Θ))
plot(Θ, r, proj=:polar, m=2)
end # testset
@testset "Drawing shapes" begin
verts = [(-1.0, 1.0), (-1.28, 0.6), (-0.2, -1.4), (0.2, -1.4), (1.28, 0.6), (1.0, 1.0), (-1.0, 1.0), (-0.2, -0.6), (0.0, -0.2), (-0.4, 0.6), (1.28, 0.6), (0.2, -1.4), (-0.2, -1.4), (0.6, 0.2), (-0.2, 0.2), (0.0, -0.2), (0.2, 0.2), (-0.2, -0.6)]
x = 0.1:0.2:0.9
y = 0.7 * rand(5) .+ 0.15
plot(x, y, line=(3, :dash, :lightblue), marker=(Shape(verts), 30, RGBA(0, 0, 0, 0.2)), bg=:pink, fg=:darkblue, xlim=(0, 1), ylim=(0, 1), leg=false)
end # testset
@testset "Histogram 2D" begin
histogram2d(randn(10000), randn(10000), nbins=20)
end # testset
@testset "Heatmap-like" begin
xs = [string("x", i) for i = 1:10]
ys = [string("y", i) for i = 1:4]
z = float((1:4) * reshape(1:10, 1, :))
pgfx_plot = heatmap(xs, ys, z, aspect_ratio=1)
Plots._update_plot_object(pgfx_plot)
if @test_nowarn(haskey(Plots.pgfx_axes(pgfx_plot.o)[1].options.dict, "colorbar") == true)
@test Plots.pgfx_axes(pgfx_plot.o)[1]["colorbar"] === nothing
@test Plots.pgfx_axes(pgfx_plot.o)[1]["colormap name"] == "plots1"
end
@testset "Legends" begin
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 )
@test length(leg_entries) == 2
@test !haskey(series[1].options.dict, "forget plot")
@test haskey(series[2].options.dict, "forget plot")
@test !haskey(series[3].options.dict, "forget plot")
end # testset
pgfx_plot = wireframe(xs, ys, z, aspect_ratio=1)
# TODO: clims are wrong
end # testset
@testset "Contours" begin
x = 1:0.5:20
y = 1:0.5:10
f(x, y) = begin
(3x + y ^ 2) * abs(sin(x) + cos(y))
end
X = repeat(reshape(x, 1, :), length(y), 1)
Y = repeat(y, 1, length(x))
Z = map(f, X, Y)
p2 = contour(x, y, Z)
p1 = contour(x, y, f, fill=true)
plot(p1, p2)
# TODO: colorbar for filled contours
end # testset
@testset "Varying colors" begin
t = range(0, stop=1, length=100)
θ = (6π) .* t
x = t .* cos.(θ)
y = t .* sin.(θ)
p1 = plot(x, y, line_z=t, linewidth=3, legend=false)
p2 = scatter(x, y, marker_z=((x, y)->begin
x + y
end), color=:bluesreds, legend=false)
plot(p1, p2)
end # testset
@testset "Framestyles" begin
scatter(fill(randn(10), 6), fill(randn(10), 6), framestyle=[:box :semi :origin :zerolines :grid :none], title=[":box" ":semi" ":origin" ":zerolines" ":grid" ":none"], color=permutedims(1:6), layout=6, label="", markerstrokewidth=0, ticks=-2:2)
# TODO: support :semi
end # testset
@testset "Quiver" begin
x = -2pi:0.2:2*pi
y = sin.(x)
@testset "3D docs example" begin
n = 100
ts = range(0, stop = 8π, length = n)
x = ts .* map(cos, ts)
y = (0.1ts) .* map(sin, ts)
z = 1:n
pl = plot(
x,
y,
z,
zcolor = reverse(z),
m = (10, 0.8, :blues, Plots.stroke(0)),
leg = false,
cbar = true,
w = 5,
)
pgfx_plot = plot!(pl, zeros(n), zeros(n), 1:n, w = 10)
Plots._update_plot_object(pgfx_plot)
if @test_nowarn(
haskey(Plots.pgfx_axes(pgfx_plot.o)[1].options.dict, "colorbar") == true
)
@test Plots.pgfx_axes(pgfx_plot.o)[1]["colorbar"] === nothing
end
end # testset
@testset "Color docs example" begin
y = rand(100)
plot(
0:10:100,
rand(11, 4),
lab = "lines",
w = 3,
palette = :grays,
fill = 0,
α = 0.6,
)
pl = scatter!(
y,
zcolor = abs.(y .- 0.5),
m = (:heat, 0.8, Plots.stroke(1, :green)),
ms = 10 * abs.(y .- 0.5) .+ 4,
lab = ["grad", "", "ient"],
)
Plots._update_plot_object(pl)
axis = Plots.pgfx_axes(pl.o)[1]
@test count(x -> x isa PGFPlotsX.LegendEntry, axis.contents) == 6
@test count(x -> x isa PGFPlotsX.Plot, axis.contents) == 108 # each marker is its own plot, fillranges create 2 plot-objects
marker = axis.contents[15]
@test marker isa PGFPlotsX.Plot
@test marker.options["mark"] == "*"
@test marker.options["mark options"]["color"] ==
RGBA{Float64}(colorant"green", 0.8)
@test marker.options["mark options"]["line width"] == 0.75 # 1px is 0.75pt
end # testset
@testset "Plot in pieces" begin
pic = plot(rand(100) / 3, reg = true, fill = (0, :green))
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 )
@test length(leg_entries) == 2
@test length(series) == 4
@test haskey(series[1].options.dict, "forget plot")
@test !haskey(series[2].options.dict, "forget plot")
@test haskey(series[3].options.dict, "forget plot")
@test !haskey(series[4].options.dict, "forget plot")
end # testset
@testset "Marker types" begin
markers = filter((m -> begin
m in Plots.supported_markers()
end), Plots._shape_keys)
markers = reshape(markers, 1, length(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),
lab = map(string, markers),
bg = :linen,
xlim = (0, 10),
ylim = (0, 10),
)
end # testset
@testset "Layout" begin
plot(
Plots.fakedata(100, 10),
layout = 4,
palette = [:grays :blues :heat :lightrainbow],
bg_inside = [:orange :pink :darkblue :black],
)
end # testset
@testset "Polar plots" begin
Θ = range(0, stop = 1.5π, length = 100)
r = abs.(0.1 * randn(100) + sin.(3Θ))
plot(Θ, r, proj = :polar, m = 2)
end # testset
@testset "Drawing shapes" begin
verts = [
(-1.0, 1.0),
(-1.28, 0.6),
(-0.2, -1.4),
(0.2, -1.4),
(1.28, 0.6),
(1.0, 1.0),
(-1.0, 1.0),
(-0.2, -0.6),
(0.0, -0.2),
(-0.4, 0.6),
(1.28, 0.6),
(0.2, -1.4),
(-0.2, -1.4),
(0.6, 0.2),
(-0.2, 0.2),
(0.0, -0.2),
(0.2, 0.2),
(-0.2, -0.6),
]
x = 0.1:0.2:0.9
y = 0.7 * rand(5) .+ 0.15
plot(
x,
y,
line = (3, :dash, :lightblue),
marker = (Shape(verts), 30, RGBA(0, 0, 0, 0.2)),
bg = :pink,
fg = :darkblue,
xlim = (0, 1),
ylim = (0, 1),
leg = false,
)
end # testset
@testset "Histogram 2D" begin
histogram2d(randn(10000), randn(10000), nbins = 20)
end # testset
@testset "Heatmap-like" begin
xs = [string("x", i) for i = 1:10]
ys = [string("y", i) for i = 1:4]
z = float((1:4) * reshape(1:10, 1, :))
pgfx_plot = heatmap(xs, ys, z, aspect_ratio = 1)
Plots._update_plot_object(pgfx_plot)
if @test_nowarn(
haskey(Plots.pgfx_axes(pgfx_plot.o)[1].options.dict, "colorbar") == true
)
@test Plots.pgfx_axes(pgfx_plot.o)[1]["colorbar"] === nothing
@test Plots.pgfx_axes(pgfx_plot.o)[1]["colormap name"] == "plots1"
end
u = ones(length(x))
v = cos.(x)
arrow_plot = plot( x, y, quiver = (u, v), arrow = true )
# TODO: could adjust limits to fit arrows if too long, but how?
# TODO: get latex available on CI
# mktempdir() do path
# @test_nowarn savefig(arrow_plot, path*"arrow.pdf")
# end
end # testset
@testset "Annotations" begin
y = rand(10)
plot(y, annotations=(3, y[3], Plots.text("this is \\#3", :left)), leg=false)
annotate!([(5, y[5], Plots.text("this is \\#5", 16, :red, :center)), (10, y[10], Plots.text("this is \\#10", :right, 20, "courier"))])
annotation_plot = scatter!(range(2, stop=8, length=6), rand(6), marker=(50, 0.2, :orange), series_annotations=["series", "annotations", "map", "to", "series", Plots.text("data", :green)])
# mktempdir() do path
# @test_nowarn savefig(annotation_plot, path*"annotation.pdf")
# end
end # testset
@testset "Ribbon" begin
aa = rand(10)
bb = rand(10)
cc = rand(10)
conf = [aa-cc bb-cc]
ribbon_plot = plot(collect(1:10),fill(1,10), ribbon=(conf[:,1],conf[:,2]))
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 !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
end # testset
pgfx_plot = wireframe(xs, ys, z, aspect_ratio = 1)
# TODO: clims are wrong
end # testset
@testset "Contours" begin
x = 1:0.5:20
y = 1:0.5:10
f(x, y) = begin
(3x + y^2) * abs(sin(x) + cos(y))
end
X = repeat(reshape(x, 1, :), length(y), 1)
Y = repeat(y, 1, length(x))
Z = map(f, X, Y)
p2 = contour(x, y, Z)
p1 = contour(x, y, f, fill = true)
plot(p1, p2)
# TODO: colorbar for filled contours
end # testset
@testset "Varying colors" begin
t = range(0, stop = 1, length = 100)
θ = (6π) .* t
x = t .* cos.(θ)
y = t .* sin.(θ)
p1 = plot(x, y, line_z = t, linewidth = 3, legend = false)
p2 = scatter(
x,
y,
marker_z = ((x, y) -> begin
x + y
end),
color = :bluesreds,
legend = false,
)
plot(p1, p2)
end # testset
@testset "Framestyles" begin
scatter(
fill(randn(10), 6),
fill(randn(10), 6),
framestyle = [:box :semi :origin :zerolines :grid :none],
title = [":box" ":semi" ":origin" ":zerolines" ":grid" ":none"],
color = permutedims(1:6),
layout = 6,
label = "",
markerstrokewidth = 0,
ticks = -2:2,
)
# TODO: support :semi
end # testset
@testset "Quiver" begin
x = (-2pi):0.2:(2 * pi)
y = sin.(x)
u = ones(length(x))
v = cos.(x)
arrow_plot = plot(x, y, quiver = (u, v), arrow = true)
# TODO: could adjust limits to fit arrows if too long, but how?
# TODO: get latex available on CI
# mktempdir() do path
# @test_nowarn savefig(arrow_plot, path*"arrow.pdf")
# end
end # testset
@testset "Annotations" begin
y = rand(10)
pgfx_plot = plot(
y,
annotations = (3, y[3], Plots.text("this is \\#3", :left)),
leg = false,
)
Plots._update_plot_object(pgfx_plot)
axis_content = Plots.pgfx_axes(pgfx_plot.o)[1].contents
nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content)
@test length(nodes) == 1
mktempdir() do path
file_path =joinpath(path,"annotations.tex")
@test_nowarn savefig(pgfx_plot, file_path)
open(file_path) do io
lines = readlines(io)
@test count(s -> occursin("node", s), lines) == 1
end
end
annotate!([
(5, y[5], Plots.text("this is \\#5", 16, :red, :center)),
(10, y[10], Plots.text("this is \\#10", :right, 20, "courier")),
])
Plots._update_plot_object(pgfx_plot)
axis_content = Plots.pgfx_axes(pgfx_plot.o)[1].contents
nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content)
@test length(nodes) == 3
mktempdir() do path
file_path =joinpath(path,"annotations.tex")
@test_nowarn savefig(pgfx_plot, file_path)
open(file_path) do io
lines = readlines(io)
@test count(s -> occursin("node", s), lines) == 3
end
end
annotation_plot = scatter!(
range(2, stop = 8, length = 6),
rand(6),
marker = (50, 0.2, :orange),
series_annotations = [
"series",
"annotations",
"map",
"to",
"series",
Plots.text("data", :green),
],
)
Plots._update_plot_object(annotation_plot)
axis_content = Plots.pgfx_axes(annotation_plot.o)[1].contents
nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content)
@test length(nodes) == 9
mktempdir() do path
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
end
end # testset
@testset "Ribbon" begin
aa = rand(10)
bb = rand(10)
cc = rand(10)
conf = [aa - cc bb - cc]
ribbon_plot =
plot(collect(1:10), fill(1, 10), ribbon = (conf[:, 1], conf[:, 2]))
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 !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
end # testset