Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67ba06cd77 | |||
| aa61d681d9 | |||
| c8e6b967e5 | |||
| 819e91aa37 | |||
| 7f835cf7cb | |||
| 43571e6d40 | |||
| dd3666b6d3 | |||
| f98b191616 | |||
| 9fdcf0db74 | |||
| 7abab41f2b | |||
| 45569c0d4d | |||
| ca653c5bec | |||
| 6c0539bac6 | |||
| 65c9a46c8d | |||
| 7bd97558dc | |||
| 6807f53a7e | |||
| ae396ef576 | |||
| 3dd1c500cf | |||
| 59e4129194 | |||
| 54d441b62a | |||
| f8d36363b4 | |||
| c32c7fda58 | |||
| 87b220e747 | |||
| 57a6f4303b | |||
| 407456eac5 | |||
| f118d0cd35 | |||
| 1b0fb95583 | |||
| 0daed24020 | |||
| 92eaa95127 | |||
| a9d22cfb60 | |||
| b433b76a84 | |||
| 5ff0a419dd | |||
| 6b7be5ae12 | |||
| 8d1f44a0c5 | |||
| 0c101e7d88 | |||
| 6e3c218d89 | |||
| fe436cf54f | |||
| efdfec13ed | |||
| 52522fde48 | |||
| 2a4882d92e | |||
| bf98db67a3 | |||
| 9f375aef09 | |||
| 1faa858690 | |||
| 5e7d2fb8ba | |||
| 974c6e5950 | |||
| e53f27c224 | |||
| d98262bc08 | |||
| 945874ca7d | |||
| 4c839837d3 | |||
| f230dee72c | |||
| 11a859e83e | |||
| 9029e37671 | |||
| 2a4b74d0ff | |||
| 0af25a2684 | |||
| f62f4806c3 | |||
| 22496535d1 | |||
| cb8df169c7 | |||
| ac25972873 | |||
| f1f9d807e6 | |||
| 7d34b9bb46 | |||
| ae009cd53f | |||
| dd36d17fa6 | |||
| 9843921503 | |||
| f6feb4b9eb | |||
| 246632abca | |||
| 5da33d7de8 | |||
| 9243de6ce9 | |||
| a5b6c27bec | |||
| 076de033fd | |||
| a1116f8e3b | |||
| d8c4c53567 | |||
| df9217e3b9 | |||
| c5aebf1309 | |||
| d4ecf468b1 | |||
| aa2cee405a | |||
| 5fc02f7f58 | |||
| 3d5ae2fc3b | |||
| eecfdab590 | |||
| e9ef5c852b | |||
| 8594926b67 | |||
| 74a37b7817 | |||
| 13b47a4ee6 | |||
| 0c2d80841c | |||
| d1465ff401 | |||
| ee0a428dbf | |||
| f808a9fae5 | |||
| 932d0da73d | |||
| d9fcfbe80e | |||
| ca3baf3881 | |||
| c37829eafb | |||
| d9a48ac7de | |||
| c684213106 | |||
| 5318352929 | |||
| dd3216489b | |||
| 17db6c01b4 | |||
| 8efd0f00c4 | |||
| b258c9d8c8 | |||
| b0548ffc14 | |||
| 5194e49b4c | |||
| 2083af9257 | |||
| 6cd8b8ff8d | |||
| ca9e47bc4d | |||
| ed8e72c1bc | |||
| 4e5e9986a1 | |||
| 55bb6fda1b | |||
| c50a5bf123 | |||
| 2702dc6654 | |||
| 3fe013cd24 | |||
| fba6802066 | |||
| fa8c1e3967 | |||
| 23b191231c | |||
| aa5d514d57 | |||
| 3fb45764b2 | |||
| fcff639d0c | |||
| 96181b8c48 | |||
| 62290a2a24 | |||
| bfafe91842 | |||
| cbaa8fca56 | |||
| f606f083a4 | |||
| b60007cde9 | |||
| 89e271bd91 | |||
| 3ed50bbf0f | |||
| 0d210843e7 | |||
| 08bed07632 | |||
| c47ab69383 | |||
| eb942352ce | |||
| 00379097cf | |||
| 16b568840d | |||
| c1dae9e9d9 | |||
| 8d8921fd08 | |||
| d04d651e55 | |||
| c771739b1b | |||
| a800f8d4d9 | |||
| 4b5ec3ad07 | |||
| 35c68c8890 | |||
| 5005120f5e | |||
| 7b68e7985f | |||
| 871c7d86c7 | |||
| 79614ffcec | |||
| edcaab3c56 | |||
| 503d741ea6 | |||
| fa6f0a6b00 |
@@ -8,3 +8,5 @@ deps/plotly-latest.min.js
|
||||
deps/build.log
|
||||
deps/deps.jl
|
||||
Manifest.toml
|
||||
dev/
|
||||
test/tmpplotsave.hdf5
|
||||
|
||||
+7
-1
@@ -4,8 +4,8 @@ os:
|
||||
- linux
|
||||
# - osx
|
||||
julia:
|
||||
- 1.0
|
||||
- 1
|
||||
- 1.3
|
||||
- nightly
|
||||
|
||||
matrix:
|
||||
@@ -20,6 +20,9 @@ addons:
|
||||
- xauth
|
||||
- xvfb
|
||||
|
||||
env:
|
||||
- GKS_ENCODING="utf8"
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.julia/artifacts
|
||||
@@ -32,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
|
||||
|
||||
+8
-6
@@ -1,7 +1,7 @@
|
||||
name = "Plots"
|
||||
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
|
||||
author = ["Tom Breloff (@tbreloff)"]
|
||||
version = "0.29.7"
|
||||
version = "1.0.4"
|
||||
|
||||
[deps]
|
||||
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
|
||||
@@ -33,7 +33,7 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
||||
[compat]
|
||||
Contour = "0.5"
|
||||
FFMPEG = "0.2, 0.3"
|
||||
FixedPointNumbers = "0.6, 0.7"
|
||||
FixedPointNumbers = "0.6, 0.7, 0.8"
|
||||
GR = "0.46, 0.47, 0.48"
|
||||
GeometryTypes = "0.7, 0.8"
|
||||
JSON = "0.21"
|
||||
@@ -41,8 +41,8 @@ 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"
|
||||
Reexport = "0.2"
|
||||
Requires = "0.5, 1.0"
|
||||
Showoff = "0.3.1"
|
||||
@@ -53,17 +53,19 @@ julia = "1"
|
||||
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"]
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
[![][pkgeval-img]][pkgeval-url]
|
||||
[![][gitter-img]][gitter-url]
|
||||
[![][docs-img]][docs-url]
|
||||
[](https://codecov.io/gh/JuliaPlots/Plots.jl)
|
||||
|
||||
#### Created by Tom Breloff (@tbreloff)
|
||||
|
||||
|
||||
+2
-3
@@ -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:
|
||||
@@ -33,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
@@ -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"
|
||||
Vendored
+4
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+8
-4
@@ -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
@@ -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",
|
||||
|
||||
+53
-37
@@ -267,6 +267,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...)`
|
||||
@@ -450,15 +451,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 +569,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 +595,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 +659,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
|
||||
|
||||
@@ -919,22 +941,6 @@ end
|
||||
function preprocessArgs!(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, ())
|
||||
for arg in wraptuple(args)
|
||||
@@ -992,13 +998,6 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
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, ())
|
||||
@@ -1017,6 +1016,26 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
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 = 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, ()))
|
||||
@@ -1219,7 +1238,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")
|
||||
@@ -1471,12 +1490,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 +1507,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
|
||||
|
||||
|
||||
@@ -84,6 +84,9 @@ function attr!(axis::Axis, args...; kw...)
|
||||
process_axis_arg!(plotattributes, arg)
|
||||
end
|
||||
|
||||
# then preprocess keyword arguments
|
||||
preprocessArgs!(KW(kw))
|
||||
|
||||
# then override for any keywords... only those keywords that already exists in plotattributes
|
||||
for (k,v) in kw
|
||||
if haskey(plotattributes, k)
|
||||
|
||||
+1
-1
@@ -211,7 +211,7 @@ const _base_supported_args = [
|
||||
:seriestype,
|
||||
:seriescolor, :seriesalpha,
|
||||
:smooth,
|
||||
:xerror, :yerror,
|
||||
:xerror, :yerror, :zerror,
|
||||
:subplot,
|
||||
:x, :y, :z,
|
||||
:show, :size,
|
||||
|
||||
+23
-1
@@ -1043,6 +1043,13 @@ 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])
|
||||
@@ -1798,6 +1805,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
|
||||
|
||||
@@ -1805,7 +1827,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)
|
||||
|
||||
+66
-36
@@ -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:
|
||||
"ATTR" => Attr,
|
||||
"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::Attr)
|
||||
for (k, v) in plotattributes
|
||||
kstr = string(k)
|
||||
_hdf5plot_gwrite(grp, kstr, v)
|
||||
end
|
||||
_hdf5plot_writetype(grp, Attr)
|
||||
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{Attr})
|
||||
attr = Attr(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 = Attr(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::Attr) = _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 = Attr(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 = Attr(KW(), _plot_defaults)
|
||||
_hdf5plot_read(grp, attr)
|
||||
_hdf5_merge!(sp.attr, attr)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
+156
-54
@@ -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},%
|
||||
},
|
||||
}
|
||||
""",
|
||||
)
|
||||
@@ -76,6 +85,7 @@ end
|
||||
|
||||
function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
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]
|
||||
@@ -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,
|
||||
@@ -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
|
||||
@@ -259,7 +283,7 @@ 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(
|
||||
[
|
||||
@@ -334,51 +358,51 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
series_index,
|
||||
)
|
||||
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
|
||||
# add to legend?
|
||||
if sp[:legend] != :none && pgfx_should_add_to_legend(series)
|
||||
if sp[:legend] != :none
|
||||
leg_entry = if opt[:label] isa AVec
|
||||
get(opt[:label], i, "")
|
||||
elseif opt[:label] isa AbstractString
|
||||
if i == 1
|
||||
opt[:label]
|
||||
get(opt, :label, "")
|
||||
else
|
||||
""
|
||||
end
|
||||
else
|
||||
throw(ArgumentError("Malformed label. label = $(opt[:label])"))
|
||||
end
|
||||
if leg_entry == ""
|
||||
push!(segment_plot.options, "forget plot" => nothing)
|
||||
continue
|
||||
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
|
||||
leg_opt = PGFPlotsX.Options()
|
||||
if ribbon !== nothing
|
||||
pgfx_filllegend!(axis.contents[end - 3].options, opt)
|
||||
end
|
||||
legend = PGFPlotsX.LegendEntry(leg_opt, leg_entry, false)
|
||||
push!(axis, legend)
|
||||
end
|
||||
end # for segments
|
||||
# add subplot annotations
|
||||
for ann in sp[:annotations]
|
||||
# add series annotations
|
||||
anns = series[:series_annotations]
|
||||
for (xi, yi, str, fnt) in EachAnn(anns, series[:x], series[:y])
|
||||
pgfx_add_annotation!(
|
||||
axis,
|
||||
locate_annotation(sp, ann...)...,
|
||||
pgfx_thickness_scaling(sp),
|
||||
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
|
||||
@@ -387,6 +411,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
|
||||
@@ -586,11 +611,22 @@ const _pgfplotsx_markers = KW(
|
||||
)
|
||||
|
||||
const _pgfplotsx_legend_pos = KW(
|
||||
:top => "north",
|
||||
:bottom => "south",
|
||||
:left => "west",
|
||||
:right => "east",
|
||||
:bottomleft => "south west",
|
||||
:bottomright => "south east",
|
||||
:topright => "north east",
|
||||
:topleft => "north west",
|
||||
:outertop => "north",
|
||||
:outerbottom => "outer south",
|
||||
:outerleft => "outer west",
|
||||
:outerright => "outer east",
|
||||
:outerbottomleft => "outer south west",
|
||||
:outerbottomright => "outer south east",
|
||||
:outertopright => "outer north east",
|
||||
:outertopleft => "outer north west",
|
||||
)
|
||||
|
||||
const _pgfx_framestyles = [:box, :axes, :origin, :zerolines, :grid, :none]
|
||||
@@ -691,7 +727,7 @@ function pgfx_font(fontsize, thickness_scaling = 1, font = "\\selectfont")
|
||||
end
|
||||
|
||||
function pgfx_should_add_to_legend(series::Series)
|
||||
series.plotattributes[:primary] && series.plotattributes[:label] != "" &&
|
||||
series.plotattributes[:primary] &&
|
||||
!(
|
||||
series.plotattributes[:seriestype] in (
|
||||
:hexbin,
|
||||
@@ -862,6 +898,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)]
|
||||
@@ -872,6 +959,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])
|
||||
@@ -1043,12 +1145,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})
|
||||
|
||||
@@ -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(dom"div"(innerHTML=plt_html))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+5
-3
@@ -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)))
|
||||
@@ -661,8 +663,8 @@ 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...)
|
||||
for f in (:length, :size, :axes)
|
||||
@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)
|
||||
|
||||
+109
-1
@@ -875,6 +875,114 @@ const _examples = PlotExample[
|
||||
end,
|
||||
],
|
||||
),
|
||||
PlotExample(
|
||||
"Array Types",
|
||||
"Plots supports different `Array` types that follow the `AbstractArray` interface, like `StaticArrays` and `OffsetArrays.`",
|
||||
[
|
||||
quote
|
||||
begin
|
||||
using StaticArrays, OffsetArrays
|
||||
sv = SVector{10}(rand(10))
|
||||
ov = OffsetVector(rand(10), -2)
|
||||
plot([sv, ov], label = ["StaticArray" "OffsetArray"])
|
||||
end
|
||||
end,
|
||||
],
|
||||
),
|
||||
PlotExample(
|
||||
"Setting defaults and font arguments",
|
||||
"",
|
||||
[
|
||||
quote
|
||||
begin
|
||||
using Plots
|
||||
default(
|
||||
titlefont = (20, "times"),
|
||||
legendfontsize = 18,
|
||||
guidefont = (18, :darkgreen),
|
||||
tickfont = (12, :orange),
|
||||
guide = "x",
|
||||
framestyle = :zerolines,
|
||||
yminorgrid = true
|
||||
)
|
||||
plot(
|
||||
[sin, cos],
|
||||
-2π,
|
||||
2π,
|
||||
label = ["sin(θ)" "cos(θ)"],
|
||||
title = "Trigonometric Functions",
|
||||
xlabel = "θ",
|
||||
linewidth = 2,
|
||||
legend = :outertopleft,
|
||||
)
|
||||
end
|
||||
end,
|
||||
],
|
||||
),
|
||||
PlotExample(
|
||||
"Heatmap with DateTime axis",
|
||||
"",
|
||||
[
|
||||
quote
|
||||
begin
|
||||
using Dates
|
||||
z = rand(5, 5)
|
||||
x = DateTime.(2016:2020)
|
||||
y = 1:5
|
||||
heatmap(x, y, z)
|
||||
end
|
||||
end,
|
||||
],
|
||||
),
|
||||
PlotExample(
|
||||
"Linked axes",
|
||||
"",
|
||||
[
|
||||
quote
|
||||
begin
|
||||
x = -5:0.1:5
|
||||
plot(plot(x, x->x^2), plot(x, x->sin(x)), layout = 2, link = :y)
|
||||
end
|
||||
end,
|
||||
],
|
||||
),
|
||||
PlotExample(
|
||||
"Error bars and array type recipes",
|
||||
"",
|
||||
[
|
||||
quote
|
||||
begin
|
||||
struct Measurement <: Number
|
||||
val::Float64
|
||||
err::Float64
|
||||
end
|
||||
value(m::Measurement) = m.val
|
||||
uncertainty(m::Measurement) = m.err
|
||||
|
||||
@recipe function f(::Type{T}, m::T) where T <: AbstractArray{<:Measurement}
|
||||
if !(get(plotattributes, :seriestype, :path) in [:contour, :contourf, :contour3d, :heatmap, :surface, :wireframe, :image])
|
||||
error_sym = Symbol(plotattributes[:letter], :error)
|
||||
plotattributes[error_sym] = uncertainty.(m)
|
||||
end
|
||||
value.(m)
|
||||
end
|
||||
|
||||
x = Measurement.(10sort(rand(10)), rand(10))
|
||||
y = Measurement.(10sort(rand(10)), rand(10))
|
||||
z = Measurement.(10sort(rand(10)), rand(10))
|
||||
surf = Measurement.((1:10) .* (1:10)', rand(10,10))
|
||||
|
||||
plot(
|
||||
scatter(x, [x y], msw = 0),
|
||||
scatter(x, y, z, msw = 0),
|
||||
heatmap(x, y, surf),
|
||||
wireframe(x, y, surf),
|
||||
legend = :topleft
|
||||
)
|
||||
end
|
||||
end,
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
# Some constants for PlotDocs and PlotReferenceImages
|
||||
@@ -887,7 +995,7 @@ _backend_skips = Dict(
|
||||
:pgfplots => [2, 5, 6, 10, 16, 20, 22, 23, 25, 28, 30, 31, 34, 37, 38, 39],
|
||||
:pgfplotsx => [ 6, # images
|
||||
10, # histogram2d
|
||||
20, # annotations due to missing sanitation
|
||||
16, # pgfplots thinks the upper panel is too small
|
||||
22, # contourf
|
||||
23, # pie
|
||||
32, # spy
|
||||
|
||||
@@ -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,
|
||||
|
||||
+36
-11
@@ -1,4 +1,28 @@
|
||||
# Error for aliases used in recipes
|
||||
function warn_on_recipe_aliases!(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] = pop_kw!(plotattributes, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
function warn_on_recipe_aliases!(v::AbstractVector, recipe_type, args...)
|
||||
foreach(x -> warn_on_recipe_aliases!(x, recipe_type, args...), v)
|
||||
end
|
||||
function warn_on_recipe_aliases!(rd::RecipeData, recipe_type, args...)
|
||||
warn_on_recipe_aliases!(rd.plotattributes, recipe_type, args...)
|
||||
end
|
||||
|
||||
function signature_string(::Type{Val{:user}}, args...)
|
||||
return string("(::", join(string.(typeof.(args)), ", ::"), ")")
|
||||
end
|
||||
signature_string(::Type{Val{:type}}, T) = "(::Type{$T}, ::$T)"
|
||||
signature_string(::Type{Val{:plot}}, st) = "(::Type{Val{:$st}}, ::AbstractPlot)"
|
||||
signature_string(::Type{Val{:series}}, st) = "(::Type{Val{:$st}}, x, y, z)"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# preprocessing
|
||||
@@ -40,14 +64,8 @@ function _preprocess_args(plotattributes::AKW, args, still_to_process::Vector{Re
|
||||
# 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
|
||||
if k in _all_subplot_args || k in _all_axis_args
|
||||
reset_kw!(plotattributes, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -82,7 +100,11 @@ function _process_userrecipes(plt::Plot, plotattributes::AKW, args)
|
||||
if isempty(next_series.args)
|
||||
_process_userrecipe(plt, kw_list, next_series)
|
||||
else
|
||||
rd_list = RecipesBase.apply_recipe(next_series.plotattributes, next_series.args...)
|
||||
rd_list = RecipesBase.apply_recipe(
|
||||
next_series.plotattributes,
|
||||
next_series.args...
|
||||
)
|
||||
warn_on_recipe_aliases!(rd_list, :user, next_series.args...)
|
||||
prepend!(still_to_process,rd_list)
|
||||
end
|
||||
end
|
||||
@@ -137,7 +159,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)
|
||||
@@ -184,6 +206,7 @@ function _process_plotrecipe(plt::Plot, kw::AKW, kw_list::Vector{KW}, still_to_p
|
||||
st = kw[:seriestype]
|
||||
st = kw[:seriestype] = get(_typeAliases, st, st)
|
||||
datalist = RecipesBase.apply_recipe(kw, Val{st}, plt)
|
||||
warn_on_recipe_aliases!(datalist, :plot, st)
|
||||
for data in datalist
|
||||
preprocessArgs!(data.plotattributes)
|
||||
if data.plotattributes[:seriestype] == st
|
||||
@@ -408,7 +431,9 @@ function _process_seriesrecipe(plt::Plot, plotattributes::AKW)
|
||||
|
||||
else
|
||||
# get a sub list of series for this seriestype
|
||||
datalist = RecipesBase.apply_recipe(plotattributes, Val{st}, plotattributes[:x], plotattributes[:y], plotattributes[:z])
|
||||
x, y, z = plotattributes[:x], plotattributes[:y], plotattributes[:z]
|
||||
datalist = RecipesBase.apply_recipe(plotattributes, Val{st}, x, y, z)
|
||||
warn_on_recipe_aliases!(datalist, :series, st)
|
||||
|
||||
# assuming there was no error, recursively apply the series recipes
|
||||
for data in datalist
|
||||
|
||||
+541
-210
File diff suppressed because it is too large
Load Diff
+48
-35
@@ -1012,49 +1012,62 @@ function error_zipit(ebar)
|
||||
end
|
||||
end
|
||||
|
||||
function error_coords(xorig, yorig, ebar)
|
||||
# init empty x/y, and zip errors if passed Tuple{Vector,Vector}
|
||||
x, y = Array{float_extended_type(xorig)}(undef, 0), Array{Float64}(undef, 0)
|
||||
# for each point, create a line segment from the bottom to the top of the errorbar
|
||||
for i = 1:max(length(xorig), length(yorig))
|
||||
xi = _cycle(xorig, i)
|
||||
yi = _cycle(yorig, i)
|
||||
ebi = _cycle(ebar, i)
|
||||
nanappend!(x, [xi, xi])
|
||||
e1, e2 = if istuple(ebi)
|
||||
first(ebi), last(ebi)
|
||||
elseif isscalar(ebi)
|
||||
ebi, ebi
|
||||
else
|
||||
error("unexpected ebi type $(typeof(ebi)) for errorbar: $ebi")
|
||||
error_tuple(x) = x, x
|
||||
error_tuple(x::Tuple) = x
|
||||
|
||||
function error_coords(errorbar, errordata, otherdata...)
|
||||
ed = Vector{float_extended_type(errordata)}(undef, 0)
|
||||
od = [Float64[] for _ in otherdata]
|
||||
for (i, edi) in enumerate(errordata)
|
||||
for (j, odj) in enumerate(otherdata)
|
||||
odi = _cycle(odj, i)
|
||||
nanappend!(od[j], [odi, odi])
|
||||
end
|
||||
nanappend!(y, [yi - e1, yi + e2])
|
||||
e1, e2 = error_tuple(_cycle(errorbar, i))
|
||||
nanappend!(ed, [edi - e1, edi + e2])
|
||||
end
|
||||
x, y
|
||||
return (ed, od...)
|
||||
end
|
||||
|
||||
# we will create a series of path segments, where each point represents one
|
||||
# side of an errorbar
|
||||
@recipe function f(::Type{Val{:yerror}}, x, y, z)
|
||||
error_style!(plotattributes)
|
||||
markershape := :hline
|
||||
plotattributes[:x], plotattributes[:y] = error_coords(
|
||||
plotattributes[:x],
|
||||
plotattributes[:y],
|
||||
error_zipit(plotattributes[:yerror]),
|
||||
)
|
||||
()
|
||||
end
|
||||
@deps yerror path
|
||||
|
||||
@recipe function f(::Type{Val{:xerror}}, x, y, z)
|
||||
error_style!(plotattributes)
|
||||
markershape := :vline
|
||||
plotattributes[:y], plotattributes[:x] = error_coords(
|
||||
plotattributes[:y],
|
||||
plotattributes[:x],
|
||||
error_zipit(plotattributes[:xerror]),
|
||||
)
|
||||
xerr = error_zipit(plotattributes[:xerror])
|
||||
if z === nothing
|
||||
plotattributes[:x], plotattributes[:y] = error_coords(xerr, x, y)
|
||||
else
|
||||
plotattributes[:x], plotattributes[:y], plotattributes[:z] =
|
||||
error_coords(xerr, x, y, z)
|
||||
end
|
||||
()
|
||||
end
|
||||
@deps xerror path
|
||||
|
||||
@recipe function f(::Type{Val{:yerror}}, x, y, z)
|
||||
error_style!(plotattributes)
|
||||
markershape := :hline
|
||||
yerr = error_zipit(plotattributes[:yerror])
|
||||
if z === nothing
|
||||
plotattributes[:y], plotattributes[:x] = error_coords(yerr, y, x)
|
||||
else
|
||||
plotattributes[:y], plotattributes[:x], plotattributes[:z] =
|
||||
error_coords(yerr, y, x, z)
|
||||
end
|
||||
()
|
||||
end
|
||||
@deps yerror path
|
||||
|
||||
@recipe function f(::Type{Val{:zerror}}, x, y, z)
|
||||
error_style!(plotattributes)
|
||||
markershape := :hline
|
||||
if z !== nothing
|
||||
zerr = error_zipit(plotattributes[:zerror])
|
||||
plotattributes[:z], plotattributes[:x], plotattributes[:y] =
|
||||
error_coords(zerr, z, x, y)
|
||||
end
|
||||
()
|
||||
end
|
||||
@deps xerror path
|
||||
@@ -1261,8 +1274,8 @@ end
|
||||
yflip := true
|
||||
aspect_ratio := 1
|
||||
rs, cs, zs = findnz(z.surf)
|
||||
xlim := ignorenan_extrema(cs)
|
||||
ylim := ignorenan_extrema(rs)
|
||||
xlims := ignorenan_extrema(cs)
|
||||
ylims := ignorenan_extrema(rs)
|
||||
if plotattributes[:markershape] == :none
|
||||
markershape := :circle
|
||||
end
|
||||
|
||||
+212
-205
@@ -15,40 +15,43 @@ prepareSeriesData(x) = error("Cannot convert $(typeof(x)) to series data for plo
|
||||
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(ar::AbstractRange{<:Number}) = ar
|
||||
function prepareSeriesData(a::AbstractArray{<:MaybeNumber})
|
||||
f = isimmutable(a) ? replace : replace!
|
||||
a = f(x -> ismissing(x) || isinf(x) ? NaN : x, map(float, a))
|
||||
end
|
||||
prepareSeriesData(a::AbstractArray{<:Missing}) = fill(NaN, axes(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)]
|
||||
series_vector(x, plotattributes) = [prepareSeriesData(x)]
|
||||
|
||||
# fixed number of blank series
|
||||
convertToAnyVector(n::Integer, plotattributes) = Any[zeros(0) for i in 1:n]
|
||||
series_vector(n::Integer, plotattributes) = [zeros(0) for i in 1:n]
|
||||
|
||||
# vector of data points is a single series
|
||||
convertToAnyVector(v::AVec{<:DataPoint}, plotattributes) = Any[prepareSeriesData(v)]
|
||||
series_vector(v::AVec{<:DataPoint}, plotattributes) = [prepareSeriesData(v)]
|
||||
|
||||
# list of things (maybe other vectors, functions, or something else)
|
||||
function convertToAnyVector(v::AVec, plotattributes)
|
||||
function series_vector(v::AVec, plotattributes)
|
||||
if all(x -> x isa MaybeNumber, v)
|
||||
convertToAnyVector(Vector{MaybeNumber}(v), plotattributes)
|
||||
series_vector(Vector{MaybeNumber}(v), plotattributes)
|
||||
elseif all(x -> x isa MaybeString, v)
|
||||
convertToAnyVector(Vector{MaybeString}(v), plotattributes)
|
||||
series_vector(Vector{MaybeString}(v), plotattributes)
|
||||
else
|
||||
vcat((convertToAnyVector(vi, plotattributes) for vi in v)...)
|
||||
vcat((series_vector(vi, plotattributes) for vi in v)...)
|
||||
end
|
||||
end
|
||||
|
||||
# Matrix is split into columns
|
||||
function convertToAnyVector(v::AMat{<:DataPoint}, plotattributes)
|
||||
function series_vector(v::AMat{<:DataPoint}, plotattributes)
|
||||
if all3D(plotattributes)
|
||||
Any[prepareSeriesData(Surface(v))]
|
||||
[prepareSeriesData(Surface(v))]
|
||||
else
|
||||
Any[prepareSeriesData(v[:, i]) for i in axes(v, 2)]
|
||||
[prepareSeriesData(v[:, i]) for i in axes(v, 2)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,34 +60,32 @@ end
|
||||
|
||||
|
||||
process_fillrange(range::Number, plotattributes) = [range]
|
||||
process_fillrange(range, plotattributes) = convertToAnyVector(range, plotattributes)
|
||||
process_fillrange(range, plotattributes) = series_vector(range, plotattributes)
|
||||
|
||||
process_ribbon(ribbon::Number, plotattributes) = [ribbon]
|
||||
process_ribbon(ribbon, plotattributes) = convertToAnyVector(ribbon, plotattributes)
|
||||
process_ribbon(ribbon, plotattributes) = series_vector(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)))
|
||||
process_ribbon(ribbon::Tuple{S, T}, plotattributes) where {S, T} = collect(zip(
|
||||
series_vector(ribbon[1], plotattributes),
|
||||
series_vector(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) = x
|
||||
|
||||
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::Nothing, y::Nothing, z) = axes(z,2)
|
||||
compute_y(x, y::Function, z) = map(y, x)
|
||||
compute_y(x, y, z) = y
|
||||
|
||||
# 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)
|
||||
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) = z
|
||||
|
||||
nobigs(v::AVec{BigFloat}) = map(Float64, v)
|
||||
nobigs(v::AVec{BigInt}) = map(Int64, v)
|
||||
@@ -107,10 +108,16 @@ compute_xyz(x::Nothing, y::Nothing, z::Nothing) = error("x/y/z are all no
|
||||
|
||||
# we are going to build recipes to do the processing and splitting of the args
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# The catch-all SliceIt recipe
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# ensure we dispatch to the slicer
|
||||
struct SliceIt end
|
||||
|
||||
# the catch-all recipes
|
||||
# The `SliceIt` recipe finishes user and type recipe processing.
|
||||
# It splits processed data into individual series data, stores in copied `plotattributes`
|
||||
# for each series and returns no arguments.
|
||||
@recipe function f(::Type{SliceIt}, x, y, z)
|
||||
|
||||
# handle data with formatting attached
|
||||
@@ -127,10 +134,9 @@ struct SliceIt end
|
||||
z = z.data
|
||||
end
|
||||
|
||||
xs = convertToAnyVector(x, plotattributes)
|
||||
ys = convertToAnyVector(y, plotattributes)
|
||||
zs = convertToAnyVector(z, plotattributes)
|
||||
|
||||
xs = series_vector(x, plotattributes)
|
||||
ys = series_vector(y, plotattributes)
|
||||
zs = series_vector(z, plotattributes)
|
||||
|
||||
fr = pop!(plotattributes, :fillrange, nothing)
|
||||
fillranges = process_fillrange(fr, plotattributes)
|
||||
@@ -140,8 +146,6 @@ struct SliceIt end
|
||||
ribbons = process_ribbon(rib, plotattributes)
|
||||
mr = length(ribbons)
|
||||
|
||||
# @show zs
|
||||
|
||||
mx = length(xs)
|
||||
my = length(ys)
|
||||
mz = length(zs)
|
||||
@@ -166,56 +170,119 @@ struct SliceIt end
|
||||
nothing # don't add a series for the main block
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Apply type recipes
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# this is the default "type recipe"... just pass the object through
|
||||
@recipe f(::Type{T}, v::T) where {T<:Any} = v
|
||||
@recipe f(::Type{T}, v::T) where T = 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]
|
||||
function _apply_type_recipe(plotattributes, v, letter)
|
||||
_preprocess_axis_args!(plotattributes, letter)
|
||||
rdvec = RecipesBase.apply_recipe(plotattributes, typeof(v), v)
|
||||
warn_on_recipe_aliases!(plotattributes, :type, typeof(v))
|
||||
_postprocess_axis_args!(plotattributes, letter)
|
||||
return rdvec[1].args[1]
|
||||
end
|
||||
|
||||
# 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)
|
||||
function _apply_type_recipe(plotattributes, v::AbstractArray, letter)
|
||||
_preprocess_axis_args!(plotattributes, letter)
|
||||
# First we try to apply an array type recipe.
|
||||
w = RecipesBase.apply_recipe(plotattributes, typeof(v), v)[1].args[1]
|
||||
warn_on_recipe_aliases!(plotattributes, :type, typeof(v))
|
||||
# If the type did not change try it element-wise
|
||||
if typeof(v) == typeof(w)
|
||||
isempty(skipmissing(v)) && return Float64[]
|
||||
x = first(skipmissing(v))
|
||||
args = RecipesBase.apply_recipe(plotattributes, typeof(x), x)[1].args
|
||||
warn_on_recipe_aliases!(plotattributes, :type, typeof(x))
|
||||
_postprocess_axis_args!(plotattributes, letter)
|
||||
if length(args) == 2 && all(arg -> arg isa Function, args)
|
||||
numfunc, formatter = args
|
||||
return Formatted(map(numfunc, v), formatter)
|
||||
else
|
||||
return v
|
||||
end
|
||||
end
|
||||
_postprocess_axis_args!(plotattributes, letter)
|
||||
return w
|
||||
end
|
||||
|
||||
# special handling for Surface... need to properly unwrap and re-wrap
|
||||
_apply_type_recipe(
|
||||
plotattributes,
|
||||
v::Surface{<:AMat{<:Union{AbstractFloat, Integer, AbstractString, Missing}}},
|
||||
letter,
|
||||
) = v
|
||||
function _apply_type_recipe(plotattributes, v::Surface, letter)
|
||||
ret = _apply_type_recipe(plotattributes, v.surf, letter)
|
||||
if typeof(ret) <: Formatted
|
||||
Formatted(Surface(ret.data), ret.formatter)
|
||||
else
|
||||
v
|
||||
Surface(ret)
|
||||
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 vectors of datapoints and for nothing
|
||||
_apply_type_recipe(plotattributes, v::Nothing, letter) = v
|
||||
_apply_type_recipe(
|
||||
plotattributes,
|
||||
v::AbstractArray{<:Union{AbstractFloat, Integer, AbstractString, Missing}},
|
||||
letter,
|
||||
) = v
|
||||
|
||||
# don't do anything for ints or floats
|
||||
_apply_type_recipe(plotattributes, v::AbstractArray{T}) where {T<:Union{Integer,AbstractFloat}} = v
|
||||
# axis args before type recipes should still be mapped to all axes
|
||||
function _preprocess_axis_args!(plotattributes)
|
||||
for (k, v) in plotattributes
|
||||
if is_axis_attr_noletter(k)
|
||||
pop!(plotattributes, k)
|
||||
for l in (:x, :y, :z)
|
||||
lk = Symbol(l, k)
|
||||
haskey(plotattributes, lk) || (plotattributes[lk] = v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function _preprocess_axis_args!(plotattributes, letter)
|
||||
plotattributes[:letter] = letter
|
||||
_preprocess_axis_args!(plotattributes)
|
||||
end
|
||||
|
||||
# axis args in type recipes should only be applied to the current axis
|
||||
function _postprocess_axis_args!(plotattributes, letter)
|
||||
pop!(plotattributes, :letter)
|
||||
if letter in (:x, :y, :z)
|
||||
for (k, v) in plotattributes
|
||||
if is_axis_attr_noletter(k)
|
||||
pop!(plotattributes, k)
|
||||
lk = Symbol(letter, k)
|
||||
haskey(plotattributes, lk) || (plotattributes[lk] = v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Fallback user recipes calling type recipes
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# handle "type recipes" by converting inputs, and then either re-calling or slicing
|
||||
@recipe function f(x, y, z)
|
||||
wrap_surfaces!(plotattributes, x, y, z)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(plotattributes, x)
|
||||
newx = _apply_type_recipe(plotattributes, x, :x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(plotattributes, y)
|
||||
newy = _apply_type_recipe(plotattributes, y, :y)
|
||||
y === newy || (did_replace = true)
|
||||
newz = _apply_type_recipe(plotattributes, z)
|
||||
newz = _apply_type_recipe(plotattributes, z, :z)
|
||||
z === newz || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy, newz
|
||||
@@ -224,10 +291,11 @@ _apply_type_recipe(plotattributes, v::AbstractArray{T}) where {T<:Union{Integer,
|
||||
end
|
||||
end
|
||||
@recipe function f(x, y)
|
||||
wrap_surfaces!(plotattributes, x, y)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(plotattributes, x)
|
||||
newx = _apply_type_recipe(plotattributes, x, :x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(plotattributes, y)
|
||||
newy = _apply_type_recipe(plotattributes, y, :y)
|
||||
y === newy || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy
|
||||
@@ -236,7 +304,8 @@ end
|
||||
end
|
||||
end
|
||||
@recipe function f(y)
|
||||
newy = _apply_type_recipe(plotattributes, y)
|
||||
wrap_surfaces!(plotattributes, y)
|
||||
newy = _apply_type_recipe(plotattributes, y, :y)
|
||||
if y !== newy
|
||||
newy
|
||||
else
|
||||
@@ -249,7 +318,7 @@ end
|
||||
@recipe function f(v1, v2, v3, v4, vrest...)
|
||||
did_replace = false
|
||||
newargs = map(v -> begin
|
||||
newv = _apply_type_recipe(plotattributes, v)
|
||||
newv = _apply_type_recipe(plotattributes, v, :unknown)
|
||||
if newv !== v
|
||||
did_replace = true
|
||||
end
|
||||
@@ -262,12 +331,14 @@ end
|
||||
end
|
||||
|
||||
|
||||
# # --------------------------------------------------------------------
|
||||
# # 1 argument
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
# helper function to ensure relevant attributes are wrapped by Surface
|
||||
function wrap_surfaces(plotattributes::AKW)
|
||||
function wrap_surfaces!(plotattributes, args...) end
|
||||
wrap_surfaces!(plotattributes, x::AMat, y::AMat, z::AMat) = wrap_surfaces!(plotattributes)
|
||||
wrap_surfaces!(plotattributes, x::AVec, y::AVec, z::AMat) = wrap_surfaces!(plotattributes)
|
||||
function wrap_surfaces!(plotattributes, x::AVec, y::AVec, z::Surface)
|
||||
wrap_surfaces!(plotattributes)
|
||||
end
|
||||
function wrap_surfaces!(plotattributes)
|
||||
if haskey(plotattributes, :fill_z)
|
||||
v = plotattributes[:fill_z]
|
||||
if !isa(v, Surface)
|
||||
@@ -276,42 +347,57 @@ function wrap_surfaces(plotattributes::AKW)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 1 argument
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@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))
|
||||
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}
|
||||
@recipe function f(mat::AMat)
|
||||
if all3D(plotattributes)
|
||||
n,m = axes(mat)
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, m, n, Surface(mat)
|
||||
n, m = axes(mat)
|
||||
m, n, Surface(mat)
|
||||
else
|
||||
SliceIt, nothing, mat, nothing
|
||||
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
|
||||
@recipe function f(fmt::Formatted{<:AMat})
|
||||
if all3D(plotattributes)
|
||||
mat = fmt.data
|
||||
n,m = axes(mat)
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, m, n, Formatted(Surface(mat), fmt.formatter)
|
||||
n, m = axes(mat)
|
||||
m, n, Formatted(Surface(mat), fmt.formatter)
|
||||
else
|
||||
SliceIt, nothing, fmt, nothing
|
||||
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}
|
||||
@recipe function f(vol::AbstractArray{<:MaybeNumber, 3}, args...)
|
||||
seriestype := :volume
|
||||
SliceIt, nothing, Volume(vol, args...), nothing
|
||||
end
|
||||
|
||||
|
||||
# # images - grays
|
||||
function clamp_greys!(mat::AMat{T}) where T<:Gray
|
||||
# images - grays
|
||||
function clamp_greys!(mat::AMat{<:Gray})
|
||||
for i in eachindex(mat)
|
||||
mat[i].val < 0 && (mat[i] = Gray(0))
|
||||
mat[i].val > 1 && (mat[i] = Gray(1))
|
||||
@@ -319,7 +405,7 @@ function clamp_greys!(mat::AMat{T}) where T<:Gray
|
||||
mat
|
||||
end
|
||||
|
||||
@recipe function f(mat::AMat{T}) where T<:Gray
|
||||
@recipe function f(mat::AMat{<:Gray})
|
||||
n, m = axes(mat)
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
@@ -334,8 +420,7 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
# # images - colors
|
||||
|
||||
# images - colors
|
||||
@recipe function f(mat::AMat{T}) where T<:Colorant
|
||||
n, m = axes(mat)
|
||||
|
||||
@@ -353,8 +438,7 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# # plotting arbitrary shapes/polygons
|
||||
# plotting arbitrary shapes/polygons
|
||||
|
||||
@recipe function f(shape::Shape)
|
||||
seriestype --> :shape
|
||||
@@ -388,40 +472,21 @@ end
|
||||
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])))
|
||||
xm = PlotUtils.tryrange(f, xinv.([-5,-1,0,0.01]))
|
||||
xm, PlotUtils.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)
|
||||
# --------------------------------------------------------------------
|
||||
# 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)
|
||||
@@ -429,58 +494,25 @@ end
|
||||
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
|
||||
# --------------------------------------------------------------------
|
||||
# 3 arguments
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# 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
|
||||
x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
|
||||
end
|
||||
|
||||
#
|
||||
# # surface-like... matrix grid
|
||||
|
||||
# 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)
|
||||
x, y, Surface(z)
|
||||
end
|
||||
|
||||
# # images - grays
|
||||
|
||||
# images - grays
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Gray
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
@@ -495,8 +527,7 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
# # images - colors
|
||||
|
||||
# images - colors
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Colorant
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
@@ -511,27 +542,19 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Parametric functions
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# # special handling... xmin/xmax with parametric function(s)
|
||||
# --------------------------------------------------------------------
|
||||
# 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
|
||||
unzip(_scaled_adapted_grid.(fs, xscale, yscale, xmin, xmax))
|
||||
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)
|
||||
@@ -542,8 +565,7 @@ function _scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
|
||||
xinv.(xs), yinv.(ys)
|
||||
end
|
||||
|
||||
#
|
||||
# # special handling... 3D parametric function(s)
|
||||
# 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
|
||||
@@ -551,12 +573,10 @@ end
|
||||
fx, fy, fz, range(umin, stop = umax, length = numPoints)
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Lists of tuples and GeometryTypes.Points
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Lists of tuples and GeometryTypes.Points
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@recipe f(v::AVec{<:Tuple}) = unzip(v)
|
||||
@recipe f(v::AVec{<:GeometryTypes.Point}) = unzip(v)
|
||||
@@ -566,23 +586,10 @@ end
|
||||
# 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
|
||||
# --------------------------------------------------------------------
|
||||
# handle grouping
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
splittable_kw(key, val, lengthGroup) = false
|
||||
splittable_kw(key, val::AbstractArray, lengthGroup) = !(key in (:group, :color_palette)) && length(axes(val,1)) == lengthGroup
|
||||
|
||||
+4
-3
@@ -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"
|
||||
|
||||
+4
-3
@@ -26,8 +26,9 @@ struct Attr <: AbstractDict{Symbol,Any}
|
||||
defaults::KW
|
||||
end
|
||||
|
||||
Base.getindex(attr::Attr, k) = haskey(attr.explicit,k) ?
|
||||
attr.explicit[k] : attr.defaults[k]
|
||||
function Base.getindex(attr::Attr, k)
|
||||
return haskey(attr.explicit, k) ? attr.explicit[k] : attr.defaults[k]
|
||||
end
|
||||
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)
|
||||
@@ -41,7 +42,7 @@ end
|
||||
function Base.delete!(attr::Attr, k)
|
||||
haskey(attr.explicit, k) && delete!(attr.explicit, k)
|
||||
haskey(attr.defaults, k) && delete!(attr.defaults, k)
|
||||
end
|
||||
end
|
||||
Base.length(attr::Attr) = length(union(keys(attr.explicit), keys(attr.defaults)))
|
||||
function Base.iterate(attr::Attr)
|
||||
exp_keys = keys(attr.explicit)
|
||||
|
||||
+162
-307
@@ -1,119 +1,4 @@
|
||||
|
||||
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)
|
||||
colors = ColorGradient(vec(z))
|
||||
@@ -262,10 +147,10 @@ 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
|
||||
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}},
|
||||
@@ -275,15 +160,13 @@ unzip(v::Union{AVec{<:GeometryTypes.Point},
|
||||
|
||||
# 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]
|
||||
@@ -301,21 +184,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]] = 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]
|
||||
@@ -334,20 +217,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 = scalefunc(scale), invscalefunc(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})
|
||||
@@ -367,8 +250,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))
|
||||
@@ -380,11 +263,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)))
|
||||
@@ -752,119 +635,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
|
||||
|
||||
@@ -908,33 +779,60 @@ 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))
|
||||
|
||||
# -------------------------------------------------------
|
||||
|
||||
@@ -968,59 +866,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
|
||||
|
||||
@@ -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...)
|
||||
|
||||
@@ -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
|
||||
+62
-6
@@ -20,6 +20,19 @@ end
|
||||
@test count(x -> x isa PGFPlotsX.Plot, axis.contents) == 1
|
||||
@test !haskey(axis.contents[1].options.dict, "fill")
|
||||
|
||||
@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
|
||||
|
||||
@testset "3D docs example" begin
|
||||
n = 100
|
||||
ts = range(0, stop = 8π, length = n)
|
||||
@@ -74,8 +87,18 @@ end
|
||||
@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)
|
||||
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
|
||||
@@ -222,15 +245,39 @@ end
|
||||
end # testset
|
||||
@testset "Annotations" begin
|
||||
y = rand(10)
|
||||
plot(
|
||||
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),
|
||||
@@ -244,9 +291,18 @@ end
|
||||
Plots.text("data", :green),
|
||||
],
|
||||
)
|
||||
# mktempdir() do path
|
||||
# @test_nowarn savefig(annotation_plot, path*"annotation.pdf")
|
||||
# end
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user