Merge pull request #3749 from t-bltg/format

Code style - format
This commit is contained in:
t-bltg 2021-08-12 18:48:42 +02:00 committed by GitHub
commit 54a6518d59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 5162 additions and 3661 deletions

12
.JuliaFormatter.toml Normal file
View File

@ -0,0 +1,12 @@
always_for_in = true
import_to_using = false
align_pair_arrow = true
align_assignment = true
align_conditional = true
always_use_return = false
conditional_to_if = false
whitespace_in_kwargs = true
remove_extra_newlines = true
whitespace_ops_in_indices = true
short_to_long_function_def = false
annotate_untyped_fields_with_any = false

View File

@ -1,4 +1,4 @@
name: Run benchmarks name: benchmarks
on: on:
pull_request: pull_request:
@ -6,9 +6,6 @@ on:
jobs: jobs:
Benchmark: Benchmark:
if: "!contains(github.event.head_commit.message, '[skip ci]')" if: "!contains(github.event.head_commit.message, '[skip ci]')"
env:
GKS_ENCODING: "utf8"
GKSwstype: "100"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -16,7 +13,7 @@ jobs:
with: with:
version: 1 version: 1
## Setup # Setup
- name: Ubuntu TESTCMD - name: Ubuntu TESTCMD
run: echo "TESTCMD=xvfb-run --auto-servernum julia" >> $GITHUB_ENV run: echo "TESTCMD=xvfb-run --auto-servernum julia" >> $GITHUB_ENV
- name: Install Plots dependencies - name: Install Plots dependencies

View File

@ -72,9 +72,9 @@ jobs:
# Run tests # Run tests
- name: Run Graphical test - name: Run Graphical test
run: | run: |
$TESTCMD --project -e 'using Pkg; Pkg.test(coverage=true);' $TESTCMD --project -e 'using Pkg; Pkg.test(coverage=true)'
$TESTCMD -e 'using Pkg; Pkg.activate(tempdir()); Pkg.develop(path=abspath(".")); Pkg.add("StatsPlots"); Pkg.test("StatsPlots");' $TESTCMD -e 'using Pkg; Pkg.activate(tempdir()); Pkg.develop(path=abspath(".")); Pkg.add("StatsPlots"); Pkg.test("StatsPlots")'
$TESTCMD -e 'using Pkg; Pkg.activate(tempdir()); Pkg.develop(path=abspath(".")); Pkg.add("GraphRecipes"); Pkg.test("GraphRecipes");' $TESTCMD -e 'using Pkg; Pkg.activate(tempdir()); Pkg.develop(path=abspath(".")); Pkg.add("GraphRecipes"); Pkg.test("GraphRecipes")'
- name: Codecov - name: Codecov
uses: julia-actions/julia-uploadcodecov@latest uses: julia-actions/julia-uploadcodecov@latest

26
.github/workflows/format.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: format
on:
pull_request:
jobs:
JuliaFormatter:
if: "!contains(github.event.head_commit.message, '[skip ci]')"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@latest
with:
version: 1
# Setup
- name: Ubuntu TESTCMD
run: echo "TESTCMD=xvfb-run --auto-servernum julia" >> $GITHUB_ENV
# Check format
- name: Install JuliaFormatter and format
run: |
git diff --name-only --exit-code
$TESTCMD -e 'using Pkg; pkg"add JuliaFormatter CSTParser#master"'
$TESTCMD -e 'using JuliaFormatter; format(["src", "test"])'
git diff --exit-code

View File

@ -4,7 +4,17 @@ if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@optle
@eval Base.Experimental.@optlevel 1 @eval Base.Experimental.@optlevel 1
end end
const _current_plots_version = VersionNumber(split(first(filter(line -> occursin("version", line), readlines(normpath(@__DIR__, "..", "Project.toml")))), "\"")[2]) const _current_plots_version = VersionNumber(
split(
first(
filter(
line -> occursin("version", line),
readlines(normpath(@__DIR__, "..", "Project.toml")),
),
),
"\"",
)[2],
)
using Reexport using Reexport
@ -25,6 +35,7 @@ import JSON
using Requires using Requires
#! format: off
export export
grid, grid,
bbox, bbox,
@ -117,7 +128,7 @@ export
scalefontsize, scalefontsize,
scalefontsizes, scalefontsizes,
resetfontsizes resetfontsizes
#! format: on
# --------------------------------------------------------- # ---------------------------------------------------------
import NaNMath # define functions that ignores NaNs. To overcome the destructive effects of https://github.com/JuliaLang/julia/pull/12563 import NaNMath # define functions that ignores NaNs. To overcome the destructive effects of https://github.com/JuliaLang/julia/pull/12563
@ -136,7 +147,6 @@ ignorenan_extrema(x) = Base.extrema(x)
# This makes it impossible to create row vectors of String and Symbol with the transpose operator. # This makes it impossible to create row vectors of String and Symbol with the transpose operator.
# This solves this issue, internally in Plots at least. # This solves this issue, internally in Plots at least.
# commented out on the insistence of the METADATA maintainers # commented out on the insistence of the METADATA maintainers
#Base.transpose(x::Symbol) = x #Base.transpose(x::Symbol) = x
@ -147,12 +157,13 @@ ignorenan_extrema(x) = Base.extrema(x)
import Measures import Measures
module PlotMeasures module PlotMeasures
import Measures import Measures
import Measures: Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height, w, h import Measures:
Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height, w, h
const BBox = Measures.Absolute2DBox const BBox = Measures.Absolute2DBox
# allow pixels and percentages # allow pixels and percentages
const px = AbsoluteLength(0.254) const px = AbsoluteLength(0.254)
const pct = Length{:pct, Float64}(1.0) const pct = Length{:pct,Float64}(1.0)
Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value) Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value)
Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value) Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value)
@ -167,7 +178,8 @@ import .PlotMeasures: Length, AbsoluteLength, Measure, width, height
# --------------------------------------------------------- # ---------------------------------------------------------
import RecipesPipeline import RecipesPipeline
import RecipesPipeline: SliceIt, import RecipesPipeline:
SliceIt,
DefaultsDict, DefaultsDict,
Formatted, Formatted,
AbstractSurface, AbstractSurface,
@ -219,33 +231,30 @@ include("backends/web.jl")
include("shorthands.jl") include("shorthands.jl")
let PlotOrSubplot = Union{Plot, Subplot} let PlotOrSubplot = Union{Plot,Subplot}
global title!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; title = s, kw...) global title!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; title = s, kw...)
global xlabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; xlabel = s, kw...) global xlabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; xlabel = s, kw...)
global ylabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; ylabel = s, kw...) global ylabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; ylabel = s, kw...)
global xlims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; xlims = lims, kw...) global xlims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; xlims = lims, kw...)
global ylims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; ylims = lims, kw...) global ylims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; ylims = lims, kw...)
global zlims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; zlims = lims, kw...) global zlims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; zlims = lims, kw...)
global xlims!(plt::PlotOrSubplot, xmin::Real, xmax::Real; kw...) = plot!(plt; xlims = (xmin,xmax), kw...) global xlims!(plt::PlotOrSubplot, xmin::Real, xmax::Real; kw...) = plot!(plt; xlims = (xmin, xmax), kw...)
global ylims!(plt::PlotOrSubplot, ymin::Real, ymax::Real; kw...) = plot!(plt; ylims = (ymin,ymax), kw...) global ylims!(plt::PlotOrSubplot, ymin::Real, ymax::Real; kw...) = plot!(plt; ylims = (ymin, ymax), kw...)
global zlims!(plt::PlotOrSubplot, zmin::Real, zmax::Real; kw...) = plot!(plt; zlims = (zmin,zmax), kw...) global zlims!(plt::PlotOrSubplot, zmin::Real, zmax::Real; kw...) = plot!(plt; zlims = (zmin, zmax), kw...)
global xticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) = plot!(plt; xticks = ticks, kw...) global xticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) = plot!(plt; xticks = ticks, kw...)
global yticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) = plot!(plt; yticks = ticks, kw...) global yticks!(plt::PlotOrSubplot, ticks::TicksArgs; kw...) = plot!(plt; yticks = ticks, kw...)
global xticks!(plt::PlotOrSubplot, global xticks!(plt::PlotOrSubplot, ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(plt; xticks = (ticks, labels), kw...)
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(plt; xticks = (ticks,labels), kw...) global yticks!(plt::PlotOrSubplot, ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(plt; yticks = (ticks, labels), kw...)
global yticks!(plt::PlotOrSubplot, global xgrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xgrid = args, kw...)
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(plt; yticks = (ticks,labels), kw...) global ygrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; ygrid = args, kw...)
global xgrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xgrid = args, kw...) global annotate!(plt::PlotOrSubplot, anns...; kw...) = plot!(plt; annotation = anns, kw...)
global ygrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; ygrid = args, kw...) global annotate!(plt::PlotOrSubplot, anns::AVec{T}; kw...) where {T<:Tuple} = plot!(plt; annotation = anns, kw...)
global annotate!(plt::PlotOrSubplot, anns...; kw...) = plot!(plt; annotation = anns, kw...) global xflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; xflip = flip, kw...)
global annotate!(plt::PlotOrSubplot, anns::AVec{T}; kw...) where {T<:Tuple} = plot!(plt; annotation = anns, kw...) global yflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; yflip = flip, kw...)
global xflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; xflip = flip, kw...) global xaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xaxis = args, kw...)
global yflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; yflip = flip, kw...) global yaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; yaxis = args, kw...)
global xaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xaxis = args, kw...)
global yaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; yaxis = args, kw...)
end end
# --------------------------------------------------------- # ---------------------------------------------------------
const CURRENT_BACKEND = CurrentBackend(:none) const CURRENT_BACKEND = CurrentBackend(:none)

View File

@ -14,24 +14,24 @@ end
Add a plot (the current plot if not specified) to an existing animation Add a plot (the current plot if not specified) to an existing animation
""" """
function frame(anim::Animation, plt::P=current()) where P<:AbstractPlot function frame(anim::Animation, plt::P = current()) where {P<:AbstractPlot}
i = length(anim.frames) + 1 i = length(anim.frames) + 1
filename = @sprintf("%06d.png", i) filename = @sprintf("%06d.png", i)
png(plt, joinpath(anim.dir, filename)) png(plt, joinpath(anim.dir, filename))
push!(anim.frames, filename) push!(anim.frames, filename)
end end
giffn() = (isijulia() ? "tmp.gif" : tempname()*".gif") giffn() = (isijulia() ? "tmp.gif" : tempname() * ".gif")
movfn() = (isijulia() ? "tmp.mov" : tempname()*".mov") movfn() = (isijulia() ? "tmp.mov" : tempname() * ".mov")
mp4fn() = (isijulia() ? "tmp.mp4" : tempname()*".mp4") mp4fn() = (isijulia() ? "tmp.mp4" : tempname() * ".mp4")
webmfn() = (isijulia() ? "tmp.webm" : tempname()*".webm") webmfn() = (isijulia() ? "tmp.webm" : tempname() * ".webm")
mutable struct FrameIterator mutable struct FrameIterator
itr itr
every::Int every::Int
kw kw
end end
FrameIterator(itr; every=1, kw...) = FrameIterator(itr, every, kw) FrameIterator(itr; every = 1, kw...) = FrameIterator(itr, every, kw)
""" """
Animate from an iterator which returns the plot args each iteration. Animate from an iterator which returns the plot args each iteration.
@ -48,8 +48,8 @@ function animate(fitr::FrameIterator, fn = giffn(); kw...)
end end
# most things will implement this # most things will implement this
function animate(obj, fn = giffn(); every=1, fps=20, loop=0, kw...) function animate(obj, fn = giffn(); every = 1, fps = 20, loop = 0, kw...)
animate(FrameIterator(obj, every, kw), fn; fps=fps, loop=loop) animate(FrameIterator(obj, every, kw), fn; fps = fps, loop = loop)
end end
# ----------------------------------------------- # -----------------------------------------------
@ -69,12 +69,16 @@ webm(anim::Animation, fn = webmfn(); kw...) = buildanimation(anim, fn, false; kw
ffmpeg_framerate(fps) = "$fps" ffmpeg_framerate(fps) = "$fps"
ffmpeg_framerate(fps::Rational) = "$(fps.num)/$(fps.den)" ffmpeg_framerate(fps::Rational) = "$(fps.num)/$(fps.den)"
function buildanimation(anim::Animation, fn::AbstractString, function buildanimation(
is_animated_gif::Bool=true; anim::Animation,
fps::Real = 20, loop::Integer = 0, fn::AbstractString,
variable_palette::Bool=false, is_animated_gif::Bool = true;
verbose=false, fps::Real = 20,
show_msg::Bool=true) loop::Integer = 0,
variable_palette::Bool = false,
verbose = false,
show_msg::Bool = true,
)
if length(anim.frames) == 0 if length(anim.frames) == 0
throw(ArgumentError("Cannot build empty animations")) throw(ArgumentError("Cannot build empty animations"))
end end
@ -82,41 +86,49 @@ function buildanimation(anim::Animation, fn::AbstractString,
fn = abspath(expanduser(fn)) fn = abspath(expanduser(fn))
animdir = anim.dir animdir = anim.dir
framerate = ffmpeg_framerate(fps) framerate = ffmpeg_framerate(fps)
verbose_level = (verbose isa Int ? verbose : verbose_level = (verbose isa Int ? verbose : verbose ? 32 : 16) # "error"
verbose ? 32 # "info"
: 16) # "error"
if is_animated_gif if is_animated_gif
if variable_palette if variable_palette
# generate a colorpalette for each frame for highest quality, but larger filesize # generate a colorpalette for each frame for highest quality, but larger filesize
palette="palettegen=stats_mode=single[pal],[0:v][pal]paletteuse=new=1" palette = "palettegen=stats_mode=single[pal],[0:v][pal]paletteuse=new=1"
ffmpeg_exe(`-v $verbose_level -framerate $framerate -i $(animdir)/%06d.png -lavfi "$palette" -loop $loop -y $fn`) ffmpeg_exe(
`-v $verbose_level -framerate $framerate -i $(animdir)/%06d.png -lavfi "$palette" -loop $loop -y $fn`,
)
else else
# generate a colorpalette first so ffmpeg does not have to guess it # generate a colorpalette first so ffmpeg does not have to guess it
ffmpeg_exe(`-v $verbose_level -i $(animdir)/%06d.png -vf "palettegen=stats_mode=diff" -y "$(animdir)/palette.bmp"`) 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 # then apply the palette to get better results
ffmpeg_exe(`-v $verbose_level -framerate $framerate -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -loop $loop -y $fn`) ffmpeg_exe(
`-v $verbose_level -framerate $framerate -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -loop $loop -y $fn`,
)
end end
else else
ffmpeg_exe(`-v $verbose_level -framerate $framerate -i $(animdir)/%06d.png -vf format=yuv420p -loop $loop -y $fn`) ffmpeg_exe(
`-v $verbose_level -framerate $framerate -i $(animdir)/%06d.png -vf format=yuv420p -loop $loop -y $fn`,
)
end end
show_msg && @info("Saved animation to ", fn) show_msg && @info("Saved animation to ", fn)
AnimatedGif(fn) AnimatedGif(fn)
end end
# write out html to view the gif # write out html to view the gif
function Base.show(io::IO, ::MIME"text/html", agif::AnimatedGif) function Base.show(io::IO, ::MIME"text/html", agif::AnimatedGif)
ext = file_extension(agif.filename) ext = file_extension(agif.filename)
if ext == "gif" if ext == "gif"
html = "<img src=\"data:image/gif;base64," * base64encode(read(agif.filename)) * "\" />" html =
elseif ext in ("mov", "mp4","webm") "<img src=\"data:image/gif;base64," *
base64encode(read(agif.filename)) *
"\" />"
elseif ext in ("mov", "mp4", "webm")
mimetype = ext == "mov" ? "video/quicktime" : "video/$ext" mimetype = ext == "mov" ? "video/quicktime" : "video/$ext"
html = "<video controls><source src=\"data:$mimetype;base64," * html =
base64encode(read(agif.filename)) * "<video controls><source src=\"data:$mimetype;base64," *
"\" type = \"$mimetype\"></video>" base64encode(read(agif.filename)) *
"\" type = \"$mimetype\"></video>"
else else
error("Cannot show animation with extension $ext: $agif") error("Cannot show animation with extension $ext: $agif")
end end
@ -125,65 +137,67 @@ function Base.show(io::IO, ::MIME"text/html", agif::AnimatedGif)
return nothing return nothing
end end
# Only gifs can be shown via image/gif # Only gifs can be shown via image/gif
Base.showable(::MIME"image/gif", agif::AnimatedGif) = file_extension(agif.filename) == "gif" Base.showable(::MIME"image/gif", agif::AnimatedGif) = file_extension(agif.filename) == "gif"
function Base.show(io::IO, ::MIME"image/gif", agif::AnimatedGif) function Base.show(io::IO, ::MIME"image/gif", agif::AnimatedGif)
open(fio-> write(io, fio), agif.filename) open(fio -> write(io, fio), agif.filename)
end end
# ----------------------------------------------- # -----------------------------------------------
function _animate(forloop::Expr, args...; callgif = false) function _animate(forloop::Expr, args...; callgif = false)
if forloop.head (:for, :while) if forloop.head (:for, :while)
error("@animate macro expects a for- or while-block. got: $(forloop.head)") error("@animate macro expects a for- or while-block. got: $(forloop.head)")
end
# add the call to frame to the end of each iteration
animsym = gensym("anim")
countersym = gensym("counter")
freqassert = :()
block = forloop.args[2]
# create filter
n = length(args)
filterexpr = if n == 0
# no filter... every iteration gets a frame
true
elseif args[1] == :every
# filter every `freq` frames (starting with the first frame)
@assert n == 2
freq = args[2]
freqassert = :(@assert isa($freq, Integer) && $freq > 0)
:(mod1($countersym, $freq) == 1)
elseif args[1] == :when
# filter on custom expression
@assert n == 2
args[2]
else
error("Unsupported animate filter: $args")
end
push!(block.args, :(if $filterexpr; Plots.frame($animsym); end))
push!(block.args, :($countersym += 1))
# add a final call to `gif(anim)`?
retval = callgif ? :(Plots.gif($animsym)) : animsym
# full expression:
esc(quote
$freqassert # if filtering, check frequency is an Integer > 0
$animsym = Plots.Animation() # init animation object
let $countersym = 1 # init iteration counter
$forloop # for loop, saving a frame after each iteration
end end
$retval # return the animation object, or the gif
end) # add the call to frame to the end of each iteration
animsym = gensym("anim")
countersym = gensym("counter")
freqassert = :()
block = forloop.args[2]
# create filter
n = length(args)
filterexpr = if n == 0
# no filter... every iteration gets a frame
true
elseif args[1] == :every
# filter every `freq` frames (starting with the first frame)
@assert n == 2
freq = args[2]
freqassert = :(@assert isa($freq, Integer) && $freq > 0)
:(mod1($countersym, $freq) == 1)
elseif args[1] == :when
# filter on custom expression
@assert n == 2
args[2]
else
error("Unsupported animate filter: $args")
end
push!(block.args, :(
if $filterexpr
Plots.frame($animsym)
end
))
push!(block.args, :($countersym += 1))
# add a final call to `gif(anim)`?
retval = callgif ? :(Plots.gif($animsym)) : animsym
# full expression:
esc(quote
$freqassert # if filtering, check frequency is an Integer > 0
$animsym = Plots.Animation() # init animation object
let $countersym = 1 # init iteration counter
$forloop # for loop, saving a frame after each iteration
end
$retval # return the animation object, or the gif
end)
end end
""" """

View File

@ -1,187 +1,187 @@
const _arg_desc = KW( const _arg_desc = KW(
# series args # series args
:label => "String type. The label for a series, which appears in a legend. If empty, no legend entry is added.", :label => "String type. The label for a series, which appears in a legend. If empty, no legend entry is added.",
:seriescolor => "Color Type. The base color for this series. `:auto` (the default) will select a color from the subplot's `color_palette`, based on the order it was added to the subplot", :seriescolor => "Color Type. The base color for this series. `:auto` (the default) will select a color from the subplot's `color_palette`, based on the order it was added to the subplot",
:seriesalpha => "Number in [0,1]. The alpha/opacity override for the series. `nothing` (the default) means it will take the alpha value of the color.", :seriesalpha => "Number in [0,1]. The alpha/opacity override for the series. `nothing` (the default) means it will take the alpha value of the color.",
:seriestype => "Symbol. This is the identifier of the type of visualization for this series. Choose from $(_allTypes) or any series recipes which are defined.", :seriestype => "Symbol. This is the identifier of the type of visualization for this series. Choose from $(_allTypes) or any series recipes which are defined.",
:linestyle => "Symbol. Style of the line (for path and bar stroke). Choose from $(_allStyles)", :linestyle => "Symbol. Style of the line (for path and bar stroke). Choose from $(_allStyles)",
:linewidth => "Number. Width of the line (in pixels)", :linewidth => "Number. Width of the line (in pixels)",
:linecolor => "Color Type. Color of the line (for path and bar stroke). `:match` will take the value from `:seriescolor`, (though histogram/bar types use `:black` as a default).", :linecolor => "Color Type. Color of the line (for path and bar stroke). `:match` will take the value from `:seriescolor`, (though histogram/bar types use `:black` as a default).",
:linealpha => "Number in [0,1]. The alpha/opacity override for the line. `nothing` (the default) means it will take the alpha value of linecolor.", :linealpha => "Number in [0,1]. The alpha/opacity override for the line. `nothing` (the default) means it will take the alpha value of linecolor.",
:fillrange => "Number or AbstractVector. Fills area between fillrange and y for line-types, sets the base for bar/stick types, and similar for other types.", :fillrange => "Number or AbstractVector. Fills area between fillrange and y for line-types, sets the base for bar/stick types, and similar for other types.",
:fillcolor => "Color Type. Color of the filled area of path or bar types. `:match` will take the value from `:seriescolor`.", :fillcolor => "Color Type. Color of the filled area of path or bar types. `:match` will take the value from `:seriescolor`.",
:fillalpha => "Number in [0,1]. The alpha/opacity override for the fill area. `nothing` (the default) means it will take the alpha value of fillcolor.", :fillalpha => "Number in [0,1]. The alpha/opacity override for the fill area. `nothing` (the default) means it will take the alpha value of fillcolor.",
:markershape => "Symbol, Shape, or AbstractVector. Choose from $(_allMarkers).", :markershape => "Symbol, Shape, or AbstractVector. Choose from $(_allMarkers).",
:markercolor => "Color Type. Color of the interior of the marker or shape. `:match` will take the value from `:seriescolor`.", :markercolor => "Color Type. Color of the interior of the marker or shape. `:match` will take the value from `:seriescolor`.",
:markeralpha => "Number in [0,1]. The alpha/opacity override for the marker interior. `nothing` (the default) means it will take the alpha value of markercolor.", :markeralpha => "Number in [0,1]. The alpha/opacity override for the marker interior. `nothing` (the default) means it will take the alpha value of markercolor.",
:markersize => "Number or AbstractVector. Size (radius pixels) of the markers", :markersize => "Number or AbstractVector. Size (radius pixels) of the markers",
:markerstrokestyle => "Symbol. Style of the marker stroke (border). Choose from $(_allStyles)", :markerstrokestyle => "Symbol. Style of the marker stroke (border). Choose from $(_allStyles)",
:markerstrokewidth => "Number. Width of the marker stroke (border) in pixels", :markerstrokewidth => "Number. Width of the marker stroke (border) in pixels",
:markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:foreground_color_subplot`.", :markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:foreground_color_subplot`.",
:markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.", :markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.",
:bins => "Integer, NTuple{2,Integer}, AbstractVector or Symbol. Default is :auto (the Freedman-Diaconis rule). For histogram-types, defines the approximate number of bins to aim for, or the auto-binning algorithm to use (:sturges, :sqrt, :rice, :scott or :fd). For fine-grained control pass a Vector of break values, e.g. `range(minimum(x), stop = maximum(x), length = 25)`", :bins => "Integer, NTuple{2,Integer}, AbstractVector or Symbol. Default is :auto (the Freedman-Diaconis rule). For histogram-types, defines the approximate number of bins to aim for, or the auto-binning algorithm to use (:sturges, :sqrt, :rice, :scott or :fd). For fine-grained control pass a Vector of break values, e.g. `range(minimum(x), stop = maximum(x), length = 25)`",
:smooth => "Bool. Add a regression line?", :smooth => "Bool. Add a regression line?",
:group => "AbstractVector. Data is split into a separate series, one for each unique value in `group`", :group => "AbstractVector. Data is split into a separate series, one for each unique value in `group`",
:x => "Various. Input data. First Dimension", :x => "Various. Input data. First Dimension",
:y => "Various. Input data. Second Dimension", :y => "Various. Input data. Second Dimension",
:z => "Various. Input data. Third Dimension. May be wrapped by a `Surface` for surface and heatmap types.", :z => "Various. Input data. Third Dimension. May be wrapped by a `Surface` for surface and heatmap types.",
:marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.", :marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.",
:line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).", :line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).",
:fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.", :fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.",
:levels => "Integer, NTuple{2,Integer}, or AbstractVector. Levels or number of levels (or x-levels/y-levels) for a contour type.", :levels => "Integer, NTuple{2,Integer}, or AbstractVector. Levels or number of levels (or x-levels/y-levels) for a contour type.",
:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).", :orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).",
:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)", :bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)",
:bar_width => "nothing or Number. Width of bars in data coordinates. When nothing, chooses based on x (or y when `orientation = :h`).", :bar_width => "nothing or Number. Width of bars in data coordinates. When nothing, chooses based on x (or y when `orientation = :h`).",
:bar_edges => "Bool. Align bars to edges (true), or centers (the default)?", :bar_edges => "Bool. Align bars to edges (true), or centers (the default)?",
:xerror => "AbstractVector or 2-Tuple of Vectors. x (horizontal) error relative to x-value. If 2-tuple of vectors, the first vector corresponds to the left error (and the second to the right)", :xerror => "AbstractVector or 2-Tuple of Vectors. x (horizontal) error relative to x-value. If 2-tuple of vectors, the first vector corresponds to the left error (and the second to the right)",
:yerror => "AbstractVector or 2-Tuple of Vectors. y (vertical) error relative to y-value. If 2-tuple of vectors, the first vector corresponds to the bottom error (and the second to the top)", :yerror => "AbstractVector or 2-Tuple of Vectors. y (vertical) error relative to y-value. If 2-tuple of vectors, the first vector corresponds to the bottom error (and the second to the top)",
:ribbon => "Number or AbstractVector. Creates a fillrange around the data points.", :ribbon => "Number or AbstractVector. Creates a fillrange around the data points.",
:quiver => "AbstractVector or 2-Tuple of vectors. The directional vectors U,V which specify velocity/gradient vectors for a quiver plot.", :quiver => "AbstractVector or 2-Tuple of vectors. The directional vectors U,V which specify velocity/gradient vectors for a quiver plot.",
:arrow => "nothing (no arrows), Bool (if true, default arrows), Arrow object, or arg(s) that could be style or head length/widths. Defines arrowheads that should be displayed at the end of path line segments (just before a NaN and the last non-NaN point). Used in quiverplot, streamplot, or similar.", :arrow => "nothing (no arrows), Bool (if true, default arrows), Arrow object, or arg(s) that could be style or head length/widths. Defines arrowheads that should be displayed at the end of path line segments (just before a NaN and the last non-NaN point). Used in quiverplot, streamplot, or similar.",
:normalize => "Bool or Symbol. Histogram normalization mode. Possible values are: false/:none (no normalization, default), true/:pdf (normalize to a discrete Probability Density Function, where the total area of the bins is 1), :probability (bin heights sum to 1) and :density (the area of each bin, rather than the height, is equal to the counts - useful for uneven bin sizes).", :normalize => "Bool or Symbol. Histogram normalization mode. Possible values are: false/:none (no normalization, default), true/:pdf (normalize to a discrete Probability Density Function, where the total area of the bins is 1), :probability (bin heights sum to 1) and :density (the area of each bin, rather than the height, is equal to the counts - useful for uneven bin sizes).",
:weights => "AbstractVector. Used in histogram types for weighted counts.", :weights => "AbstractVector. Used in histogram types for weighted counts.",
:show_empty_bins => "Bool. Whether empty bins in a 2D histogram are colored as 0 (true), or transparent (the default)", :show_empty_bins => "Bool. Whether empty bins in a 2D histogram are colored as 0 (true), or transparent (the default)",
:contours => "Bool. Add contours to the side-grids of 3D plots? Used in surface/wireframe.", :contours => "Bool. Add contours to the side-grids of 3D plots? Used in surface/wireframe.",
:contour_labels => "Bool. Show labels at the contour lines?", :contour_labels => "Bool. Show labels at the contour lines?",
:match_dimensions => "Bool. For heatmap types... should the first dimension of a matrix (rows) correspond to the first dimension of the plot (x-axis)? The default is false, which matches the behavior of Matplotlib, Plotly, and others. Note: when passing a function for z, the function should still map `(x,y) -> z`.", :match_dimensions => "Bool. For heatmap types... should the first dimension of a matrix (rows) correspond to the first dimension of the plot (x-axis)? The default is false, which matches the behavior of Matplotlib, Plotly, and others. Note: when passing a function for z, the function should still map `(x,y) -> z`.",
:subplot => "Integer (subplot index) or Subplot object. The subplot that this series belongs to.", :subplot => "Integer (subplot index) or Subplot object. The subplot that this series belongs to.",
:series_annotations => "AbstractVector of String or PlotText. These are annotations which are mapped to data points/positions.", :series_annotations => "AbstractVector of String or PlotText. These are annotations which are mapped to data points/positions.",
:primary => "Bool. Does this count as a 'real series'? For example, you could have a path (primary), and a scatter (secondary) as 2 separate series, maybe with different data (see sticks recipe for an example). The secondary series will get the same color, etc as the primary.", :primary => "Bool. Does this count as a 'real series'? For example, you could have a path (primary), and a scatter (secondary) as 2 separate series, maybe with different data (see sticks recipe for an example). The secondary series will get the same color, etc as the primary.",
:hover => "nothing or vector of strings. Text to display when hovering over each data point.", :hover => "nothing or vector of strings. Text to display when hovering over each data point.",
:colorbar_entry => "Bool. Include this series in the color bar? Set to `false` to exclude.", :colorbar_entry => "Bool. Include this series in the color bar? Set to `false` to exclude.",
# plot args # plot args
:plot_title => "String. Title for the whole plot (not the subplots)", :plot_title => "String. Title for the whole plot (not the subplots)",
:plot_titlevspan => "Number in [0,1]. Vertical span of the whole plot title (fraction of the plot height)", :plot_titlevspan => "Number in [0,1]. Vertical span of the whole plot title (fraction of the plot height)",
:background_color => "Color Type. Base color for all backgrounds.", :background_color => "Color Type. Base color for all backgrounds.",
:background_color_outside => "Color Type or `:match` (matches `:background_color`). Color outside the plot area(s)", :background_color_outside => "Color Type or `:match` (matches `:background_color`). Color outside the plot area(s)",
:foreground_color => "Color Type. Base color for all foregrounds.", :foreground_color => "Color Type. Base color for all foregrounds.",
:size => "NTuple{2,Int}. (width_px, height_px) of the whole Plot", :size => "NTuple{2,Int}. (width_px, height_px) of the whole Plot",
:pos => "NTuple{2,Int}. (left_px, top_px) position of the GUI window (note: currently unimplemented)", :pos => "NTuple{2,Int}. (left_px, top_px) position of the GUI window (note: currently unimplemented)",
:window_title => "String. Title of the standalone gui-window.", :window_title => "String. Title of the standalone gui-window.",
:show => "Bool. Should this command open/refresh a GUI/display? This allows displaying in scripts or functions without explicitly calling `display`", :show => "Bool. Should this command open/refresh a GUI/display? This allows displaying in scripts or functions without explicitly calling `display`",
:layout => "Integer (number of subplots), NTuple{2,Integer} (grid dimensions), AbstractLayout (for example `grid(2,2)`), or the return from the `@layout` macro. This builds the layout of subplots.", :layout => "Integer (number of subplots), NTuple{2,Integer} (grid dimensions), AbstractLayout (for example `grid(2,2)`), or the return from the `@layout` macro. This builds the layout of subplots.",
:link => "Symbol. How/whether to link axis limits between subplots. Values: `:none`, `:x` (x axes are linked by columns), `:y` (y axes are linked by rows), `:both` (x and y are linked), `:all` (every subplot is linked together regardless of layout position).", :link => "Symbol. How/whether to link axis limits between subplots. Values: `:none`, `:x` (x axes are linked by columns), `:y` (y axes are linked by rows), `:both` (x and y are linked), `:all` (every subplot is linked together regardless of layout position).",
:overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).", :overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).",
:html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.", :html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.",
:tex_output_standalone => "Bool. When writing tex output, should the source include a preamble for a standalone document class.", :tex_output_standalone => "Bool. When writing tex output, should the source include a preamble for a standalone document class.",
:inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots", :inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots",
:dpi => "Number. Dots Per Inch of output figures", :dpi => "Number. Dots Per Inch of output figures",
:thickness_scaling => "Number. Scale for the thickness of all line elements like lines, borders, axes, grid lines, ... defaults to 1.", :thickness_scaling => "Number. Scale for the thickness of all line elements like lines, borders, axes, grid lines, ... defaults to 1.",
:display_type => "Symbol (`:auto`, `:gui`, or `:inline`). When supported, `display` will either open a GUI window or plot inline.", :display_type => "Symbol (`:auto`, `:gui`, or `:inline`). When supported, `display` will either open a GUI window or plot inline.",
:extra_kwargs => "Either one of (`:plot`, `:subplot`, `:series`) to specify for which element extra keyword args are collected or a KW (Dict{Symbol,Any}) to pass a map of extra keyword args which may be specific to a backend. Default: `:series`.\n Example: `pgfplotsx(); scatter(1:5, extra_kwargs=Dict(:subplot=>Dict(\"axis line shift\" => \"10pt\"))`", :extra_kwargs => "Either one of (`:plot`, `:subplot`, `:series`) to specify for which element extra keyword args are collected or a KW (Dict{Symbol,Any}) to pass a map of extra keyword args which may be specific to a backend. Default: `:series`.\n Example: `pgfplotsx(); scatter(1:5, extra_kwargs=Dict(:subplot=>Dict(\"axis line shift\" => \"10pt\"))`",
:fontfamily => "String or Symbol. Default font family for title, legend entries, tick labels and guides", :fontfamily => "String or Symbol. Default font family for title, legend entries, tick labels and guides",
:warn_on_unsupported => "Bool. Warn on unsupported attributes, series types and marker shapes", :warn_on_unsupported => "Bool. Warn on unsupported attributes, series types and marker shapes",
# subplot args # subplot args
:title => "String. Subplot title.", :title => "String. Subplot title.",
:titlelocation => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`", :titlelocation => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`",
:titlefontfamily => "String or Symbol. Font family of subplot title.", :titlefontfamily => "String or Symbol. Font family of subplot title.",
:titlefontsize => "Integer. Font pointsize of subplot title.", :titlefontsize => "Integer. Font pointsize of subplot title.",
:titlefonthalign => "Symbol. Font horizontal alignment of subplot title: :hcenter, :left, :right or :center", :titlefonthalign => "Symbol. Font horizontal alignment of subplot title: :hcenter, :left, :right or :center",
:titlefontvalign => "Symbol. Font vertical alignment of subplot title: :vcenter, :top, :bottom or :center", :titlefontvalign => "Symbol. Font vertical alignment of subplot title: :vcenter, :top, :bottom or :center",
:titlefontrotation => "Real. Font rotation of subplot title", :titlefontrotation => "Real. Font rotation of subplot title",
:titlefontcolor => "Color Type. Font color of subplot title", :titlefontcolor => "Color Type. Font color of subplot title",
:background_color_subplot => "Color Type or `:match` (matches `:background_color`). Base background color of the subplot.", :background_color_subplot => "Color Type or `:match` (matches `:background_color`). Base background color of the subplot.",
:background_color_legend => "Color Type or `:match` (matches `:background_color_subplot`). Background color of the legend.", :background_color_legend => "Color Type or `:match` (matches `:background_color_subplot`). Background color of the legend.",
:background_color_inside => "Color Type or `:match` (matches `:background_color_subplot`). Background color inside the plot area (under the grid).", :background_color_inside => "Color Type or `:match` (matches `:background_color_subplot`). Background color inside the plot area (under the grid).",
:foreground_color_subplot => "Color Type or `:match` (matches `:foreground_color`). Base foreground color of the subplot.", :foreground_color_subplot => "Color Type or `:match` (matches `:foreground_color`). Base foreground color of the subplot.",
:foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.", :foreground_color_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.", :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.", :color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.",
:legend => "Bool (show the legend?) or (x,y) tuple or Symbol (legend position) or angle or (angle,inout) tuple. Bottom left corner of legend is placed at (x,y). Symbol values: `:none`; `:best`; `:inline`; `:inside`; `:legend`; any valid combination of `:(outer ?)(top/bottom ?)(right/left ?)`, i.e.: `:top`, `:topright`, `:outerleft`, `:outerbottomright` ... (note: only some may be supported in each backend). Legend is positioned at (angle degrees) (so (90,:outer) is roughly equivalent to :outertop), close to the inside of the axes or the outside if inout=:outer.", :legend => "Bool (show the legend?) or (x,y) tuple or Symbol (legend position) or angle or (angle,inout) tuple. Bottom left corner of legend is placed at (x,y). Symbol values: `:none`; `:best`; `:inline`; `:inside`; `:legend`; any valid combination of `:(outer ?)(top/bottom ?)(right/left ?)`, i.e.: `:top`, `:topright`, `:outerleft`, `:outerbottomright` ... (note: only some may be supported in each backend). Legend is positioned at (angle degrees) (so (90,:outer) is roughly equivalent to :outertop), close to the inside of the axes or the outside if inout=:outer.",
:legendfontfamily => "String or Symbol. Font family of legend entries.", :legendfontfamily => "String or Symbol. Font family of legend entries.",
:legendfontsize => "Integer. Font pointsize of legend entries.", :legendfontsize => "Integer. Font pointsize of legend entries.",
:legendfonthalign => "Symbol. Font horizontal alignment of legend entries: :hcenter, :left, :right or :center", :legendfonthalign => "Symbol. Font horizontal alignment of legend entries: :hcenter, :left, :right or :center",
:legendfontvalign => "Symbol. Font vertical alignment of legend entries: :vcenter, :top, :bottom or :center", :legendfontvalign => "Symbol. Font vertical alignment of legend entries: :vcenter, :top, :bottom or :center",
:legendfontrotation => "Real. Font rotation of legend entries", :legendfontrotation => "Real. Font rotation of legend entries",
:legendfontcolor => "Color Type. Font color of legend entries", :legendfontcolor => "Color Type. Font color of legend entries",
:legendtitle => "String. Legend title.", :legendtitle => "String. Legend title.",
:legendtitlefontfamily => "String or Symbol. Font family of the legend title.", :legendtitlefontfamily => "String or Symbol. Font family of the legend title.",
:legendtitlefontsize => "Integer. Font pointsize the legend title.", :legendtitlefontsize => "Integer. Font pointsize the legend title.",
:legendtitlefonthalign => "Symbol. Font horizontal alignment of the legend title: :hcenter, :left, :right or :center", :legendtitlefonthalign => "Symbol. Font horizontal alignment of the legend title: :hcenter, :left, :right or :center",
:legendtitlefontvalign => "Symbol. Font vertical alignment of the legend title: :vcenter, :top, :bottom or :center", :legendtitlefontvalign => "Symbol. Font vertical alignment of the legend title: :vcenter, :top, :bottom or :center",
:legendtitlefontrotation => "Real. Font rotation of the legend title", :legendtitlefontrotation => "Real. Font rotation of the legend title",
:legendtitlefontcolor => "Color Type. Font color of the legend title", :legendtitlefontcolor => "Color Type. Font color of the legend title",
:colorbar => "Bool (show the colorbar?) or Symbol (colorbar position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:legend` (matches legend value) (note: only some may be supported in each backend)", :colorbar => "Bool (show the colorbar?) or Symbol (colorbar position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:legend` (matches legend value) (note: only some may be supported in each backend)",
:clims => "`:auto`, NTuple{2,Number}, or a function that takes series data in and returns NTuple{2,Number}. Fixes the limits of the colorbar.", :clims => "`:auto`, NTuple{2,Number}, or a function that takes series data in and returns NTuple{2,Number}. Fixes the limits of the colorbar.",
:colorbar_fontfamily => "String or Symbol. Font family of colobar entries.", :colorbar_fontfamily => "String or Symbol. Font family of colobar entries.",
:colorbar_ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`", :colorbar_ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`",
:colorbar_tickfontfamily => "String or Symbol. Font family of colorbar tick labels.", :colorbar_tickfontfamily => "String or Symbol. Font family of colorbar tick labels.",
:colorbar_tickfontsize => "Integer. Font pointsize of colorbar tick entries.", :colorbar_tickfontsize => "Integer. Font pointsize of colorbar tick entries.",
:colorbar_tickfontcolor => "Color Type. Font color of colorbar tick entries", :colorbar_tickfontcolor => "Color Type. Font color of colorbar tick entries",
:colorbar_scale => "Symbol. Scale of the colorbar axis: `:none`, `:ln`, `:log2`, `:log10`", :colorbar_scale => "Symbol. Scale of the colorbar axis: `:none`, `:ln`, `:log2`, `:log10`",
:colorbar_formatter => "Function, :scientific, :plain or :auto. A method which converts a number to a string for tick labeling.", :colorbar_formatter => "Function, :scientific, :plain or :auto. A method which converts a number to a string for tick labeling.",
:legendfont => "Font. Font of legend items.", :legendfont => "Font. Font of legend items.",
:legendtitlefont => "Font. Font of the legend title.", :legendtitlefont => "Font. Font of the legend title.",
:annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String, PlotText (created with `text(args...)`), or a tuple of arguments to `text` (e.g., `(\"Label\", 8, :red, :top)`). Add one-off text annotations at the x,y coordinates.", :annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String, PlotText (created with `text(args...)`), or a tuple of arguments to `text` (e.g., `(\"Label\", 8, :red, :top)`). Add one-off text annotations at the x,y coordinates.",
:annotationfontfamily => "String or Symbol. Font family of annotations.", :annotationfontfamily => "String or Symbol. Font family of annotations.",
:annotationfontsize => "Integer. Font pointsize of annotations.", :annotationfontsize => "Integer. Font pointsize of annotations.",
:annotationhalign => "Symbol. horizontal alignment of annotations, :hcenter, :left, :right or :center.", :annotationhalign => "Symbol. horizontal alignment of annotations, :hcenter, :left, :right or :center.",
:annotationvalign => "Symbol. Vertical alignment of annotations, :vcenter, :top, :bottom or :center.", :annotationvalign => "Symbol. Vertical alignment of annotations, :vcenter, :top, :bottom or :center.",
:annotationrotation => "Float. Rotation of annotations in degrees.", :annotationrotation => "Float. Rotation of annotations in degrees.",
:annotationcolor => "Colorant or :match. Color of annotations.", :annotationcolor => "Colorant or :match. Color of annotations.",
:projection => "Symbol or String. '3d' or 'polar'", :projection => "Symbol or String. '3d' or 'polar'",
:aspect_ratio => "Symbol (:equal or :none) or Number. Plot area is resized so that 1 y-unit is the same size as `aspect_ratio` x-units. With `:none`, images inherit aspect ratio of the plot area.", :aspect_ratio => "Symbol (:equal or :none) or Number. Plot area is resized so that 1 y-unit is the same size as `aspect_ratio` x-units. With `:none`, images inherit aspect ratio of the plot area.",
:margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.", :margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.",
:left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.", :left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.",
:top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.", :top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.",
:right_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the right of the subplot.", :right_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the right of the subplot.",
:bottom_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the bottom of the subplot.", :bottom_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the bottom of the subplot.",
:subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.", :subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.",
:colorbar_title => "String. Title of colorbar.", :colorbar_title => "String. Title of colorbar.",
:framestyle => "Symbol. Style of the axes frame. Choose from $(_allFramestyles)", :framestyle => "Symbol. Style of the axes frame. Choose from $(_allFramestyles)",
:camera => "NTuple{2, Real}. Sets the view angle (azimuthal, elevation) for 3D plots", :camera => "NTuple{2, Real}. Sets the view angle (azimuthal, elevation) for 3D plots",
# axis args # axis args
:guide => "String. Axis guide (label).", :guide => "String. Axis guide (label).",
:guide_position => "Symbol. Position of axis guides: :top, :bottom, :left or :right", :guide_position => "Symbol. Position of axis guides: :top, :bottom, :left or :right",
:lims => """ :lims => """
NTuple{2,Number} or Symbol. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example). NTuple{2,Number} or Symbol. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example).
`:round` widens the limit to the nearest round number ie. [0.1,3.6]=>[0.0,4.0] `:round` widens the limit to the nearest round number ie. [0.1,3.6]=>[0.0,4.0]
`:symmetric` sets the limits to be symmetric around zero. `:symmetric` sets the limits to be symmetric around zero.
Set widen=true to widen the specified limits (as occurs when lims are not specified). Set widen=true to widen the specified limits (as occurs when lims are not specified).
""", """,
:ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`", :ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`",
:scale => "Symbol. Scale of the axis: `:none`, `:ln`, `:log2`, `:log10`", :scale => "Symbol. Scale of the axis: `:none`, `:ln`, `:log2`, `:log10`",
:rotation => "Number. Degrees rotation of tick labels.", :rotation => "Number. Degrees rotation of tick labels.",
:flip => "Bool. Should we flip (reverse) the axis?", :flip => "Bool. Should we flip (reverse) the axis?",
:formatter => "Function, :scientific, :plain or :auto. A method which converts a number to a string for tick labeling.", :formatter => "Function, :scientific, :plain or :auto. A method which converts a number to a string for tick labeling.",
:tickfontfamily => "String or Symbol. Font family of tick labels.", :tickfontfamily => "String or Symbol. Font family of tick labels.",
:tickfontsize => "Integer. Font pointsize of tick labels.", :tickfontsize => "Integer. Font pointsize of tick labels.",
:tickfonthalign => "Symbol. Font horizontal alignment of tick labels: :hcenter, :left, :right or :center", :tickfonthalign => "Symbol. Font horizontal alignment of tick labels: :hcenter, :left, :right or :center",
:tickfontvalign => "Symbol. Font vertical alignment of tick labels: :vcenter, :top, :bottom or :center", :tickfontvalign => "Symbol. Font vertical alignment of tick labels: :vcenter, :top, :bottom or :center",
:tickfontrotation => "Real. Font rotation of tick labels", :tickfontrotation => "Real. Font rotation of tick labels",
:tickfontcolor => "Color Type. Font color of tick labels", :tickfontcolor => "Color Type. Font color of tick labels",
:guidefontfamily => "String or Symbol. Font family of axes guides.", :guidefontfamily => "String or Symbol. Font family of axes guides.",
:guidefontsize => "Integer. Font pointsize of axes guides.", :guidefontsize => "Integer. Font pointsize of axes guides.",
:guidefonthalign => "Symbol. Font horizontal alignment of axes guides: :hcenter, :left, :right or :center", :guidefonthalign => "Symbol. Font horizontal alignment of axes guides: :hcenter, :left, :right or :center",
:guidefontvalign => "Symbol. Font vertical alignment of axes guides: :vcenter, :top, :bottom or :center", :guidefontvalign => "Symbol. Font vertical alignment of axes guides: :vcenter, :top, :bottom or :center",
:guidefontrotation => "Real. Font rotation of axes guides", :guidefontrotation => "Real. Font rotation of axes guides",
:guidefontcolor => "Color Type. Font color of axes guides", :guidefontcolor => "Color Type. Font color of axes guides",
:foreground_color_axis => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis ticks.", :foreground_color_axis => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis ticks.",
:foreground_color_border => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of plot area border (spines).", :foreground_color_border => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of plot area border (spines).",
:foreground_color_text => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of tick labels.", :foreground_color_text => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of tick labels.",
:foreground_color_guide => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis guides (axis labels).", :foreground_color_guide => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis guides (axis labels).",
:mirror => "Bool. Switch the side of the tick labels (right or top).", :mirror => "Bool. Switch the side of the tick labels (right or top).",
:grid => "Bool, Symbol, String or `nothing`. Show the grid lines? `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:none`, `:off`", :grid => "Bool, Symbol, String or `nothing`. Show the grid lines? `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:none`, `:off`",
:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.", :foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.",
:gridalpha => "Number in [0,1]. The alpha/opacity override for the grid lines.", :gridalpha => "Number in [0,1]. The alpha/opacity override for the grid lines.",
:gridstyle => "Symbol. Style of the grid lines. Choose from $(_allStyles)", :gridstyle => "Symbol. Style of the grid lines. Choose from $(_allStyles)",
:gridlinewidth => "Number. Width of the grid lines (in pixels)", :gridlinewidth => "Number. Width of the grid lines (in pixels)",
:foreground_color_minor_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of minor grid lines.", :foreground_color_minor_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of minor grid lines.",
:minorgrid => "Bool. Adds minor grid lines and ticks to the plot. Set minorticks to change number of gridlines", :minorgrid => "Bool. Adds minor grid lines and ticks to the plot. Set minorticks to change number of gridlines",
:minorticks => "Integer. Intervals to divide the gap between major ticks into", :minorticks => "Integer. Intervals to divide the gap between major ticks into",
:minorgridalpha => "Number in [0,1]. The alpha/opacity override for the minorgrid lines.", :minorgridalpha => "Number in [0,1]. The alpha/opacity override for the minorgrid lines.",
:minorgridstyle => "Symbol. Style of the minor grid lines. Choose from $(_allStyles)", :minorgridstyle => "Symbol. Style of the minor grid lines. Choose from $(_allStyles)",
:minorgridlinewidth => "Number. Width of the minor grid lines (in pixels)", :minorgridlinewidth => "Number. Width of the minor grid lines (in pixels)",
:tick_direction => "Symbol. Direction of the ticks. `:in`, `:out` or `:none`", :tick_direction => "Symbol. Direction of the ticks. `:in`, `:out` or `:none`",
:showaxis => "Bool, Symbol or String. Show the axis. `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:off`", :showaxis => "Bool, Symbol or String. Show the axis. `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:off`",
:widen => """ :widen => """
Bool or :auto. Widen the axis limits by a small factor to avoid cut-off markers and lines at the borders. Bool or :auto. Widen the axis limits by a small factor to avoid cut-off markers and lines at the borders.
Defaults to `:auto`, which widens unless limits were manually set. Defaults to `:auto`, which widens unless limits were manually set.
""", """,
:draw_arrow => "Bool. Draw arrow at the end of the axis.", :draw_arrow => "Bool. Draw arrow at the end of the axis.",
) )

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
# xaxis(args...; kw...) = Axis(:x, args...; kw...) # xaxis(args...; kw...) = Axis(:x, args...; kw...)
# yaxis(args...; kw...) = Axis(:y, args...; kw...) # yaxis(args...; kw...) = Axis(:y, args...; kw...)
# zaxis(args...; kw...) = Axis(:z, args...; kw...) # zaxis(args...; kw...) = Axis(:z, args...; kw...)
@ -36,42 +35,41 @@ function process_axis_arg!(plotattributes::AKW, arg, letter = "")
T = typeof(arg) T = typeof(arg)
arg = get(_scaleAliases, arg, arg) arg = get(_scaleAliases, arg, arg)
if typeof(arg) <: Font if typeof(arg) <: Font
plotattributes[Symbol(letter,:tickfont)] = arg plotattributes[Symbol(letter, :tickfont)] = arg
plotattributes[Symbol(letter,:guidefont)] = arg plotattributes[Symbol(letter, :guidefont)] = arg
elseif arg in _allScales elseif arg in _allScales
plotattributes[Symbol(letter,:scale)] = arg plotattributes[Symbol(letter, :scale)] = arg
elseif arg in (:flip, :invert, :inverted) elseif arg in (:flip, :invert, :inverted)
plotattributes[Symbol(letter,:flip)] = true plotattributes[Symbol(letter, :flip)] = true
elseif T <: AbstractString elseif T <: AbstractString
plotattributes[Symbol(letter,:guide)] = arg plotattributes[Symbol(letter, :guide)] = arg
# xlims/ylims # xlims/ylims
elseif (T <: Tuple || T <: AVec) && length(arg) == 2 elseif (T <: Tuple || T <: AVec) && length(arg) == 2
sym = typeof(arg[1]) <: Number ? :lims : :ticks sym = typeof(arg[1]) <: Number ? :lims : :ticks
plotattributes[Symbol(letter,sym)] = arg plotattributes[Symbol(letter, sym)] = arg
# xticks/yticks # xticks/yticks
elseif T <: AVec elseif T <: AVec
plotattributes[Symbol(letter,:ticks)] = arg plotattributes[Symbol(letter, :ticks)] = arg
elseif arg === nothing elseif arg === nothing
plotattributes[Symbol(letter,:ticks)] = [] plotattributes[Symbol(letter, :ticks)] = []
elseif T <: Bool || arg in _allShowaxisArgs elseif T <: Bool || arg in _allShowaxisArgs
plotattributes[Symbol(letter,:showaxis)] = showaxis(arg, letter) plotattributes[Symbol(letter, :showaxis)] = showaxis(arg, letter)
elseif typeof(arg) <: Number elseif typeof(arg) <: Number
plotattributes[Symbol(letter,:rotation)] = arg plotattributes[Symbol(letter, :rotation)] = arg
elseif typeof(arg) <: Function elseif typeof(arg) <: Function
plotattributes[Symbol(letter,:formatter)] = arg plotattributes[Symbol(letter, :formatter)] = arg
elseif !handleColors!(plotattributes, arg, Symbol(letter, :foreground_color_axis)) elseif !handleColors!(plotattributes, arg, Symbol(letter, :foreground_color_axis))
@warn("Skipped $(letter)axis arg $arg") @warn("Skipped $(letter)axis arg $arg")
end end
end end
@ -87,15 +85,15 @@ function attr!(axis::Axis, args...; kw...)
RecipesPipeline.preprocess_attributes!(KW(kw)) RecipesPipeline.preprocess_attributes!(KW(kw))
# then override for any keywords... only those keywords that already exists in plotattributes # then override for any keywords... only those keywords that already exists in plotattributes
for (k,v) in kw for (k, v) in kw
if haskey(plotattributes, k) if haskey(plotattributes, k)
if k == :discrete_values if k == :discrete_values
# add these discrete values to the axis # add these discrete values to the axis
for vi in v for vi in v
discrete_value!(axis, vi) discrete_value!(axis, vi)
end end
#could perhaps use TimeType here, as Date and DateTime are both subtypes of TimeType #could perhaps use TimeType here, as Date and DateTime are both subtypes of TimeType
# or could perhaps check if dateformatter or datetimeformatter is in use # or could perhaps check if dateformatter or datetimeformatter is in use
elseif k == :lims && isa(v, Tuple{Date,Date}) elseif k == :lims && isa(v, Tuple{Date,Date})
plotattributes[k] = (v[1].instant.periods.value, v[2].instant.periods.value) plotattributes[k] = (v[1].instant.periods.value, v[2].instant.periods.value)
elseif k == :lims && isa(v, Tuple{DateTime,DateTime}) elseif k == :lims && isa(v, Tuple{DateTime,DateTime})
@ -122,11 +120,8 @@ Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.plotattributes, v,
Base.haskey(axis::Axis, k::Symbol) = haskey(axis.plotattributes, k) Base.haskey(axis::Axis, k::Symbol) = haskey(axis.plotattributes, k)
ignorenan_extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax)) ignorenan_extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax))
const _label_func = Dict{Symbol,Function}( const _label_func =
:log10 => x -> "10^$x", Dict{Symbol,Function}(:log10 => x -> "10^$x", :log2 => x -> "2^$x", :ln => x -> "e^$x")
:log2 => x -> "2^$x",
:ln => x -> "e^$x",
)
labelfunc(scale::Symbol, backend::AbstractBackend) = get(_label_func, scale, string) labelfunc(scale::Symbol, backend::AbstractBackend) = get(_label_func, scale, string)
const _label_func_tex = Dict{Symbol,Function}( const _label_func_tex = Dict{Symbol,Function}(
@ -136,7 +131,6 @@ const _label_func_tex = Dict{Symbol,Function}(
) )
labelfunc_tex(scale::Symbol) = get(_label_func_tex, scale, convert_sci_unicode) labelfunc_tex(scale::Symbol) = get(_label_func_tex, scale, convert_sci_unicode)
function optimal_ticks_and_labels(ticks, alims, scale, formatter) function optimal_ticks_and_labels(ticks, alims, scale, formatter)
amin, amax = alims amin, amax = alims
@ -158,8 +152,8 @@ function optimal_ticks_and_labels(ticks, alims, scale, formatter)
# are converted to 'DateTime integers' (actually floats) before # are converted to 'DateTime integers' (actually floats) before
# being passed to optimize_datetime_ticks. # being passed to optimize_datetime_ticks.
# (convert(Int, convert(DateTime, convert(Date, i))) == 87600000*i) # (convert(Int, convert(DateTime, convert(Date, i))) == 87600000*i)
ticks, labels = optimize_datetime_ticks(864e5 * amin, 864e5 * amax; ticks, labels =
k_min = 2, k_max = 4) optimize_datetime_ticks(864e5 * amin, 864e5 * amax; k_min = 2, k_max = 4)
# Now the ticks are converted back to floats corresponding to Dates. # Now the ticks are converted back to floats corresponding to Dates.
return ticks / 864e5, labels return ticks / 864e5, labels
elseif formatter == RecipesPipeline.datetimeformatter elseif formatter == RecipesPipeline.datetimeformatter
@ -198,7 +192,10 @@ function optimal_ticks_and_labels(ticks, alims, scale, formatter)
if formatter in (:auto, :plain, :scientific, :engineering) if formatter in (:auto, :plain, :scientific, :engineering)
map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, formatter)) map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, formatter))
elseif formatter == :latex elseif formatter == :latex
map(x -> string("\$", replace(convert_sci_unicode(x), '×' => "\\times"), "\$"), Showoff.showoff(unscaled_ticks, :auto)) map(
x -> string("\$", replace(convert_sci_unicode(x), '×' => "\\times"), "\$"),
Showoff.showoff(unscaled_ticks, :auto),
)
else else
# there was an override for the formatter... use that on the unscaled ticks # there was an override for the formatter... use that on the unscaled ticks
map(formatter, unscaled_ticks) map(formatter, unscaled_ticks)
@ -223,16 +220,22 @@ function get_ticks(sp::Subplot, axis::Axis; update = true)
if update || !haskey(axis.plotattributes, :optimized_ticks) if update || !haskey(axis.plotattributes, :optimized_ticks)
dvals = axis[:discrete_values] dvals = axis[:discrete_values]
ticks = _transform_ticks(axis[:ticks]) ticks = _transform_ticks(axis[:ticks])
axis.plotattributes[:optimized_ticks] = if ticks isa Symbol && ticks !== :none && axis.plotattributes[:optimized_ticks] =
ispolar(sp) && axis[:letter] === :x && !isempty(dvals) if (
collect(0:pi/4:7pi/4), string.(0:45:315) ticks isa Symbol &&
else ticks !== :none &&
cvals = axis[:continuous_values] ispolar(sp) &&
alims = axis_limits(sp, axis[:letter]) axis[:letter] === :x &&
scale = axis[:scale] !isempty(dvals)
formatter = axis[:formatter] )
get_ticks(ticks, cvals, dvals, alims, scale, formatter) collect(0:(pi / 4):(7pi / 4)), string.(0:45:315)
end else
cvals = axis[:continuous_values]
alims = axis_limits(sp, axis[:letter])
scale = axis[:scale]
formatter = axis[:formatter]
get_ticks(ticks, cvals, dvals, alims, scale, formatter)
end
end end
return axis.plotattributes[:optimized_ticks] return axis.plotattributes[:optimized_ticks]
end end
@ -297,7 +300,7 @@ end
get_ticks(sp::Subplot, s::Symbol) = get_ticks(sp, sp[Symbol(s, :axis)]) get_ticks(sp::Subplot, s::Symbol) = get_ticks(sp, sp[Symbol(s, :axis)])
get_ticks(p::Plot, s::Symbol) = [get_ticks(sp, s) for sp in p.subplots] get_ticks(p::Plot, s::Symbol) = [get_ticks(sp, s) for sp in p.subplots]
function get_ticks(ticks::Symbol, cvals::T, dvals, args...) where T function get_ticks(ticks::Symbol, cvals::T, dvals, args...) where {T}
if ticks === :none if ticks === :none
return T[], String[] return T[], String[]
elseif !isempty(dvals) elseif !isempty(dvals)
@ -316,21 +319,21 @@ end
get_ticks(ticks::AVec, cvals, dvals, args...) = optimal_ticks_and_labels(ticks, args...) get_ticks(ticks::AVec, cvals, dvals, args...) = optimal_ticks_and_labels(ticks, args...)
function get_ticks(ticks::Int, dvals, cvals, args...) function get_ticks(ticks::Int, dvals, cvals, args...)
if !isempty(dvals) if !isempty(dvals)
rng = round.(Int, range(1, stop=length(dvals), length=ticks)) rng = round.(Int, range(1, stop = length(dvals), length = ticks))
cvals[rng], string.(dvals[rng]) cvals[rng], string.(dvals[rng])
else else
optimal_ticks_and_labels(ticks, args...) optimal_ticks_and_labels(ticks, args...)
end end
end end
get_ticks(ticks::NTuple{2, Any}, args...) = ticks get_ticks(ticks::NTuple{2,Any}, args...) = ticks
get_ticks(::Nothing, cvals::T, args...) where T = T[], String[] get_ticks(::Nothing, cvals::T, args...) where {T} = T[], String[]
get_ticks(ticks::Bool, args...) = get_ticks(ticks::Bool, args...) =
ticks ? get_ticks(:auto, args...) : get_ticks(nothing, args...) ticks ? get_ticks(:auto, args...) : get_ticks(nothing, args...)
get_ticks(::T, args...) where T = error("Unknown ticks type in get_ticks: $T") get_ticks(::T, args...) where {T} = error("Unknown ticks type in get_ticks: $T")
_transform_ticks(ticks) = ticks _transform_ticks(ticks) = ticks
_transform_ticks(ticks::AbstractArray{T}) where T <: Dates.TimeType = Dates.value.(ticks) _transform_ticks(ticks::AbstractArray{T}) where {T<:Dates.TimeType} = Dates.value.(ticks)
_transform_ticks(ticks::NTuple{2, Any}) = (_transform_ticks(ticks[1]), ticks[2]) _transform_ticks(ticks::NTuple{2,Any}) = (_transform_ticks(ticks[1]), ticks[2])
function get_minor_ticks(sp, axis, ticks) function get_minor_ticks(sp, axis, ticks)
axis[:minorticks] (:none, nothing, false) && !axis[:minorgrid] && return nothing axis[:minorticks] (:none, nothing, false) && !axis[:minorgrid] && return nothing
@ -350,30 +353,36 @@ function get_minor_ticks(sp, axis, ticks)
sub = 1 # unused sub = 1 # unused
ratio = length(ticks) > 2 ? (ticks[3] - ticks[2]) / (ticks[2] - ticks[1]) : 1 ratio = length(ticks) > 2 ? (ticks[3] - ticks[2]) / (ticks[2] - ticks[1]) : 1
first_step = ticks[2] - ticks[1] first_step = ticks[2] - ticks[1]
last_step = ticks[end] - ticks[end-1] last_step = ticks[end] - ticks[end - 1]
ticks = [ticks[1] - first_step / ratio; ticks; ticks[end] + last_step * ratio] ticks = [ticks[1] - first_step / ratio; ticks; ticks[end] + last_step * ratio]
end end
# default to 9 intervals between major ticks for log10 scale and 5 intervals otherwise # default to 9 intervals between major ticks for log10 scale and 5 intervals otherwise
n_default = (scale == :log10) ? 9 : 5 n_default = (scale == :log10) ? 9 : 5
n = typeof(axis[:minorticks]) <: Integer && axis[:minorticks] > 1 ? axis[:minorticks] : n_default n =
typeof(axis[:minorticks]) <: Integer && axis[:minorticks] > 1 ? axis[:minorticks] :
n_default
minorticks = typeof(ticks[1])[] minorticks = typeof(ticks[1])[]
for (i, hi) enumerate(ticks[2:end]) for (i, hi) in enumerate(ticks[2:end])
lo = ticks[i] lo = ticks[i]
if isfinite(lo) && isfinite(hi) && hi > lo if isfinite(lo) && isfinite(hi) && hi > lo
if log_scaled if log_scaled
for e 1:sub for e in 1:sub
lo_ = lo * base^(e - 1) lo_ = lo * base^(e - 1)
hi_ = lo_ * base hi_ = lo_ * base
step = (hi_ - lo_) / n step = (hi_ - lo_) / n
append!(minorticks, collect( append!(
lo_ + (e > 1 ? 0 : step) : step : hi_ - (e < sub ? 0 : step / 2) minorticks,
)) collect(
(lo_ + (e > 1 ? 0 : step)):step:(hi_ - (e < sub ? 0 :
step / 2)),
),
)
end end
else else
step = (hi - lo) / n step = (hi - lo) / n
append!(minorticks, collect(lo + step : step : hi - step / 2)) append!(minorticks, collect((lo + step):step:(hi - step / 2)))
end end
end end
end end
@ -382,17 +391,15 @@ end
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
function reset_extrema!(sp::Subplot) function reset_extrema!(sp::Subplot)
for asym in (:x,:y,:z) for asym in (:x, :y, :z)
sp[Symbol(asym,:axis)][:extrema] = Extrema() sp[Symbol(asym, :axis)][:extrema] = Extrema()
end end
for series in sp.series_list for series in sp.series_list
expand_extrema!(sp, series.plotattributes) expand_extrema!(sp, series.plotattributes)
end end
end end
function expand_extrema!(ex::Extrema, v::Number) function expand_extrema!(ex::Extrema, v::Number)
ex.emin = isfinite(v) ? min(v, ex.emin) : ex.emin ex.emin = isfinite(v) ? min(v, ex.emin) : ex.emin
ex.emax = isfinite(v) ? max(v, ex.emax) : ex.emax ex.emax = isfinite(v) ? max(v, ex.emax) : ex.emax
@ -407,14 +414,13 @@ end
expand_extrema!(axis::Axis, ::Nothing) = axis[:extrema] expand_extrema!(axis::Axis, ::Nothing) = axis[:extrema]
expand_extrema!(axis::Axis, ::Bool) = axis[:extrema] expand_extrema!(axis::Axis, ::Bool) = axis[:extrema]
function expand_extrema!(axis::Axis, v::Tuple{MIN,MAX}) where {MIN<:Number,MAX<:Number} function expand_extrema!(axis::Axis, v::Tuple{MIN,MAX}) where {MIN<:Number,MAX<:Number}
ex = axis[:extrema] ex = axis[:extrema]
ex.emin = isfinite(v[1]) ? min(v[1], ex.emin) : ex.emin ex.emin = isfinite(v[1]) ? min(v[1], ex.emin) : ex.emin
ex.emax = isfinite(v[2]) ? max(v[2], ex.emax) : ex.emax ex.emax = isfinite(v[2]) ? max(v[2], ex.emax) : ex.emax
ex ex
end end
function expand_extrema!(axis::Axis, v::AVec{N}) where N<:Number function expand_extrema!(axis::Axis, v::AVec{N}) where {N<:Number}
ex = axis[:extrema] ex = axis[:extrema]
for vi in v for vi in v
expand_extrema!(ex, vi) expand_extrema!(ex, vi)
@ -422,7 +428,6 @@ function expand_extrema!(axis::Axis, v::AVec{N}) where N<:Number
ex ex
end end
function expand_extrema!(sp::Subplot, plotattributes::AKW) function expand_extrema!(sp::Subplot, plotattributes::AKW)
vert = isvertical(plotattributes) vert = isvertical(plotattributes)
@ -433,7 +438,12 @@ function expand_extrema!(sp::Subplot, plotattributes::AKW)
else else
letter == :x ? :y : letter == :y ? :x : :z letter == :x ? :y : letter == :y ? :x : :z
end] end]
if letter != :z && plotattributes[:seriestype] == :straightline && any(series[:seriestype] != :straightline for series in series_list(sp)) && data[1] != data[2] if (
letter != :z &&
plotattributes[:seriestype] == :straightline &&
any(series[:seriestype] != :straightline for series in series_list(sp)) &&
data[1] != data[2]
)
data = [NaN] data = [NaN]
end end
axis = sp[Symbol(letter, "axis")] axis = sp[Symbol(letter, "axis")]
@ -442,7 +452,8 @@ function expand_extrema!(sp::Subplot, plotattributes::AKW)
expand_extrema!(sp[:xaxis], data.x_extents) expand_extrema!(sp[:xaxis], data.x_extents)
expand_extrema!(sp[:yaxis], data.y_extents) expand_extrema!(sp[:yaxis], data.y_extents)
expand_extrema!(sp[:zaxis], data.z_extents) expand_extrema!(sp[:zaxis], data.z_extents)
elseif eltype(data) <: Number || (isa(data, Surface) && all(di -> isa(di, Number), data.surf)) elseif eltype(data) <: Number ||
(isa(data, Surface) && all(di -> isa(di, Number), data.surf))
if !(eltype(data) <: Number) if !(eltype(data) <: Number)
# huh... must have been a mis-typed surface? lets swap it out # huh... must have been a mis-typed surface? lets swap it out
data = plotattributes[letter] = Surface(Matrix{Float64}(data.surf)) data = plotattributes[letter] = Surface(Matrix{Float64}(data.surf))
@ -452,7 +463,8 @@ function expand_extrema!(sp::Subplot, plotattributes::AKW)
# TODO: need more here... gotta track the discrete reference value # TODO: need more here... gotta track the discrete reference value
# as well as any coord offset (think of boxplot shape coords... they all # as well as any coord offset (think of boxplot shape coords... they all
# correspond to the same x-value) # correspond to the same x-value)
plotattributes[letter], plotattributes[Symbol(letter,"_discrete_indices")] = discrete_value!(axis, data) plotattributes[letter], plotattributes[Symbol(letter, "_discrete_indices")] =
discrete_value!(axis, data)
expand_extrema!(axis, plotattributes[letter]) expand_extrema!(axis, plotattributes[letter])
end end
end end
@ -486,7 +498,9 @@ function expand_extrema!(sp::Subplot, plotattributes::AKW)
bw = plotattributes[:bar_width] bw = plotattributes[:bar_width]
if bw === nothing if bw === nothing
bw = plotattributes[:bar_width] = _bar_width * ignorenan_minimum(filter(x->x>0,diff(sort(data)))) bw =
plotattributes[:bar_width] =
_bar_width * ignorenan_minimum(filter(x -> x > 0, diff(sort(data))))
end end
axis = sp.attr[Symbol(dsym, :axis)] axis = sp.attr[Symbol(dsym, :axis)]
expand_extrema!(axis, ignorenan_maximum(data) + 0.5maximum(bw)) expand_extrema!(axis, ignorenan_maximum(data) + 0.5maximum(bw))
@ -512,7 +526,7 @@ end
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# push the limits out slightly # push the limits out slightly
function widen(lmin, lmax, scale=:identity) function widen(lmin, lmax, scale = :identity)
f, invf = RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale) f, invf = RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale)
span = f(lmax) - f(lmin) span = f(lmax) - f(lmin)
# eps = NaNMath.max(1e-16, min(1e-2span, 1e-10)) # eps = NaNMath.max(1e-16, min(1e-2span, 1e-10))
@ -521,7 +535,28 @@ function widen(lmin, lmax, scale=:identity)
end end
# figure out if widening is a good idea. # figure out if widening is a good idea.
const _widen_seriestypes = (:line, :path, :steppre, :stepmid, :steppost, :sticks, :scatter, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :bar, :shape, :path3d, :scatter3d) const _widen_seriestypes = (
:line,
:path,
:steppre,
:stepmid,
:steppost,
:sticks,
:scatter,
:barbins,
:barhist,
:histogram,
:scatterbins,
:scatterhist,
:stepbins,
:stephist,
:bins2d,
:histogram2d,
:bar,
:shape,
:path3d,
:scatter3d,
)
function default_should_widen(axis::Axis) function default_should_widen(axis::Axis)
if axis[:widen] isa Bool if axis[:widen] isa Bool
@ -540,7 +575,7 @@ function default_should_widen(axis::Axis)
end end
function round_limits(amin, amax, scale) function round_limits(amin, amax, scale)
base = get(_logScaleBases, scale, 10.) base = get(_logScaleBases, scale, 10.0)
factor = base^(1 - round(log(base, amax - amin))) factor = base^(1 - round(log(base, amax - amin)))
amin = floor(amin * factor) / factor amin = floor(amin * factor) / factor
amax = ceil(amax * factor) / factor amax = ceil(amax * factor) / factor
@ -548,7 +583,12 @@ function round_limits(amin, amax, scale)
end end
# using the axis extrema and limit overrides, return the min/max value for this axis # using the axis extrema and limit overrides, return the min/max value for this axis
function axis_limits(sp, letter, should_widen = default_should_widen(sp[Symbol(letter, :axis)]), consider_aspect = true) function axis_limits(
sp,
letter,
should_widen = default_should_widen(sp[Symbol(letter, :axis)]),
consider_aspect = true,
)
axis = sp[Symbol(letter, :axis)] axis = sp[Symbol(letter, :axis)]
ex = axis[:extrema] ex = axis[:extrema]
amin, amax = ex.emin, ex.emax amin, amax = ex.emin, ex.emax
@ -594,7 +634,9 @@ function axis_limits(sp, letter, should_widen = default_should_widen(sp[Symbol(l
end end
if ( if (
!has_user_lims && consider_aspect && letter in (:x, :y) && !has_user_lims &&
consider_aspect &&
letter in (:x, :y) &&
!(sp[:aspect_ratio] in (:none, :auto) || RecipesPipeline.is3d(:sp)) !(sp[:aspect_ratio] in (:none, :auto) || RecipesPipeline.is3d(:sp))
) )
aspect_ratio = isa(sp[:aspect_ratio], Number) ? sp[:aspect_ratio] : 1 aspect_ratio = isa(sp[:aspect_ratio], Number) ? sp[:aspect_ratio] : 1
@ -664,11 +706,11 @@ end
# add the discrete value for each item. return the continuous values and the indices # add the discrete value for each item. return the continuous values and the indices
function discrete_value!(axis::Axis, v::AMat) function discrete_value!(axis::Axis, v::AMat)
n,m = axes(v) n, m = axes(v)
cmat = zeros(axes(v)) cmat = zeros(axes(v))
discrete_indices = similar(Array{Int}, axes(v)) discrete_indices = similar(Array{Int}, axes(v))
for i in n, j in m for i in n, j in m
cmat[i,j], discrete_indices[i,j] = discrete_value!(axis, v[i,j]) cmat[i, j], discrete_indices[i, j] = discrete_value!(axis, v[i, j])
end end
cmat, discrete_indices cmat, discrete_indices
end end
@ -711,7 +753,11 @@ function axis_drawing_info(sp, letter)
if sp[:framestyle] != :grid if sp[:framestyle] != :grid
push!(segments, reverse_if((amin, oa1), isy), reverse_if((amax, oa1), isy)) push!(segments, reverse_if((amin, oa1), isy), reverse_if((amax, oa1), isy))
# don't show the 0 tick label for the origin framestyle # don't show the 0 tick label for the origin framestyle
if sp[:framestyle] == :origin && !(ticks in (:none, nothing, false)) && length(ticks) > 1 if (
sp[:framestyle] == :origin &&
!(ticks in (:none, nothing, false)) &&
length(ticks) > 1
)
i = findfirst(==(0), ticks[1]) i = findfirst(==(0), ticks[1])
if i !== nothing if i !== nothing
deleteat!(ticks[1], i) deleteat!(ticks[1], i)
@ -765,11 +811,23 @@ function axis_drawing_info(sp, letter)
ax_length = letter === :x ? height(sp.plotarea).value : width(sp.plotarea).value ax_length = letter === :x ? height(sp.plotarea).value : width(sp.plotarea).value
# add major grid segments # add major grid segments
add_major_or_minor_segments(ticks[1], ax[:grid], grid_segments, 1.2 / ax_length, ax[:tick_direction] !== :none) add_major_or_minor_segments(
ticks[1],
ax[:grid],
grid_segments,
1.2 / ax_length,
ax[:tick_direction] !== :none,
)
# add minor grid segments # add minor grid segments
if ax[:minorticks] (:none, nothing, false) || ax[:minorgrid] if ax[:minorticks] (:none, nothing, false) || ax[:minorgrid]
add_major_or_minor_segments(minor_ticks, ax[:minorgrid], minorgrid_segments, 0.6 / ax_length, true) add_major_or_minor_segments(
minor_ticks,
ax[:minorgrid],
minorgrid_segments,
0.6 / ax_length,
true,
)
end end
end end
end end
@ -780,7 +838,7 @@ function axis_drawing_info(sp, letter)
tick_segments = tick_segments, tick_segments = tick_segments,
grid_segments = grid_segments, grid_segments = grid_segments,
minorgrid_segments = minorgrid_segments, minorgrid_segments = minorgrid_segments,
border_segments = border_segments border_segments = border_segments,
) )
end end
@ -816,8 +874,7 @@ function axis_drawing_info_3d(sp, letter)
minorgrid_segments = Segments(3) minorgrid_segments = Segments(3)
border_segments = Segments(3) border_segments = Segments(3)
if sp[:framestyle] != :none # && letter === :x
if sp[:framestyle] != :none# && letter === :x
na0, na1 = if sp[:framestyle] in (:origin, :zerolines) na0, na1 = if sp[:framestyle] in (:origin, :zerolines)
0, 0 0, 0
else else
@ -837,7 +894,11 @@ function axis_drawing_info_3d(sp, letter)
sort_3d_axes(amax, na0, fa0, letter), sort_3d_axes(amax, na0, fa0, letter),
) )
# don't show the 0 tick label for the origin framestyle # don't show the 0 tick label for the origin framestyle
if sp[:framestyle] == :origin && !(ticks in (:none, nothing, false)) && length(ticks) > 1 if (
sp[:framestyle] == :origin &&
!(ticks in (:none, nothing, false)) &&
length(ticks) > 1
)
i0 = findfirst(==(0), ticks[1]) i0 = findfirst(==(0), ticks[1])
if i0 !== nothing if i0 !== nothing
deleteat!(ticks[1], i0) deleteat!(ticks[1], i0)
@ -857,7 +918,8 @@ function axis_drawing_info_3d(sp, letter)
if ax[:ticks] (:none, nothing, false) if ax[:ticks] (:none, nothing, false)
f = RecipesPipeline.scale_func(nax[:scale]) f = RecipesPipeline.scale_func(nax[:scale])
invf = RecipesPipeline.inverse_scale_func(nax[:scale]) invf = RecipesPipeline.inverse_scale_func(nax[:scale])
ga0, ga1 = sp[:framestyle] in (:origin, :zerolines) ? (namin, namax) : (na0, na1) ga0, ga1 =
sp[:framestyle] in (:origin, :zerolines) ? (namin, namax) : (na0, na1)
add_major_or_minor_segments(ticks, grid, segments, factor, cond) = begin add_major_or_minor_segments(ticks, grid, segments, factor, cond) = begin
ticks === nothing && return ticks === nothing && return
@ -898,11 +960,23 @@ function axis_drawing_info_3d(sp, letter)
end end
# add major grid segments # add major grid segments
add_major_or_minor_segments(ticks[1], ax[:grid], grid_segments, 0.012, ax[:tick_direction] !== :none) add_major_or_minor_segments(
ticks[1],
ax[:grid],
grid_segments,
0.012,
ax[:tick_direction] !== :none,
)
# add minor grid segments # add minor grid segments
if ax[:minorticks] (:none, nothing, false) || ax[:minorgrid] if ax[:minorticks] (:none, nothing, false) || ax[:minorgrid]
add_major_or_minor_segments(minor_ticks, ax[:minorgrid], minorgrid_segments, 0.006, true) add_major_or_minor_segments(
minor_ticks,
ax[:minorgrid],
minorgrid_segments,
0.006,
true,
)
end end
end end
end end
@ -913,7 +987,7 @@ function axis_drawing_info_3d(sp, letter)
tick_segments = tick_segments, tick_segments = tick_segments,
grid_segments = grid_segments, grid_segments = grid_segments,
minorgrid_segments = minorgrid_segments, minorgrid_segments = minorgrid_segments,
border_segments = border_segments border_segments = border_segments,
) )
end end

View File

@ -1,12 +1,12 @@
struct NoBackend <: AbstractBackend end struct NoBackend <: AbstractBackend end
const _backendType = Dict{Symbol, DataType}(:none => NoBackend) const _backendType = Dict{Symbol,DataType}(:none => NoBackend)
const _backendSymbol = Dict{DataType, Symbol}(NoBackend => :none) const _backendSymbol = Dict{DataType,Symbol}(NoBackend => :none)
const _backends = Symbol[] const _backends = Symbol[]
const _initialized_backends = Set{Symbol}() const _initialized_backends = Set{Symbol}()
const _default_backends = (:none, :gr, :plotly) const _default_backends = (:none, :gr, :plotly)
const _backend_packages = Dict{Symbol, Symbol}() const _backend_packages = Dict{Symbol,Symbol}()
"Returns a list of supported backends" "Returns a list of supported backends"
backends() = _backends backends() = _backends
@ -66,10 +66,11 @@ function text_size(lablen::Int, sz::Number, rot::Number = 0)
# now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles # now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles
height = abs(sind(rot)) * width + abs(cosd(rot)) * ptsz height = abs(sind(rot)) * width + abs(cosd(rot)) * ptsz
width = abs(sind(rot+90)) * width + abs(cosd(rot+90)) * ptsz width = abs(sind(rot + 90)) * width + abs(cosd(rot + 90)) * ptsz
width, height width, height
end end
text_size(lab::AbstractString, sz::Number, rot::Number = 0) = text_size(length(lab), sz, rot) text_size(lab::AbstractString, sz::Number, rot::Number = 0) =
text_size(length(lab), sz, rot)
text_size(lab::PlotText, sz::Number, rot::Number = 0) = text_size(length(lab.str), sz, rot) text_size(lab::PlotText, sz::Number, rot::Number = 0) = text_size(length(lab.str), sz, rot)
# account for the size/length/rotation of tick labels # account for the size/length/rotation of tick labels
@ -104,8 +105,8 @@ end
# to fit ticks, tick labels, guides, colorbars, etc. # to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot) function _update_min_padding!(sp::Subplot)
# TODO: something different when `RecipesPipeline.is3d(sp) == true` # TODO: something different when `RecipesPipeline.is3d(sp) == true`
leftpad = tick_padding(sp, sp[:yaxis]) + sp[:left_margin] + guide_padding(sp[:yaxis]) leftpad = tick_padding(sp, sp[:yaxis]) + sp[:left_margin] + guide_padding(sp[:yaxis])
toppad = sp[:top_margin] + title_padding(sp) toppad = sp[:top_margin] + title_padding(sp)
rightpad = sp[:right_margin] rightpad = sp[:right_margin]
bottompad = tick_padding(sp, sp[:xaxis]) + sp[:bottom_margin] + guide_padding(sp[:xaxis]) bottompad = tick_padding(sp, sp[:xaxis]) + sp[:bottom_margin] + guide_padding(sp[:xaxis])
@ -125,10 +126,9 @@ _update_plot_object(plt::Plot) = nothing
# --------------------------------------------------------- # ---------------------------------------------------------
mutable struct CurrentBackend mutable struct CurrentBackend
sym::Symbol sym::Symbol
pkg::AbstractBackend pkg::AbstractBackend
end end
CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym)) CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym))
@ -143,8 +143,10 @@ function _pick_default_backend()
if sym in _backends if sym in _backends
backend(sym) backend(sym)
else else
@warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not a valid backend package. Choose from:\n\t" * @warn(
join(sort(_backends), "\n\t")) "You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not a valid backend package. Choose from:\n\t" *
join(sort(_backends), "\n\t")
)
_fallback_default_backend() _fallback_default_backend()
end end
else else
@ -152,7 +154,6 @@ function _pick_default_backend()
end end
end end
# --------------------------------------------------------- # ---------------------------------------------------------
""" """
@ -189,7 +190,8 @@ function backend(sym::Symbol)
end end
end end
const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse, :glvisualize, :pgfplots] const _deprecated_backends =
[:qwt, :winston, :bokeh, :gadfly, :immerse, :glvisualize, :pgfplots]
function warn_on_deprecated_backend(bsym::Symbol) function warn_on_deprecated_backend(bsym::Symbol)
if bsym in _deprecated_backends if bsym in _deprecated_backends
@ -201,23 +203,29 @@ function warn_on_deprecated_backend(bsym::Symbol)
end end
end end
# --------------------------------------------------------- # ---------------------------------------------------------
# these are args which every backend supports because they're not used in the backend code # these are args which every backend supports because they're not used in the backend code
const _base_supported_args = [ const _base_supported_args = [
:color_palette, :color_palette,
:background_color, :background_color_subplot, :background_color,
:foreground_color, :foreground_color_subplot, :background_color_subplot,
:foreground_color,
:foreground_color_subplot,
:group, :group,
:seriestype, :seriestype,
:seriescolor, :seriesalpha, :seriescolor,
:seriesalpha,
:smooth, :smooth,
:xerror, :yerror, :zerror, :xerror,
:yerror,
:zerror,
:subplot, :subplot,
:x, :y, :z, :x,
:show, :size, :y,
:z,
:show,
:size,
:margin, :margin,
:left_margin, :left_margin,
:right_margin, :right_margin,
@ -231,23 +239,21 @@ const _base_supported_args = [
:subplot_index, :subplot_index,
:discrete_values, :discrete_values,
:projection, :projection,
:show_empty_bins :show_empty_bins,
] ]
function merge_with_base_supported(v::AVec) function merge_with_base_supported(v::AVec)
v = vcat(v, _base_supported_args) v = vcat(v, _base_supported_args)
for vi in v for vi in v
if haskey(_axis_defaults, vi) if haskey(_axis_defaults, vi)
for letter in (:x,:y,:z) for letter in (:x, :y, :z)
push!(v, Symbol(letter,vi)) push!(v, Symbol(letter, vi))
end end
end end
end end
Set(v) Set(v)
end end
@init_backend PyPlot @init_backend PyPlot
@init_backend UnicodePlots @init_backend UnicodePlots
@init_backend Plotly @init_backend Plotly
@ -286,7 +292,6 @@ end
# is_subplot_supported(::AbstractBackend) = false # is_subplot_supported(::AbstractBackend) = false
# is_subplot_supported() = is_subplot_supported(backend()) # is_subplot_supported() = is_subplot_supported(backend())
################################################################################ ################################################################################
# initialize the backends # initialize the backends
@ -305,39 +310,91 @@ _initialize_backend(pkg::GRBackend) = nothing
const _gr_attr = merge_with_base_supported([ const _gr_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_inside, :background_color_outside, :background_color_legend,
:foreground_color_legend, :foreground_color_grid, :foreground_color_axis, :background_color_inside,
:foreground_color_text, :foreground_color_border, :background_color_outside,
:foreground_color_legend,
:foreground_color_grid,
:foreground_color_axis,
:foreground_color_text,
:foreground_color_border,
:label, :label,
:seriescolor, :seriesalpha, :seriescolor,
:linecolor, :linestyle, :linewidth, :linealpha, :seriesalpha,
:markershape, :markercolor, :markersize, :markeralpha, :linecolor,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :linestyle,
:fillrange, :fillcolor, :fillalpha, :linewidth,
:linealpha,
:markershape,
:markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokealpha,
:fillrange,
:fillcolor,
:fillalpha,
:bins, :bins,
:layout, :layout,
:title, :window_title, :title,
:guide, :lims, :ticks, :scale, :flip, :window_title,
:titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign, :guide,
:titlefontrotation, :titlefontcolor, :lims,
:legendfontfamily, :legendfontsize, :legendfonthalign, :legendfontvalign, :ticks,
:legendfontrotation, :legendfontcolor, :scale,
:tickfontfamily, :tickfontsize, :tickfonthalign, :tickfontvalign, :flip,
:tickfontrotation, :tickfontcolor, :titlefontfamily,
:guidefontfamily, :guidefontsize, :guidefonthalign, :guidefontvalign, :titlefontsize,
:guidefontrotation, :guidefontcolor, :titlefonthalign,
:grid, :gridalpha, :gridstyle, :gridlinewidth, :titlefontvalign,
:legend, :legendtitle, :colorbar, :colorbar_title, :colorbar_entry, :titlefontrotation,
:colorbar_titlefontfamily, :colorbar_titlefontsize, :titlefontcolor,
:colorbar_titlefontvalign, :colorbar_titlefonthalign, :legendfontfamily,
:colorbar_titlefontrotation, :colorbar_titlefontcolor, :legendfontsize,
:fill_z, :line_z, :marker_z, :levels, :legendfonthalign,
:ribbon, :quiver, :legendfontvalign,
:legendfontrotation,
:legendfontcolor,
:tickfontfamily,
:tickfontsize,
:tickfonthalign,
:tickfontvalign,
:tickfontrotation,
:tickfontcolor,
:guidefontfamily,
:guidefontsize,
:guidefonthalign,
:guidefontvalign,
:guidefontrotation,
:guidefontcolor,
:grid,
:gridalpha,
:gridstyle,
:gridlinewidth,
:legend,
:legendtitle,
:colorbar,
:colorbar_title,
:colorbar_entry,
:colorbar_titlefontfamily,
:colorbar_titlefontsize,
:colorbar_titlefontvalign,
:colorbar_titlefonthalign,
:colorbar_titlefontrotation,
:colorbar_titlefontcolor,
:fill_z,
:line_z,
:marker_z,
:levels,
:ribbon,
:quiver,
:orientation, :orientation,
:overwrite_figure, :overwrite_figure,
:polar, :polar,
:aspect_ratio, :aspect_ratio,
:normalize, :weights, :normalize,
:weights,
:inset_subplots, :inset_subplots,
:bar_width, :bar_width,
:arrow, :arrow,
@ -381,35 +438,79 @@ end
const _plotly_attr = merge_with_base_supported([ const _plotly_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_inside, :background_color_outside, :background_color_legend,
:foreground_color_legend, :foreground_color_guide, :background_color_inside,
:foreground_color_grid, :foreground_color_axis, :background_color_outside,
:foreground_color_text, :foreground_color_border, :foreground_color_legend,
:foreground_color_guide,
:foreground_color_grid,
:foreground_color_axis,
:foreground_color_text,
:foreground_color_border,
:foreground_color_title, :foreground_color_title,
:label, :label,
:seriescolor, :seriesalpha, :seriescolor,
:linecolor, :linestyle, :linewidth, :linealpha, :seriesalpha,
:markershape, :markercolor, :markersize, :markeralpha, :linecolor,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle, :linestyle,
:fillrange, :fillcolor, :fillalpha, :linewidth,
:linealpha,
:markershape,
:markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokealpha,
:markerstrokestyle,
:fillrange,
:fillcolor,
:fillalpha,
:bins, :bins,
:title, :titlelocation, :title,
:titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign, :titlelocation,
:titlefontfamily,
:titlefontsize,
:titlefonthalign,
:titlefontvalign,
:titlefontcolor, :titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfontcolor, :legendfontfamily,
:tickfontfamily, :tickfontsize, :tickfontcolor, :legendfontsize,
:guidefontfamily, :guidefontsize, :guidefontcolor, :legendfontcolor,
:tickfontfamily,
:tickfontsize,
:tickfontcolor,
:guidefontfamily,
:guidefontsize,
:guidefontcolor,
:window_title, :window_title,
:guide, :lims, :ticks, :scale, :flip, :rotation, :guide,
:tickfont, :guidefont, :legendfont, :lims,
:grid, :gridalpha, :gridlinewidth, :ticks,
:legend, :colorbar, :colorbar_title, :colorbar_entry, :scale,
:marker_z, :fill_z, :line_z, :levels, :flip,
:ribbon, :quiver, :rotation,
:tickfont,
:guidefont,
:legendfont,
:grid,
:gridalpha,
:gridlinewidth,
:legend,
:colorbar,
:colorbar_title,
:colorbar_entry,
:marker_z,
:fill_z,
:line_z,
:levels,
:ribbon,
:quiver,
:orientation, :orientation,
# :overwrite_figure, # :overwrite_figure,
:polar, :polar,
:normalize, :weights, :normalize,
:weights,
# :contours, # :contours,
:aspect_ratio, :aspect_ratio,
:hover, :hover,
@ -420,7 +521,7 @@ const _plotly_attr = merge_with_base_supported([
:tick_direction, :tick_direction,
:camera, :camera,
:contour_labels, :contour_labels,
]) ])
const _plotly_seriestype = [ const _plotly_seriestype = [
:path, :path,
@ -434,12 +535,24 @@ const _plotly_seriestype = [
:shape, :shape,
:scattergl, :scattergl,
:straightline, :straightline,
:mesh3d :mesh3d,
] ]
const _plotly_style = [:auto, :solid, :dash, :dot, :dashdot] const _plotly_style = [:auto, :solid, :dash, :dot, :dashdot]
const _plotly_marker = [ const _plotly_marker = [
:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :none,
:cross, :xcross, :pentagon, :hexagon, :octagon, :vline, :hline :auto,
:circle,
:rect,
:diamond,
:utriangle,
:dtriangle,
:cross,
:xcross,
:pentagon,
:hexagon,
:octagon,
:vline,
:hline,
] ]
const _plotly_scale = [:identity, :log10] const _plotly_scale = [:identity, :log10]
@ -454,23 +567,50 @@ const _pgfplots_attr = merge_with_base_supported([
:background_color_inside, :background_color_inside,
# :background_color_outside, # :background_color_outside,
# :foreground_color_legend, # :foreground_color_legend,
:foreground_color_grid, :foreground_color_axis, :foreground_color_grid,
:foreground_color_text, :foreground_color_border, :foreground_color_axis,
:foreground_color_text,
:foreground_color_border,
:label, :label,
:seriescolor, :seriesalpha, :seriescolor,
:linecolor, :linestyle, :linewidth, :linealpha, :seriesalpha,
:markershape, :markercolor, :markersize, :markeralpha, :linecolor,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle, :linestyle,
:fillrange, :fillcolor, :fillalpha, :linewidth,
:linealpha,
:markershape,
:markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokealpha,
:markerstrokestyle,
:fillrange,
:fillcolor,
:fillalpha,
:bins, :bins,
# :bar_width, :bar_edges, # :bar_width, :bar_edges,
:title, :title,
# :window_title, # :window_title,
:guide, :guide_position, :lims, :ticks, :scale, :flip, :rotation, :guide,
:tickfont, :guidefont, :legendfont, :guide_position,
:grid, :legend, :lims,
:colorbar, :colorbar_title, :ticks,
:fill_z, :line_z, :marker_z, :levels, :scale,
:flip,
:rotation,
:tickfont,
:guidefont,
:legendfont,
:grid,
:legend,
:colorbar,
:colorbar_title,
:fill_z,
:line_z,
:marker_z,
:levels,
# :ribbon, :quiver, :arrow, # :ribbon, :quiver, :arrow,
# :orientation, # :orientation,
# :overwrite_figure, # :overwrite_figure,
@ -482,9 +622,36 @@ const _pgfplots_attr = merge_with_base_supported([
:camera, :camera,
:contour_labels, :contour_labels,
]) ])
const _pgfplots_seriestype = [:path, :path3d, :scatter, :steppre, :stepmid, :steppost, :histogram2d, :ysticks, :xsticks, :contour, :shape, :straightline,] const _pgfplots_seriestype = [
:path,
:path3d,
:scatter,
:steppre,
:stepmid,
:steppost,
:histogram2d,
:ysticks,
:xsticks,
:contour,
:shape,
:straightline,
]
const _pgfplots_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] const _pgfplots_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
const _pgfplots_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon, :hline, :vline] #vcat(_allMarkers, Shape) const _pgfplots_marker = [
:none,
:auto,
:circle,
:rect,
:diamond,
:utriangle,
:dtriangle,
:cross,
:xcross,
:star5,
:pentagon,
:hline,
:vline,
] #vcat(_allMarkers, Shape)
const _pgfplots_scale = [:identity, :ln, :log2, :log10] const _pgfplots_scale = [:identity, :ln, :log2, :log10]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -497,11 +664,11 @@ function _initialize_backend(pkg::PlotlyJSBackend)
end end
end end
const _plotlyjs_attr = _plotly_attr const _plotlyjs_attr = _plotly_attr
const _plotlyjs_seriestype = _plotly_seriestype const _plotlyjs_seriestype = _plotly_seriestype
const _plotlyjs_style = _plotly_style const _plotlyjs_style = _plotly_style
const _plotlyjs_marker = _plotly_marker const _plotlyjs_marker = _plotly_marker
const _plotlyjs_scale = _plotly_scale const _plotlyjs_scale = _plotly_scale
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# pyplot # pyplot
@ -519,36 +686,89 @@ end
const _pyplot_attr = merge_with_base_supported([ const _pyplot_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_inside, :background_color_outside, :background_color_legend,
:foreground_color_grid, :foreground_color_legend, :foreground_color_title, :background_color_inside,
:foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text, :background_color_outside,
:foreground_color_grid,
:foreground_color_legend,
:foreground_color_title,
:foreground_color_axis,
:foreground_color_border,
:foreground_color_guide,
:foreground_color_text,
:label, :label,
:linecolor, :linestyle, :linewidth, :linealpha, :linecolor,
:markershape, :markercolor, :markersize, :markeralpha, :linestyle,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :linewidth,
:fillrange, :fillcolor, :fillalpha, :linealpha,
:bins, :bar_width, :bar_edges, :bar_position, :markershape,
:title, :titlelocation, :titlefont, :markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokealpha,
:fillrange,
:fillcolor,
:fillalpha,
:bins,
:bar_width,
:bar_edges,
:bar_position,
:title,
:titlelocation,
:titlefont,
:window_title, :window_title,
:guide, :guide_position, :lims, :ticks, :scale, :flip, :rotation, :guide,
:titlefontfamily, :titlefontsize, :titlefontcolor, :guide_position,
:legendfontfamily, :legendfontsize, :legendfontcolor, :lims,
:tickfontfamily, :tickfontsize, :tickfontcolor, :ticks,
:guidefontfamily, :guidefontsize, :guidefontcolor, :scale,
:grid, :gridalpha, :gridstyle, :gridlinewidth, :flip,
:legend, :legendtitle, :colorbar, :colorbar_title, :colorbar_entry, :rotation,
:colorbar_ticks, :colorbar_tickfontfamily, :colorbar_tickfontsize, :titlefontfamily,
:colorbar_tickfonthalign, :colorbar_tickfontvalign, :titlefontsize,
:colorbar_tickfontrotation, :colorbar_tickfontcolor, :titlefontcolor,
:legendfontfamily,
:legendfontsize,
:legendfontcolor,
:tickfontfamily,
:tickfontsize,
:tickfontcolor,
:guidefontfamily,
:guidefontsize,
:guidefontcolor,
:grid,
:gridalpha,
:gridstyle,
:gridlinewidth,
:legend,
:legendtitle,
:colorbar,
:colorbar_title,
:colorbar_entry,
:colorbar_ticks,
:colorbar_tickfontfamily,
:colorbar_tickfontsize,
:colorbar_tickfonthalign,
:colorbar_tickfontvalign,
:colorbar_tickfontrotation,
:colorbar_tickfontcolor,
:colorbar_scale, :colorbar_scale,
:marker_z, :line_z, :fill_z, :marker_z,
:line_z,
:fill_z,
:levels, :levels,
:ribbon, :quiver, :arrow, :ribbon,
:quiver,
:arrow,
:orientation, :orientation,
:overwrite_figure, :overwrite_figure,
:polar, :polar,
:normalize, :weights, :normalize,
:contours, :aspect_ratio, :weights,
:contours,
:aspect_ratio,
:clims, :clims,
:inset_subplots, :inset_subplots,
:dpi, :dpi,
@ -557,7 +777,7 @@ const _pyplot_attr = merge_with_base_supported([
:tick_direction, :tick_direction,
:camera, :camera,
:contour_labels, :contour_labels,
]) ])
const _pyplot_seriestype = [ const _pyplot_seriestype = [
:path, :path,
:steppre, :steppre,
@ -598,18 +818,35 @@ const _gaston_attr = merge_with_base_supported([
# :foreground_color_legend, # :foreground_color_legend,
# :foreground_color_grid, :foreground_color_axis, # :foreground_color_grid, :foreground_color_axis,
# :foreground_color_text, :foreground_color_border, # :foreground_color_text, :foreground_color_border,
:label, :seriescolor, :seriesalpha, :label,
:linecolor, :linestyle, :linewidth, :linealpha, :seriescolor,
:markershape, :markercolor, :markersize, :markeralpha, :seriesalpha,
:linecolor,
:linestyle,
:linewidth,
:linealpha,
:markershape,
:markercolor,
:markersize,
:markeralpha,
# :markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle, # :markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
# :fillrange, :fillcolor, :fillalpha, # :fillrange, :fillcolor, :fillalpha,
# :bins, # :bins,
# :bar_width, :bar_edges, # :bar_width, :bar_edges,
:title, :window_title, :title,
:guide, :guide_position, :window_title,
:lims, :ticks, :scale, :flip, :rotation, :guide,
:tickfont, :guidefont, :guide_position,
:legendfont, :grid, :legend, :lims,
:ticks,
:scale,
:flip,
:rotation,
:tickfont,
:guidefont,
:legendfont,
:grid,
:legend,
# :colorbar, :colorbar_title, # :colorbar, :colorbar_title,
# :fill_z, :line_z, :marker_z, :levels, # :fill_z, :line_z, :marker_z, :levels,
# :ribbon, # :ribbon,
@ -623,7 +860,7 @@ const _gaston_attr = merge_with_base_supported([
# :framestyle, # :framestyle,
# :camera, # :camera,
# :contour_labels, # :contour_labels,
]) ])
const _gaston_seriestype = [ const _gaston_seriestype = [
:path, :path,
@ -632,7 +869,8 @@ const _gaston_seriestype = [
:steppre, :steppre,
:stepmid, :stepmid,
:steppost, :steppost,
:ysticks, :xsticks, :ysticks,
:xsticks,
:contour, :contour,
:shape, :shape,
:straightline, :straightline,
@ -645,14 +883,7 @@ const _gaston_seriestype = [
:image, :image,
] ]
const _gaston_style = [ const _gaston_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
:auto,
:solid,
:dash,
:dot,
:dashdot,
:dashdotdot
]
const _gaston_marker = [ const _gaston_marker = [
:none, :none,
@ -687,14 +918,17 @@ const _unicodeplots_attr = merge_with_base_supported([
:markershape, :markershape,
:bins, :bins,
:title, :title,
:guide, :lims, :guide,
]) :lims,
])
const _unicodeplots_seriestype = [ const _unicodeplots_seriestype = [
:path, :scatter, :straightline, :path,
:scatter,
:straightline,
# :bar, # :bar,
:shape, :shape,
:histogram2d, :histogram2d,
:spy :spy,
] ]
const _unicodeplots_style = [:auto, :solid] const _unicodeplots_style = [:auto, :solid]
const _unicodeplots_marker = [:none, :auto, :circle] const _unicodeplots_marker = [:none, :auto, :circle]
@ -708,33 +942,70 @@ const _canvas_type = Ref(:auto)
const _hdf5_attr = merge_with_base_supported([ const _hdf5_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_inside, :background_color_outside, :background_color_legend,
:foreground_color_grid, :foreground_color_legend, :foreground_color_title, :background_color_inside,
:foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text, :background_color_outside,
:foreground_color_grid,
:foreground_color_legend,
:foreground_color_title,
:foreground_color_axis,
:foreground_color_border,
:foreground_color_guide,
:foreground_color_text,
:label, :label,
:linecolor, :linestyle, :linewidth, :linealpha, :linecolor,
:markershape, :markercolor, :markersize, :markeralpha, :linestyle,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :linewidth,
:fillrange, :fillcolor, :fillalpha, :linealpha,
:bins, :bar_width, :bar_edges, :bar_position, :markershape,
:title, :titlelocation, :titlefont, :markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokealpha,
:fillrange,
:fillcolor,
:fillalpha,
:bins,
:bar_width,
:bar_edges,
:bar_position,
:title,
:titlelocation,
:titlefont,
:window_title, :window_title,
:guide, :lims, :ticks, :scale, :flip, :rotation, :guide,
:tickfont, :guidefont, :legendfont, :lims,
:grid, :legend, :colorbar, :ticks,
:marker_z, :line_z, :fill_z, :scale,
:flip,
:rotation,
:tickfont,
:guidefont,
:legendfont,
:grid,
:legend,
:colorbar,
:marker_z,
:line_z,
:fill_z,
:levels, :levels,
:ribbon, :quiver, :arrow, :ribbon,
:quiver,
:arrow,
:orientation, :orientation,
:overwrite_figure, :overwrite_figure,
:polar, :polar,
:normalize, :weights, :normalize,
:contours, :aspect_ratio, :weights,
:contours,
:aspect_ratio,
:clims, :clims,
:inset_subplots, :inset_subplots,
:dpi, :dpi,
:colorbar_title, :colorbar_title,
]) ])
const _hdf5_seriestype = [ const _hdf5_seriestype = [
:path, :path,
:steppre, :steppre,
@ -759,68 +1030,115 @@ const _hdf5_scale = [:identity, :ln, :log2, :log10]
# Additional constants # Additional constants
#Dict has problems using "Types" as keys. Initialize in "_initialize_backend": #Dict has problems using "Types" as keys. Initialize in "_initialize_backend":
const HDF5PLOT_MAP_STR2TELEM = Dict{String, Type}() const HDF5PLOT_MAP_STR2TELEM = Dict{String,Type}()
const HDF5PLOT_MAP_TELEM2STR = Dict{Type, String}() const HDF5PLOT_MAP_TELEM2STR = Dict{Type,String}()
#Don't really like this global variable... Very hacky #Don't really like this global variable... Very hacky
mutable struct HDF5Plot_PlotRef mutable struct HDF5Plot_PlotRef
ref::Union{Plot, Nothing} ref::Union{Plot,Nothing}
end end
const HDF5PLOT_PLOTREF = HDF5Plot_PlotRef(nothing) const HDF5PLOT_PLOTREF = HDF5Plot_PlotRef(nothing)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# inspectdr # inspectdr
const _inspectdr_attr = merge_with_base_supported([ const _inspectdr_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_inside, :background_color_outside, :background_color_legend,
:background_color_inside,
:background_color_outside,
# :foreground_color_grid, # :foreground_color_grid,
:foreground_color_legend, :foreground_color_title, :foreground_color_legend,
:foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text, :foreground_color_title,
:foreground_color_axis,
:foreground_color_border,
:foreground_color_guide,
:foreground_color_text,
:label, :label,
:seriescolor, :seriesalpha, :seriescolor,
:linecolor, :linestyle, :linewidth, :linealpha, :seriesalpha,
:markershape, :markercolor, :markersize, :markeralpha, :linecolor,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :linestyle,
:linewidth,
:linealpha,
:markershape,
:markercolor,
:markersize,
:markeralpha,
:markerstrokewidth,
:markerstrokecolor,
:markerstrokealpha,
:markerstrokestyle, #Causes warning not to have it... what is this? :markerstrokestyle, #Causes warning not to have it... what is this?
:fillcolor, :fillalpha, #:fillrange, :fillcolor,
# :bins, :bar_width, :bar_edges, :bar_position, :fillalpha, #:fillrange,
:title, :titlelocation, # :bins, :bar_width, :bar_edges, :bar_position,
:title,
:titlelocation,
:window_title, :window_title,
:guide, :lims, :scale, #:ticks, :flip, :rotation, :guide,
:titlefontfamily, :titlefontsize, :titlefontcolor, :lims,
:legendfontfamily, :legendfontsize, :legendfontcolor, :scale, #:ticks, :flip, :rotation,
:tickfontfamily, :tickfontsize, :tickfontcolor, :titlefontfamily,
:guidefontfamily, :guidefontsize, :guidefontcolor, :titlefontsize,
:grid, :legend, #:colorbar, :titlefontcolor,
# :marker_z, :legendfontfamily,
# :line_z, :legendfontsize,
# :levels, :legendfontcolor,
# :ribbon, :quiver, :arrow, :tickfontfamily,
# :orientation, :tickfontsize,
:tickfontcolor,
:guidefontfamily,
:guidefontsize,
:guidefontcolor,
:grid,
:legend, #:colorbar,
# :marker_z,
# :line_z,
# :levels,
# :ribbon, :quiver, :arrow,
# :orientation,
:overwrite_figure, :overwrite_figure,
:polar, :polar,
# :normalize, :weights, # :normalize, :weights,
# :contours, :aspect_ratio, # :contours, :aspect_ratio,
# :clims, # :clims,
# :inset_subplots, # :inset_subplots,
:dpi, :dpi,
# :colorbar_title, # :colorbar_title,
]) ])
const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot] const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot]
const _inspectdr_seriestype = [ const _inspectdr_seriestype = [
:path, :scatter, :shape, :straightline, #, :steppre, :stepmid, :steppost :path,
] :scatter,
:shape,
:straightline, #, :steppre, :stepmid, :steppost
]
#see: _allMarkers, _shape_keys #see: _allMarkers, _shape_keys
const _inspectdr_marker = Symbol[ const _inspectdr_marker = Symbol[
:none, :auto, :none,
:circle, :rect, :diamond, :auto,
:cross, :xcross, :circle,
:utriangle, :dtriangle, :rtriangle, :ltriangle, :rect,
:pentagon, :hexagon, :heptagon, :octagon, :diamond,
:star4, :star5, :star6, :star7, :star8, :cross,
:vline, :hline, :+, :x, :xcross,
:utriangle,
:dtriangle,
:rtriangle,
:ltriangle,
:pentagon,
:hexagon,
:heptagon,
:octagon,
:star4,
:star5,
:star6,
:star7,
:star8,
:vline,
:hline,
:+,
:x,
] ]
const _inspectdr_scale = [:identity, :ln, :log2, :log10] const _inspectdr_scale = [:identity, :ln, :log2, :log10]

View File

@ -27,7 +27,7 @@ const _pgfplots_markers = KW(
:diamond => "diamond*", :diamond => "diamond*",
:pentagon => "pentagon*", :pentagon => "pentagon*",
:hline => "-", :hline => "-",
:vline => "|" :vline => "|",
) )
const _pgfplots_legend_pos = KW( const _pgfplots_legend_pos = KW(
@ -38,7 +38,6 @@ const _pgfplots_legend_pos = KW(
:outertopright => "outer north east", :outertopright => "outer north east",
) )
const _pgf_series_extrastyle = KW( const _pgf_series_extrastyle = KW(
:steppre => "const plot mark right", :steppre => "const plot mark right",
:stepmid => "const plot mark mid", :stepmid => "const plot mark mid",
@ -50,11 +49,7 @@ const _pgf_series_extrastyle = KW(
# PGFPlots uses the anchors to define orientations for example to align left # PGFPlots uses the anchors to define orientations for example to align left
# one needs to use the right edge as anchor # one needs to use the right edge as anchor
const _pgf_annotation_halign = KW( const _pgf_annotation_halign = KW(:center => "", :left => "right", :right => "left")
:center => "",
:left => "right",
:right => "left"
)
const _pgf_framestyles = [:box, :axes, :origin, :zerolines, :grid, :none] const _pgf_framestyles = [:box, :axes, :origin, :zerolines, :grid, :none]
const _pgf_framestyle_defaults = Dict(:semi => :box) const _pgf_framestyle_defaults = Dict(:semi => :box)
@ -63,7 +58,9 @@ function pgf_framestyle(style::Symbol)
return style return style
else else
default_style = get(_pgf_framestyle_defaults, style, :axes) default_style = get(_pgf_framestyle_defaults, style, :axes)
@warn("Framestyle :$style is not (yet) supported by the PGFPlots backend. :$default_style was cosen instead.") @warn(
"Framestyle :$style is not (yet) supported by the PGFPlots backend. :$default_style was cosen instead."
)
default_style default_style
end end
end end
@ -78,15 +75,15 @@ end
function pgf_color(grad::ColorGradient) function pgf_color(grad::ColorGradient)
# Can't handle ColorGradient here, fallback to defaults. # Can't handle ColorGradient here, fallback to defaults.
cstr = @sprintf("{rgb,1:red,%.8f;green,%.8f;blue,%.8f}", 0.0, 0.60560316,0.97868012) cstr = @sprintf("{rgb,1:red,%.8f;green,%.8f;blue,%.8f}", 0.0, 0.60560316, 0.97868012)
cstr, 1 cstr, 1
end end
# Generates a colormap for pgfplots based on a ColorGradient # Generates a colormap for pgfplots based on a ColorGradient
function pgf_colormap(grad::ColorGradient) function pgf_colormap(grad::ColorGradient)
join(map(grad.colors) do c join(map(grad.colors) do c
@sprintf("rgb=(%.8f,%.8f,%.8f)", red(c), green(c),blue(c)) @sprintf("rgb=(%.8f,%.8f,%.8f)", red(c), green(c), blue(c))
end,", ") end, ", ")
end end
pgf_thickness_scaling(plt::Plot) = plt[:thickness_scaling] pgf_thickness_scaling(plt::Plot) = plt[:thickness_scaling]
@ -94,7 +91,7 @@ pgf_thickness_scaling(sp::Subplot) = pgf_thickness_scaling(sp.plt)
pgf_thickness_scaling(series) = pgf_thickness_scaling(series[:subplot]) pgf_thickness_scaling(series) = pgf_thickness_scaling(series[:subplot])
function pgf_fillstyle(plotattributes, i = 1) function pgf_fillstyle(plotattributes, i = 1)
cstr,a = pgf_color(get_fillcolor(plotattributes, i)) cstr, a = pgf_color(get_fillcolor(plotattributes, i))
fa = get_fillalpha(plotattributes, i) fa = get_fillalpha(plotattributes, i)
if fa !== nothing if fa !== nothing
a = fa a = fa
@ -126,8 +123,15 @@ end
function pgf_marker(plotattributes, i = 1) function pgf_marker(plotattributes, i = 1)
shape = _cycle(plotattributes[:markershape], i) shape = _cycle(plotattributes[:markershape], i)
cstr, a = pgf_color(plot_color(get_markercolor(plotattributes, i), get_markeralpha(plotattributes, i))) cstr, a = pgf_color(
cstr_stroke, a_stroke = pgf_color(plot_color(get_markerstrokecolor(plotattributes, i), get_markerstrokealpha(plotattributes, i))) plot_color(get_markercolor(plotattributes, i), get_markeralpha(plotattributes, i)),
)
cstr_stroke, a_stroke = pgf_color(
plot_color(
get_markerstrokecolor(plotattributes, i),
get_markerstrokealpha(plotattributes, i),
),
)
return string( return string(
"mark = $(get(_pgfplots_markers, shape, "*")),\n", "mark = $(get(_pgfplots_markers, shape, "*")),\n",
"mark size = $(pgf_thickness_scaling(plotattributes) * 0.5 * _cycle(plotattributes[:markersize], i)),\n", "mark size = $(pgf_thickness_scaling(plotattributes) * 0.5 * _cycle(plotattributes[:markersize], i)),\n",
@ -138,22 +142,28 @@ function pgf_marker(plotattributes, i = 1)
line width = $(pgf_thickness_scaling(plotattributes) * _cycle(plotattributes[:markerstrokewidth], i)), line width = $(pgf_thickness_scaling(plotattributes) * _cycle(plotattributes[:markerstrokewidth], i)),
rotate = $(shape == :dtriangle ? 180 : 0), rotate = $(shape == :dtriangle ? 180 : 0),
$(get(_pgfplots_linestyles, _cycle(plotattributes[:markerstrokestyle], i), "solid")) $(get(_pgfplots_linestyles, _cycle(plotattributes[:markerstrokestyle], i), "solid"))
}" }",
) )
end end
function pgf_add_annotation!(o, x, y, val, thickness_scaling = 1) function pgf_add_annotation!(o, x, y, val, thickness_scaling = 1)
# Construct the style string. # Construct the style string.
# Currently supports color and orientation # Currently supports color and orientation
cstr,a = pgf_color(val.font.color) cstr, a = pgf_color(val.font.color)
push!(o, PGFPlots.Plots.Node(val.str, # Annotation Text push!(
x, y, o,
style=""" PGFPlots.Plots.Node(
$(get(_pgf_annotation_halign,val.font.halign,"")), val.str, # Annotation Text
color=$cstr, draw opacity=$(convert(Float16,a)), x,
rotate=$(val.font.rotation), y,
font=$(pgf_font(val.font.pointsize, thickness_scaling)) style = """
""")) $(get(_pgf_annotation_halign,val.font.halign,"")),
color=$cstr, draw opacity=$(convert(Float16,a)),
rotate=$(val.font.rotation),
font=$(pgf_font(val.font.pointsize, thickness_scaling))
""",
),
)
end end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
@ -241,7 +251,15 @@ function pgf_series(sp::Subplot, series::Series)
# add fillrange # add fillrange
if series[:fillrange] !== nothing && st != :shape if series[:fillrange] !== nothing && st != :shape
push!(series_collection, pgf_fillrange_series(series, i, _cycle(series[:fillrange], rng), seg_args...)) push!(
series_collection,
pgf_fillrange_series(
series,
i,
_cycle(series[:fillrange], rng),
seg_args...,
),
)
end end
# build/return the series object # build/return the series object
@ -313,7 +331,7 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function pgf_axis(sp::Subplot, letter) function pgf_axis(sp::Subplot, letter)
axis = sp[Symbol(letter,:axis)] axis = sp[Symbol(letter, :axis)]
style = [] style = []
kw = KW() kw = KW()
@ -324,7 +342,7 @@ function pgf_axis(sp::Subplot, letter)
framestyle = pgf_framestyle(sp[:framestyle]) framestyle = pgf_framestyle(sp[:framestyle])
# axis guide # axis guide
kw[Symbol(letter,:label)] = axis[:guide] kw[Symbol(letter, :label)] = axis[:guide]
# axis label position # axis label position
labelpos = "" labelpos = ""
@ -336,7 +354,23 @@ function pgf_axis(sp::Subplot, letter)
# Add label font # Add label font
cstr, α = pgf_color(plot_color(axis[:guidefontcolor])) cstr, α = pgf_color(plot_color(axis[:guidefontcolor]))
push!(style, string(letter, "label style = {", labelpos ,"font = ", pgf_font(axis[:guidefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", axis[:guidefontrotation], "}")) push!(
style,
string(
letter,
"label style = {",
labelpos,
"font = ",
pgf_font(axis[:guidefontsize], pgf_thickness_scaling(sp)),
", color = ",
cstr,
", draw opacity = ",
α,
", rotate = ",
axis[:guidefontrotation],
"}",
),
)
# flip/reverse? # flip/reverse?
axis[:flip] && push!(style, "$letter dir=reverse") axis[:flip] && push!(style, "$letter dir=reverse")
@ -344,7 +378,7 @@ function pgf_axis(sp::Subplot, letter)
# scale # scale
scale = axis[:scale] scale = axis[:scale]
if scale in (:log2, :ln, :log10) if scale in (:log2, :ln, :log10)
kw[Symbol(letter,:mode)] = "log" kw[Symbol(letter, :mode)] = "log"
scale == :ln || push!(style, "log basis $letter=$(scale == :log2 ? 2 : 10)") scale == :ln || push!(style, "log basis $letter=$(scale == :log2 ? 2 : 10)")
end end
@ -363,16 +397,20 @@ function pgf_axis(sp::Subplot, letter)
# limits # limits
# TODO: support zlims # TODO: support zlims
if letter != :z if letter != :z
lims = ispolar(sp) && letter == :x ? rad2deg.(axis_limits(sp, :x)) : axis_limits(sp, letter) lims =
kw[Symbol(letter,:min)] = lims[1] ispolar(sp) && letter == :x ? rad2deg.(axis_limits(sp, :x)) :
kw[Symbol(letter,:max)] = lims[2] axis_limits(sp, letter)
kw[Symbol(letter, :min)] = lims[1]
kw[Symbol(letter, :max)] = lims[2]
end end
if !(axis[:ticks] in (nothing, false, :none, :native)) && framestyle != :none if !(axis[:ticks] in (nothing, false, :none, :native)) && framestyle != :none
ticks = get_ticks(sp, axis) ticks = get_ticks(sp, axis)
#pgf plot ignores ticks with angle below 90 when xmin = 90 so shift values #pgf plot ignores ticks with angle below 90 when xmin = 90 so shift values
tick_values = ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] : ticks[1] tick_values =
push!(style, string(letter, "tick = {", join(tick_values,","), "}")) ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] :
ticks[1]
push!(style, string(letter, "tick = {", join(tick_values, ","), "}"))
if axis[:showaxis] && axis[:scale] in (:ln, :log2, :log10) && axis[:ticks] == :auto if axis[:showaxis] && axis[:scale] in (:ln, :log2, :log10) && axis[:ticks] == :auto
# wrap the power part of label with } # wrap the power part of label with }
tick_labels = Vector{String}(undef, length(ticks[2])) tick_labels = Vector{String}(undef, length(ticks[2]))
@ -381,21 +419,59 @@ function pgf_axis(sp::Subplot, letter)
power = string("{", power, "}") power = string("{", power, "}")
tick_labels[i] = string(base, "^", power) tick_labels[i] = string(base, "^", power)
end end
push!(style, string(letter, "ticklabels = {\$", join(tick_labels,"\$,\$"), "\$}")) push!(
style,
string(letter, "ticklabels = {\$", join(tick_labels, "\$,\$"), "\$}"),
)
elseif axis[:showaxis] elseif axis[:showaxis]
tick_labels = ispolar(sp) && letter == :x ? [ticks[2][3:end]..., "0", "45"] : ticks[2] tick_labels =
ispolar(sp) && letter == :x ? [ticks[2][3:end]..., "0", "45"] : ticks[2]
if axis[:formatter] in (:scientific, :auto) if axis[:formatter] in (:scientific, :auto)
tick_labels = string.("\$", convert_sci_unicode.(tick_labels), "\$") tick_labels = string.("\$", convert_sci_unicode.(tick_labels), "\$")
tick_labels = replace.(tick_labels, Ref("×" => "\\times")) tick_labels = replace.(tick_labels, Ref("×" => "\\times"))
end end
push!(style, string(letter, "ticklabels = {", join(tick_labels,","), "}")) push!(style, string(letter, "ticklabels = {", join(tick_labels, ","), "}"))
else else
push!(style, string(letter, "ticklabels = {}")) push!(style, string(letter, "ticklabels = {}"))
end end
push!(style, string(letter, "tick align = ", (axis[:tick_direction] == :out ? "outside" : "inside"))) push!(
style,
string(
letter,
"tick align = ",
(axis[:tick_direction] == :out ? "outside" : "inside"),
),
)
cstr, α = pgf_color(plot_color(axis[:tickfontcolor])) cstr, α = pgf_color(plot_color(axis[:tickfontcolor]))
push!(style, string(letter, "ticklabel style = {font = ", pgf_font(axis[:tickfontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", axis[:tickfontrotation], "}")) push!(
push!(style, string(letter, " grid style = {", pgf_linestyle(pgf_thickness_scaling(sp) * axis[:gridlinewidth], axis[:foreground_color_grid], axis[:gridalpha], axis[:gridstyle]), "}")) style,
string(
letter,
"ticklabel style = {font = ",
pgf_font(axis[:tickfontsize], pgf_thickness_scaling(sp)),
", color = ",
cstr,
", draw opacity = ",
α,
", rotate = ",
axis[:tickfontrotation],
"}",
),
)
push!(
style,
string(
letter,
" grid style = {",
pgf_linestyle(
pgf_thickness_scaling(sp) * axis[:gridlinewidth],
axis[:foreground_color_grid],
axis[:gridalpha],
axis[:gridstyle],
),
"}",
),
)
end end
# framestyle # framestyle
@ -412,7 +488,20 @@ function pgf_axis(sp::Subplot, letter)
if framestyle == :zerolines if framestyle == :zerolines
push!(style, string("extra ", letter, " ticks = 0")) push!(style, string("extra ", letter, " ticks = 0"))
push!(style, string("extra ", letter, " tick labels = ")) push!(style, string("extra ", letter, " tick labels = "))
push!(style, string("extra ", letter, " tick style = {grid = major, major grid style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_border], 1.0), "}}")) push!(
style,
string(
"extra ",
letter,
" tick style = {grid = major, major grid style = {",
pgf_linestyle(
pgf_thickness_scaling(sp),
axis[:foreground_color_border],
1.0,
),
"}}",
),
)
end end
if !axis[:showaxis] if !axis[:showaxis]
@ -421,7 +510,19 @@ function pgf_axis(sp::Subplot, letter)
if !axis[:showaxis] || framestyle in (:zerolines, :grid, :none) if !axis[:showaxis] || framestyle in (:zerolines, :grid, :none)
push!(style, string(letter, " axis line style = {draw opacity = 0}")) push!(style, string(letter, " axis line style = {draw opacity = 0}"))
else else
push!(style, string(letter, " axis line style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_border], 1.0), "}")) push!(
style,
string(
letter,
" axis line style = {",
pgf_linestyle(
pgf_thickness_scaling(sp),
axis[:foreground_color_border],
1.0,
),
"}",
),
)
end end
# return the style list and KW args # return the style list and KW args
@ -430,7 +531,6 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function _update_plot_object(plt::Plot{PGFPlotsBackend}) function _update_plot_object(plt::Plot{PGFPlotsBackend})
plt.o = PGFPlots.Axis[] plt.o = PGFPlots.Axis[]
# Obtain the total height of the plot by extracting the maximal bottom # Obtain the total height of the plot by extracting the maximal bottom
@ -438,7 +538,7 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
total_height = bottom(bbox(plt.layout)) total_height = bottom(bbox(plt.layout))
for sp in plt.subplots for sp in plt.subplots
# first build the PGFPlots.Axis object # first build the PGFPlots.Axis object
style = ["unbounded coords=jump"] style = ["unbounded coords=jump"]
kw = KW() kw = KW()
@ -456,18 +556,34 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
# A round on 2 decimal places should be enough precision for 300 dpi # A round on 2 decimal places should be enough precision for 300 dpi
# plots. # plots.
bb = bbox(sp) bb = bbox(sp)
push!(style, """ push!(
xshift = $(left(bb).value)mm, style,
yshift = $(round((total_height - (bottom(bb))).value, digits=2))mm, """
axis background/.style={fill=$(pgf_color(sp[:background_color_inside])[1])} xshift = $(left(bb).value)mm,
""") yshift = $(round((total_height - (bottom(bb))).value, digits=2))mm,
axis background/.style={fill=$(pgf_color(sp[:background_color_inside])[1])}
""",
)
kw[:width] = "$(width(bb).value)mm" kw[:width] = "$(width(bb).value)mm"
kw[:height] = "$(height(bb).value)mm" kw[:height] = "$(height(bb).value)mm"
if sp[:title] != "" if sp[:title] != ""
kw[:title] = "$(sp[:title])" kw[:title] = "$(sp[:title])"
cstr, α = pgf_color(plot_color(sp[:titlefontcolor])) cstr, α = pgf_color(plot_color(sp[:titlefontcolor]))
push!(style, string("title style = {font = ", pgf_font(sp[:titlefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", sp[:titlefontrotation], "}")) push!(
style,
string(
"title style = {font = ",
pgf_font(sp[:titlefontsize], pgf_thickness_scaling(sp)),
", color = ",
cstr,
", draw opacity = ",
α,
", rotate = ",
sp[:titlefontrotation],
"}",
),
)
end end
if get_aspect_ratio(sp) in (1, :equal) if get_aspect_ratio(sp) in (1, :equal)
@ -481,15 +597,25 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
cstr, bg_alpha = pgf_color(plot_color(sp[:background_color_legend])) cstr, bg_alpha = pgf_color(plot_color(sp[:background_color_legend]))
fg_alpha = alpha(plot_color(sp[:foreground_color_legend])) fg_alpha = alpha(plot_color(sp[:foreground_color_legend]))
push!(style, string( push!(
"legend style = {", style,
pgf_linestyle(pgf_thickness_scaling(sp), sp[:foreground_color_legend], fg_alpha, "solid", ), ",", string(
"legend style = {",
pgf_linestyle(
pgf_thickness_scaling(sp),
sp[:foreground_color_legend],
fg_alpha,
"solid",
),
",",
"fill = $cstr,", "fill = $cstr,",
"fill opacity = $bg_alpha,", "fill opacity = $bg_alpha,",
"text opacity = $(alpha(plot_color(sp[:legendfontcolor]))),", "text opacity = $(alpha(plot_color(sp[:legendfontcolor]))),",
"font = ", pgf_font(sp[:legendfontsize], pgf_thickness_scaling(sp)), "font = ",
"}", pgf_font(sp[:legendfontsize], pgf_thickness_scaling(sp)),
)) "}",
),
)
if any(s[:seriestype] == :contour for s in series_list(sp)) if any(s[:seriestype] == :contour for s in series_list(sp))
kw[:view] = "{0}{90}" kw[:view] = "{0}{90}"
@ -520,7 +646,10 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
for series in series_list(sp) for series in series_list(sp)
for col in (:markercolor, :fillcolor, :linecolor) for col in (:markercolor, :fillcolor, :linecolor)
if typeof(series.plotattributes[col]) == ColorGradient if typeof(series.plotattributes[col]) == ColorGradient
push!(style,"colormap={plots}{$(pgf_colormap(series.plotattributes[col]))}") push!(
style,
"colormap={plots}{$(pgf_colormap(series.plotattributes[col]))}",
)
if sp[:colorbar] == :none if sp[:colorbar] == :none
kw[:colorbar] = "false" kw[:colorbar] = "false"
@ -543,17 +672,26 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
# add series annotations # add series annotations
anns = series[:series_annotations] anns = series[:series_annotations]
for (xi,yi,str,fnt) in EachAnn(anns, series[:x], series[:y]) for (xi, yi, str, fnt) in EachAnn(anns, series[:x], series[:y])
pgf_add_annotation!(o, xi, yi, PlotText(str, fnt), pgf_thickness_scaling(series)) pgf_add_annotation!(
o,
xi,
yi,
PlotText(str, fnt),
pgf_thickness_scaling(series),
)
end end
end end
# add the annotations # add the annotations
for ann in sp[:annotations] for ann in sp[:annotations]
pgf_add_annotation!(o, locate_annotation(sp, ann...)..., pgf_thickness_scaling(sp)) pgf_add_annotation!(
o,
locate_annotation(sp, ann...)...,
pgf_thickness_scaling(sp),
)
end end
# add the PGFPlots.Axis to the list # add the PGFPlots.Axis to the list
push!(plt.o, o) push!(plt.o, o)
end end
@ -568,7 +706,7 @@ function _show(io::IO, mime::MIME"application/pdf", plt::Plot{PGFPlotsBackend})
pgfplt = PGFPlots.plot(plt.o) pgfplt = PGFPlots.plot(plt.o)
# save a pdf # save a pdf
fn = tempname()*".pdf" fn = tempname() * ".pdf"
PGFPlots.save(PGFPlots.PDF(fn), pgfplt) PGFPlots.save(PGFPlots.PDF(fn), pgfplt)
# read it into io # read it into io
@ -579,8 +717,12 @@ function _show(io::IO, mime::MIME"application/pdf", plt::Plot{PGFPlotsBackend})
end end
function _show(io::IO, mime::MIME"application/x-tex", plt::Plot{PGFPlotsBackend}) function _show(io::IO, mime::MIME"application/x-tex", plt::Plot{PGFPlotsBackend})
fn = tempname()*".tex" fn = tempname() * ".tex"
PGFPlots.save(fn, backend_object(plt), include_preamble=plt.attr[:tex_output_standalone]) PGFPlots.save(
fn,
backend_object(plt),
include_preamble = plt.attr[:tex_output_standalone],
)
write(io, read(open(fn), String)) write(io, read(open(fn), String))
end end

View File

@ -31,11 +31,11 @@ function _before_layout_calcs(plt::Plot{GastonBackend})
plt.o.layout = gaston_init_subplots(plt, sps) plt.o.layout = gaston_init_subplots(plt, sps)
# Then add the series (curves in gaston) # Then add the series (curves in gaston)
for series plt.series_list for series in plt.series_list
gaston_add_series(plt, series) gaston_add_series(plt, series)
end end
for sp plt.subplots for sp in plt.subplots
sp === nothing && continue sp === nothing && continue
for ann in sp[:annotations] for ann in sp[:annotations]
x, y, val = locate_annotation(sp, ann...) x, y, val = locate_annotation(sp, ann...)
@ -57,7 +57,7 @@ function _update_plot_object(plt::Plot{GastonBackend})
nothing nothing
end end
for (mime, term) ( for (mime, term) in (
"application/eps" => "epscairo", "application/eps" => "epscairo",
"image/eps" => "epslatex", "image/eps" => "epslatex",
"application/pdf" => "pdfcairo", "application/pdf" => "pdfcairo",
@ -69,12 +69,19 @@ for (mime, term) ∈ (
"text/plain" => "dumb", "text/plain" => "dumb",
) )
@eval function _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GastonBackend}) @eval function _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GastonBackend})
term = String($term); tmpfile = "$(Gaston.tempname()).$term" term = String($term)
tmpfile = "$(Gaston.tempname()).$term"
Gaston.save(term=term, output=tmpfile, handle=plt.o.handle, saveopts=gaston_saveopts(plt)) Gaston.save(
while !isfile(tmpfile) end # avoid race condition with read in next line term = term,
output = tmpfile,
handle = plt.o.handle,
saveopts = gaston_saveopts(plt),
)
while !isfile(tmpfile)
end # avoid race condition with read in next line
write(io, read(tmpfile)) write(io, read(tmpfile))
rm(tmpfile, force=true) rm(tmpfile, force = true)
nothing nothing
end end
end end
@ -88,7 +95,16 @@ _display(plt::Plot{GastonBackend}) = display(plt.o)
function gaston_saveopts(plt::Plot{GastonBackend}) function gaston_saveopts(plt::Plot{GastonBackend})
saveopts = String["size $(join(plt.attr[:size], ","))"] saveopts = String["size $(join(plt.attr[:size], ","))"]
push!(saveopts, gaston_font(plottitlefont(plt), rot=false, align=false, color=false, scale=1)) push!(
saveopts,
gaston_font(
plottitlefont(plt),
rot = false,
align = false,
color = false,
scale = 1,
),
)
push!(saveopts, "background $(gaston_color(plt.attr[:background_color]))") push!(saveopts, "background $(gaston_color(plt.attr[:background_color]))")
@ -104,7 +120,7 @@ end
function gaston_get_subplots(n, plt_subplots, layout) function gaston_get_subplots(n, plt_subplots, layout)
nr, nc = size(layout) nr, nc = size(layout)
sps = Array{Any}(nothing, nr, nc) sps = Array{Any}(nothing, nr, nc)
for r 1:nr, c 1:nc # NOTE: col major for r in 1:nr, c in 1:nc # NOTE: col major
l = layout[r, c] l = layout[r, c]
if l isa GridLayout if l isa GridLayout
n, sub = gaston_get_subplots(n, plt_subplots, l) n, sub = gaston_get_subplots(n, plt_subplots, l)
@ -118,7 +134,7 @@ end
function gaston_init_subplots(plt, sps) function gaston_init_subplots(plt, sps)
sz = nr, nc = size(sps) sz = nr, nc = size(sps)
for c 1:nc, r 1:nr # NOTE: row major for c in 1:nc, r in 1:nr # NOTE: row major
sp = sps[r, c] sp = sps[r, c]
if sp isa Subplot || sp === nothing if sp isa Subplot || sp === nothing
gaston_init_subplot(plt, sp) gaston_init_subplot(plt, sp)
@ -130,20 +146,28 @@ function gaston_init_subplots(plt, sps)
return sz return sz
end end
function gaston_init_subplot(plt::Plot{GastonBackend}, sp::Union{Nothing,Subplot{GastonBackend}}) function gaston_init_subplot(
plt::Plot{GastonBackend},
sp::Union{Nothing,Subplot{GastonBackend}},
)
if sp === nothing if sp === nothing
push!(plt.o.subplots, sp) push!(plt.o.subplots, sp)
else else
dims = RecipesPipeline.is3d(sp) || sp.attr[:projection] == "3d" || needs_any_3d_axes(sp) ? 3 : 2 dims =
RecipesPipeline.is3d(sp) ||
sp.attr[:projection] == "3d" ||
needs_any_3d_axes(sp) ? 3 : 2
any_label = false any_label = false
for series series_list(sp) for series in series_list(sp)
if dims == 2 && series[:seriestype] (:heatmap, :contour) if dims == 2 && series[:seriestype] (:heatmap, :contour)
dims = 3 # we need heatmap/contour to use splot, not plot dims = 3 # we need heatmap/contour to use splot, not plot
end end
any_label |= should_add_to_legend(series) any_label |= should_add_to_legend(series)
end end
sp.o = Gaston.Plot( sp.o = Gaston.Plot(
dims=dims, curves=[], axesconf=gaston_parse_axes_args(plt, sp, dims, any_label), dims = dims,
curves = [],
axesconf = gaston_parse_axes_args(plt, sp, dims, any_label),
) )
push!(plt.o.subplots, sp.o) push!(plt.o.subplots, sp.o)
end end
@ -153,7 +177,7 @@ end
function gaston_multiplot_pos_size(layout, parent_xy_wh) function gaston_multiplot_pos_size(layout, parent_xy_wh)
nr, nc = size(layout) nr, nc = size(layout)
dat = Array{Any}(nothing, nr, nc) dat = Array{Any}(nothing, nr, nc)
for r 1:nr, c 1:nc for r in 1:nr, c in 1:nc
l = layout[r, c] l = layout[r, c]
# width and height (pct) are multiplicative (parent) # width and height (pct) are multiplicative (parent)
w = layout.widths[c].value * parent_xy_wh[3] w = layout.widths[c].value * parent_xy_wh[3]
@ -181,7 +205,7 @@ end
function gaston_multiplot_pos_size!(dat) function gaston_multiplot_pos_size!(dat)
nr, nc = size(dat) nr, nc = size(dat)
for r 1:nr, c 1:nc for r in 1:nr, c in 1:nc
xy_wh_sp = dat[r, c] xy_wh_sp = dat[r, c]
if xy_wh_sp isa Array if xy_wh_sp isa Array
gaston_multiplot_pos_size!(xy_wh_sp) gaston_multiplot_pos_size!(xy_wh_sp)
@ -197,15 +221,16 @@ function gaston_multiplot_pos_size!(dat)
end end
function gaston_add_series(plt::Plot{GastonBackend}, series::Series) function gaston_add_series(plt::Plot{GastonBackend}, series::Series)
sp = series[:subplot]; gsp = sp.o sp = series[:subplot]
gsp = sp.o
x, y, z = series[:x], series[:y], series[:z] x, y, z = series[:x], series[:y], series[:z]
st = series[:seriestype] st = series[:seriestype]
curves = [] curves = []
if gsp.dims == 2 && z === nothing if gsp.dims == 2 && z === nothing
for (n, seg) enumerate(series_segments(series, st; check=true)) for (n, seg) in enumerate(series_segments(series, st; check = true))
i, rng = seg.attr_index, seg.range i, rng = seg.attr_index, seg.range
for sc gaston_seriesconf!(sp, series, i, n == 1) for sc in gaston_seriesconf!(sp, series, i, n == 1)
push!(curves, Gaston.Curve(x[rng], y[rng], nothing, nothing, sc)) push!(curves, Gaston.Curve(x[rng], y[rng], nothing, nothing, sc))
end end
end end
@ -213,35 +238,41 @@ function gaston_add_series(plt::Plot{GastonBackend}, series::Series)
if z isa Surface if z isa Surface
z = z.surf z = z.surf
if st == :image if st == :image
z = reverse(Float32.(Gray.(z)), dims=1) # flip y axis z = reverse(Float32.(Gray.(z)), dims = 1) # flip y axis
nr, nc = size(z) nr, nc = size(z)
if (ly = length(y)) == 2 && ly != nr if (ly = length(y)) == 2 && ly != nr
y = collect(range(y[1], y[2], length=nr)) y = collect(range(y[1], y[2], length = nr))
end end
if (lx = length(x)) == 2 && lx != nc if (lx = length(x)) == 2 && lx != nc
x = collect(range(x[1], x[2], length=nc)) x = collect(range(x[1], x[2], length = nc))
end end
elseif st == :heatmap elseif st == :heatmap
length(x) == size(z, 2) + 1 && (x = @view x[1:end-1]) length(x) == size(z, 2) + 1 && (x = @view x[1:(end - 1)])
length(y) == size(z, 1) + 1 && (y = @view y[1:end-1]) length(y) == size(z, 1) + 1 && (y = @view y[1:(end - 1)])
end end
end end
if st == :mesh3d if st == :mesh3d
x, y, z = mesh3d_triangles(x, y, z, series[:connections]) x, y, z = mesh3d_triangles(x, y, z, series[:connections])
end end
for sc gaston_seriesconf!(sp, series, 1, true) for sc in gaston_seriesconf!(sp, series, 1, true)
push!(curves, Gaston.Curve(x, y, z, nothing, sc)) push!(curves, Gaston.Curve(x, y, z, nothing, sc))
end end
end end
for c curves for c in curves
append = length(gsp.curves) > 0; push!(gsp.curves, c) append = length(gsp.curves) > 0
Gaston.write_data(c, gsp.dims, gsp.datafile, append=append) push!(gsp.curves, c)
Gaston.write_data(c, gsp.dims, gsp.datafile, append = append)
end end
nothing nothing
end end
function gaston_seriesconf!(sp::Subplot{GastonBackend}, series::Series, i::Int, add_to_legend::Bool) function gaston_seriesconf!(
sp::Subplot{GastonBackend},
series::Series,
i::Int,
add_to_legend::Bool,
)
#= #=
gnuplot abbreviations (see gnuplot/src/set.c) gnuplot abbreviations (see gnuplot/src/set.c)
--------------------------------------------- ---------------------------------------------
@ -261,7 +292,9 @@ function gaston_seriesconf!(sp::Subplot{GastonBackend}, series::Series, i::Int,
tc: textcolor tc: textcolor
w: with w: with
=# =#
gsp = sp.o; st = series[:seriestype]; extra = [] gsp = sp.o
st = series[:seriestype]
extra = []
add_to_legend &= should_add_to_legend(series) add_to_legend &= should_add_to_legend(series)
curveconf = String[add_to_legend ? "title '$(series[:label])'" : "notitle"] curveconf = String[add_to_legend ? "title '$(series[:label])'" : "notitle"]
@ -320,18 +353,24 @@ function gaston_seriesconf!(sp::Subplot{GastonBackend}, series::Series, i::Int,
end end
function gaston_parse_axes_args( function gaston_parse_axes_args(
plt::Plot{GastonBackend}, sp::Subplot{GastonBackend}, dims::Int, any_label::Bool plt::Plot{GastonBackend},
sp::Subplot{GastonBackend},
dims::Int,
any_label::Bool,
) )
# axesconf = String["set margins 2, 2, 2, 2"] # left, right, bottom, top # axesconf = String["set margins 2, 2, 2, 2"] # left, right, bottom, top
axesconf = String[] axesconf = String[]
polar = ispolar(sp) && dims == 2 # cannot splot in polar coordinates polar = ispolar(sp) && dims == 2 # cannot splot in polar coordinates
for letter (:x, :y, :z) for letter in (:x, :y, :z)
(letter == :z && dims == 2) && continue (letter == :z && dims == 2) && continue
axis = sp.attr[Symbol(letter, :axis)] axis = sp.attr[Symbol(letter, :axis)]
# label names # label names
push!(axesconf, "set $(letter)label '$(axis[:guide])' $(gaston_font(guidefont(axis)))") push!(
axesconf,
"set $(letter)label '$(axis[:guide])' $(gaston_font(guidefont(axis)))",
)
mirror = axis[:mirror] ? "mirror" : "nomirror" mirror = axis[:mirror] ? "mirror" : "nomirror"
if axis[:scale] == :identity if axis[:scale] == :identity
@ -349,7 +388,10 @@ function gaston_parse_axes_args(
if polar if polar
push!(axesconf, "set size square\nunset $(letter)tics") push!(axesconf, "set size square\nunset $(letter)tics")
else else
push!(axesconf, "set $(letter)tics $(mirror) $(axis[:tick_direction]) $(gaston_font(tickfont(axis)))") push!(
axesconf,
"set $(letter)tics $(mirror) $(axis[:tick_direction]) $(gaston_font(tickfont(axis)))",
)
# major tick locations # major tick locations
if axis[:ticks] != :native if axis[:ticks] != :native
@ -372,7 +414,8 @@ function gaston_parse_axes_args(
if axis[:grid] if axis[:grid]
push!(axesconf, "set grid " * (polar ? "polar" : "$(letter)tics")) push!(axesconf, "set grid " * (polar ? "polar" : "$(letter)tics"))
axis[:minorgrid] && push!(axesconf, "set grid " * (polar ? "polar" : "m$(letter)tics")) axis[:minorgrid] &&
push!(axesconf, "set grid " * (polar ? "polar" : "m$(letter)tics"))
end end
ratio = get_aspect_ratio(sp) ratio = get_aspect_ratio(sp)
@ -399,12 +442,20 @@ function gaston_parse_axes_args(
if (ttype = ticksType(rticks)) == :ticks if (ttype = ticksType(rticks)) == :ticks
gaston_ticks = string.(rticks) gaston_ticks = string.(rticks)
elseif ttype == :ticks_and_labels elseif ttype == :ticks_and_labels
gaston_ticks = String["'$l' $t" for (t, l) zip(rticks...)] gaston_ticks = String["'$l' $t" for (t, l) in zip(rticks...)]
end end
push!(axesconf, "set rtics ( " * join(gaston_ticks, ", ") * " ) $(gaston_font(tickfont(sp.attr[:yaxis])))") push!(
axesconf,
"set rtics ( " *
join(gaston_ticks, ", ") *
" ) $(gaston_font(tickfont(sp.attr[:yaxis])))",
)
push!(axesconf, "set trange [$(min(0, tmin)):$(max(2π, tmax))]") push!(axesconf, "set trange [$(min(0, tmin)):$(max(2π, tmax))]")
push!(axesconf, "set rrange [$rmin:$rmax]") push!(axesconf, "set rrange [$rmin:$rmax]")
push!(axesconf, "set ttics 0,30 format \"%g\".GPVAL_DEGREE_SIGN $(gaston_font(tickfont(sp.attr[:xaxis])))") push!(
axesconf,
"set ttics 0,30 format \"%g\".GPVAL_DEGREE_SIGN $(gaston_font(tickfont(sp.attr[:xaxis])))",
)
push!(axesconf, "set mttics 3") push!(axesconf, "set mttics 3")
end end
@ -421,7 +472,7 @@ function gaston_set_ticks!(axesconf, ticks, letter, maj_min, add)
gaston_ticks = String[] gaston_ticks = String[]
if (ttype = ticksType(ticks)) == :ticks if (ttype = ticksType(ticks)) == :ticks
tick_locs = @view ticks[:] tick_locs = @view ticks[:]
for i eachindex(tick_locs) for i in eachindex(tick_locs)
tick = if maj_min == "m" tick = if maj_min == "m"
"'' $(tick_locs[i]) 1" # see gnuplot manual 'Mxtics' "'' $(tick_locs[i]) 1" # see gnuplot manual 'Mxtics'
else else
@ -432,7 +483,7 @@ function gaston_set_ticks!(axesconf, ticks, letter, maj_min, add)
elseif ttype == :ticks_and_labels elseif ttype == :ticks_and_labels
tick_locs = @view ticks[1][:] tick_locs = @view ticks[1][:]
tick_labels = @view ticks[2][:] tick_labels = @view ticks[2][:]
for i eachindex(tick_locs) for i in eachindex(tick_locs)
lab = gaston_enclose_tick_string(tick_labels[i]) lab = gaston_enclose_tick_string(tick_labels[i])
push!(gaston_ticks, "'$lab' $(tick_locs[i])") push!(gaston_ticks, "'$lab' $(tick_locs[i])")
end end
@ -451,8 +502,11 @@ function gaston_set_legend!(axesconf, sp, any_label)
if sp[:legend] (:none, :inline) && any_label if sp[:legend] (:none, :inline) && any_label
leg == :best && (leg = :topright) leg == :best && (leg = :topright)
push!(axesconf, "set key " * (occursin("outer", string(leg)) ? "outside" : "inside")) push!(
for position ("top", "bottom", "left", "right") axesconf,
"set key " * (occursin("outer", string(leg)) ? "outside" : "inside"),
)
for position in ("top", "bottom", "left", "right")
occursin(position, string(leg)) && push!(axesconf, "set key $position") occursin(position, string(leg)) && push!(axesconf, "set key $position")
end end
push!(axesconf, "set key $(gaston_font(legendfont(sp), rot=false, align=false))") push!(axesconf, "set key $(gaston_font(legendfont(sp), rot=false, align=false))")
@ -472,8 +526,8 @@ end
# Helpers # Helpers
# -------------------------------------------- # --------------------------------------------
gaston_halign(k) = (left=:left, hcenter=:center, right=:right)[k] gaston_halign(k) = (left = :left, hcenter = :center, right = :right)[k]
gaston_valign(k) = (top=:top, vcenter=:center, bottom=:bottom)[k] gaston_valign(k) = (top = :top, vcenter = :center, bottom = :bottom)[k]
gaston_alpha(alpha) = alpha === nothing ? 0 : alpha gaston_alpha(alpha) = alpha === nothing ? 0 : alpha
@ -489,7 +543,7 @@ gaston_mk_ms_mc(series::Series, clims, i::Int) = (
gaston_color(get_markercolor(series, clims, i), get_markeralpha(series, i)), gaston_color(get_markercolor(series, clims, i), get_markeralpha(series, i)),
) )
function gaston_font(f; rot=true, align=true, color=true, scale=1) function gaston_font(f; rot = true, align = true, color = true, scale = 1)
font = String["font '$(f.family),$(round(Int, scale * f.pointsize))'"] font = String["font '$(f.family),$(round(Int, scale * f.pointsize))'"]
align && push!(font, "$(gaston_halign(f.halign))") align && push!(font, "$(gaston_halign(f.halign))")
rot && push!(font, "rotate by $(f.rotation)") rot && push!(font, "rotate by $(f.rotation)")
@ -498,8 +552,9 @@ function gaston_font(f; rot=true, align=true, color=true, scale=1)
end end
function gaston_palette(gradient) function gaston_palette(gradient)
palette = String[]; n = -1 palette = String[]
for rgba gradient # FIXME: naive conversion, inefficient ? n = -1
for rgba in gradient # FIXME: naive conversion, inefficient ?
push!(palette, "$(n += 1) $(rgba.r) $(rgba.g) $(rgba.b)") push!(palette, "$(n += 1) $(rgba.r) $(rgba.g) $(rgba.b)")
end end
return '(' * join(palette, ", ") * ')' return '(' * join(palette, ", ") * ')'
@ -524,7 +579,7 @@ function gaston_marker(marker, alpha)
return 1 return 1
end end
function gaston_color(col, alpha=0) function gaston_color(col, alpha = 0)
col = single_color(col) # in case of gradients col = single_color(col) # in case of gradients
col = alphacolor(col, gaston_alpha(alpha)) # add a default alpha if non existent col = alphacolor(col, gaston_alpha(alpha)) # add a default alpha if non existent
return "rgb '#$(hex(col, :aarrggbb))'" return "rgb '#$(hex(col, :aarrggbb))'"

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,6 @@ Read from .hdf5 file using:
display(pread) display(pread)
==# ==#
#==TODO #==TODO
=============================================================================== ===============================================================================
1. Support more features. 1. Support more features.
@ -56,7 +55,8 @@ import ..HDF5PLOT_MAP_STR2TELEM, ..HDF5PLOT_MAP_TELEM2STR
import ..HDF5Plot_PlotRef, ..HDF5PLOT_PLOTREF import ..HDF5Plot_PlotRef, ..HDF5PLOT_PLOTREF
import ..BoundingBox, ..Extrema, ..Length import ..BoundingBox, ..Extrema, ..Length
import ..RecipesPipeline.datetimeformatter import ..RecipesPipeline.datetimeformatter
import ..PlotUtils.ColorPalette, ..PlotUtils.CategoricalColorGradient, ..PlotUtils.ContinuousColorGradient import ..PlotUtils.ColorPalette,
..PlotUtils.CategoricalColorGradient, ..PlotUtils.ContinuousColorGradient
import ..Surface, ..Shape, ..Arrow import ..Surface, ..Shape, ..Arrow
import ..GridLayout, ..RootLayout import ..GridLayout, ..RootLayout
import ..Font, ..PlotText, ..SeriesAnnotations import ..Font, ..PlotText, ..SeriesAnnotations
@ -66,14 +66,13 @@ import .._axis_defaults
import ..plot, ..plot! import ..plot, ..plot!
#Types that already have built-in HDF5 support (just write out natively): #Types that already have built-in HDF5 support (just write out natively):
const HDF5_SupportedTypes = Union{Number, String} const HDF5_SupportedTypes = Union{Number,String}
#TODO: Types_HDF5Support #TODO: Types_HDF5Support
#Dispatch types: #Dispatch types:
struct CplxTuple; end #Identifies a "complex" tuple structure (not merely numbers) struct CplxTuple end #Identifies a "complex" tuple structure (not merely numbers)
#HDF5 reader will auto-detect type correctly: #HDF5 reader will auto-detect type correctly:
struct HDF5_AutoDetect; end #See HDF5_SupportedTypes struct HDF5_AutoDetect end #See HDF5_SupportedTypes
#== #==
===============================================================================# ===============================================================================#
@ -82,13 +81,12 @@ if length(HDF5PLOT_MAP_TELEM2STR) < 1
#Possible element types of high-level data types: #Possible element types of high-level data types:
#(Used to add type information as an HDF5 string attribute) #(Used to add type information as an HDF5 string attribute)
#(Also used to dispatch appropriate read function through _read_typed()) #(Also used to dispatch appropriate read function through _read_typed())
_telem2str = Dict{String, Type}( _telem2str = Dict{String,Type}(
"NOTHING" => Nothing, "NOTHING" => Nothing,
"SYMBOL" => Symbol, "SYMBOL" => Symbol,
"RGBA" => Colorant, #Write out any Colorant to an #RRGGBBAA string "RGBA" => Colorant, #Write out any Colorant to an #RRGGBBAA string
"TUPLE" => Tuple, "TUPLE" => Tuple,
"CTUPLE" => CplxTuple, #Tuple of complex structures "CTUPLE" => CplxTuple,
"EXTREMA" => Extrema, "EXTREMA" => Extrema,
"LENGTH" => Length, "LENGTH" => Length,
"ARRAY" => Array, #Array{Any} (because Array{T<:Union{Number, String}} natively supported by HDF5) "ARRAY" => Array, #Array{Any} (because Array{T<:Union{Number, String}} natively supported by HDF5)
@ -115,10 +113,12 @@ if length(HDF5PLOT_MAP_TELEM2STR) < 1
"SUBPLOT" => Subplot, "SUBPLOT" => Subplot,
) )
merge!(HDF5PLOT_MAP_STR2TELEM, _telem2str) #Faster to create than push!()?? merge!(HDF5PLOT_MAP_STR2TELEM, _telem2str) #Faster to create than push!()??
merge!(HDF5PLOT_MAP_TELEM2STR, Dict{Type, String}(v=>k for (k,v) in HDF5PLOT_MAP_STR2TELEM)) merge!(
HDF5PLOT_MAP_TELEM2STR,
Dict{Type,String}(v => k for (k, v) in HDF5PLOT_MAP_STR2TELEM),
)
end end
#==Helper functions #==Helper functions
===============================================================================# ===============================================================================#
@ -151,19 +151,18 @@ function _hdf5_merge!(dest::AKW, src::AKW)
end end
#_type_for_map returns the type to use with HDF5PLOT_MAP_TELEM2STR[], in case it is not concrete: #_type_for_map returns the type to use with HDF5PLOT_MAP_TELEM2STR[], in case it is not concrete:
_type_for_map(::Type{T}) where T = T #Catch-all _type_for_map(::Type{T}) where {T} = T #Catch-all
_type_for_map(::Type{T}) where T<:BoundingBox = BoundingBox _type_for_map(::Type{T}) where {T<:BoundingBox} = BoundingBox
_type_for_map(::Type{T}) where T<:ColorScheme = ColorScheme _type_for_map(::Type{T}) where {T<:ColorScheme} = ColorScheme
_type_for_map(::Type{T}) where T<:Surface = Surface _type_for_map(::Type{T}) where {T<:Surface} = Surface
#==Read/write things like type name in attributes #==Read/write things like type name in attributes
===============================================================================# ===============================================================================#
function _write_datatype_attr(ds::Union{Group, Dataset}, ::Type{T}) where T function _write_datatype_attr(ds::Union{Group,Dataset}, ::Type{T}) where {T}
typestr = HDF5PLOT_MAP_TELEM2STR[T] typestr = HDF5PLOT_MAP_TELEM2STR[T]
HDF5.attributes(ds)["TYPE"] = typestr HDF5.attributes(ds)["TYPE"] = typestr
end end
function _read_datatype_attr(ds::Union{Group, Dataset}) function _read_datatype_attr(ds::Union{Group,Dataset})
if !Base.haskey(HDF5.attributes(ds), "TYPE") if !Base.haskey(HDF5.attributes(ds), "TYPE")
return HDF5_AutoDetect return HDF5_AutoDetect
end end
@ -173,7 +172,7 @@ function _read_datatype_attr(ds::Union{Group, Dataset})
end end
#Type parameter attributes: #Type parameter attributes:
function _write_typeparam_attr(ds::Dataset, v::Length{T}) where T function _write_typeparam_attr(ds::Dataset, v::Length{T}) where {T}
HDF5.attributes(ds)["TYPEPARAM"] = string(T) #Need to add units for Length HDF5.attributes(ds)["TYPEPARAM"] = string(T) #Need to add units for Length
end end
_read_typeparam_attr(ds::Dataset) = HDF5.read(HDF5.attributes(ds)["TYPEPARAM"]) _read_typeparam_attr(ds::Dataset) = HDF5.read(HDF5.attributes(ds)["TYPEPARAM"])
@ -186,8 +185,8 @@ _read_length_attr(::Type{Vector}, grp::Group) = HDF5.read(HDF5.attributes(grp)["
function _write_size_attr(grp::Group, v::Array) #of an array function _write_size_attr(grp::Group, v::Array) #of an array
HDF5.attributes(grp)["SIZE"] = [size(v)...] HDF5.attributes(grp)["SIZE"] = [size(v)...]
end end
_read_size_attr(::Type{Array}, grp::Group) = tuple(HDF5.read(HDF5.attributes(grp)["SIZE"])...) _read_size_attr(::Type{Array}, grp::Group) =
tuple(HDF5.read(HDF5.attributes(grp)["SIZE"])...)
#==_write_typed(): Simple (leaf) datatypes. (Labels with type name.) #==_write_typed(): Simple (leaf) datatypes. (Labels with type name.)
===============================================================================# ===============================================================================#
@ -228,9 +227,9 @@ function _write_typed(grp::Group, name::String, v::Length)
end end
function _write_typed(grp::Group, name::String, v::typeof(datetimeformatter)) function _write_typed(grp::Group, name::String, v::typeof(datetimeformatter))
grp[name] = string(v) #Just write something that helps reader grp[name] = string(v) #Just write something that helps reader
_write_datatype_attr(grp[name], typeof(datetimeformatter)) _write_datatype_attr(grp[name], typeof(datetimeformatter))
end end
function _write_typed(grp::Group, name::String, v::Array{T}) where T<:Number #Default for arrays function _write_typed(grp::Group, name::String, v::Array{T}) where {T<:Number} #Default for arrays
grp[name] = v grp[name] = v
return #No need to _write_datatype_attr return #No need to _write_datatype_attr
end end
@ -238,8 +237,6 @@ function _write_typed(grp::Group, name::String, v::AbstractRange)
_write_typed(grp, name, collect(v)) #For now _write_typed(grp, name, collect(v)) #For now
end end
#== Helper functions for writing complex data structures #== Helper functions for writing complex data structures
===============================================================================# ===============================================================================#
@ -270,20 +267,19 @@ function _write(grp::Group, name::String, d::AbstractDict)
end end
#Write out arbitrary `struct`s: #Write out arbitrary `struct`s:
function _writestructgeneric(grp::Group, obj::T) where T function _writestructgeneric(grp::Group, obj::T) where {T}
for fname in fieldnames(T) for fname in fieldnames(T)
v = getfield(obj, fname) v = getfield(obj, fname)
_write_typed(grp, String(fname), v) _write_typed(grp, String(fname), v)
end end
return return
end end
#==_write_typed(): More complex structures. (Labels with type name.) #==_write_typed(): More complex structures. (Labels with type name.)
===============================================================================# ===============================================================================#
#Catch-all (default behaviour for `struct`s): #Catch-all (default behaviour for `struct`s):
function _write_typed(grp::Group, name::String, v::T) where T function _write_typed(grp::Group, name::String, v::T) where {T}
#NOTE: need "name" parameter so that call signature is same with built-ins #NOTE: need "name" parameter so that call signature is same with built-ins
MT = _type_for_map(T) MT = _type_for_map(T)
try #Check to see if type is supported try #Check to see if type is supported
@ -299,12 +295,12 @@ function _write_typed(grp::Group, name::String, v::T) where T
_writestructgeneric(objgrp, v) _writestructgeneric(objgrp, v)
end end
function _write_typed(grp::Group, name::String, v::Array{T}) where T function _write_typed(grp::Group, name::String, v::Array{T}) where {T}
_write_harray(grp, name, v) _write_harray(grp, name, v)
_write_datatype_attr(grp[name], Array) #{Any} _write_datatype_attr(grp[name], Array) #{Any}
end end
function _write_typed(grp::Group, name::String, v::Tuple, ::Type{ELT}) where ELT<: Number #Basic Tuple function _write_typed(grp::Group, name::String, v::Tuple, ::Type{ELT}) where {ELT<:Number} #Basic Tuple
_write_typed(grp, name, [v...]) _write_typed(grp, name, [v...])
_write_datatype_attr(grp[name], Tuple) _write_datatype_attr(grp[name], Tuple)
end end
@ -315,12 +311,12 @@ end
_write_typed(grp::Group, name::String, v::Tuple) = _write_typed(grp, name, v, eltype(v)) _write_typed(grp::Group, name::String, v::Tuple) = _write_typed(grp, name, v, eltype(v))
function _write_typed(grp::Group, name::String, v::Dict) function _write_typed(grp::Group, name::String, v::Dict)
#= #=
tstr = string(Dict) tstr = string(Dict)
path = HDF5.name(grp) * "/" * name path = HDF5.name(grp) * "/" * name
@info("Type not supported: $tstr\npath: $path") @info("Type not supported: $tstr\npath: $path")
return return
=# =#
#No support for structures with Dicts yet #No support for structures with Dicts yet
end end
function _write_typed(grp::Group, name::String, d::DefaultsDict) #Typically for plot attributes function _write_typed(grp::Group, name::String, d::DefaultsDict) #Typically for plot attributes
@ -347,7 +343,6 @@ function _write_typed(grp::Group, name::String, v::Plot)
#Don't write plot references #Don't write plot references
end end
#==_write(): Write out more complex structures #==_write(): Write out more complex structures
NOTE: No need to write out type information (inferred from hierarchy) NOTE: No need to write out type information (inferred from hierarchy)
===============================================================================# ===============================================================================#
@ -378,7 +373,11 @@ function _write(grp::Group, plt::Plot{HDF5Backend})
return return
end end
function hdf5plot_write(plt::Plot{HDF5Backend}, path::AbstractString; name::String="_unnamed") function hdf5plot_write(
plt::Plot{HDF5Backend},
path::AbstractString;
name::String = "_unnamed",
)
HDF5.h5open(path, "w") do file HDF5.h5open(path, "w") do file
HDF5.write_dataset(file, "VERSION_INFO", _get_Plots_versionstr()) HDF5.write_dataset(file, "VERSION_INFO", _get_Plots_versionstr())
grp = HDF5.create_group(file, h5plotpath(name)) grp = HDF5.create_group(file, h5plotpath(name))
@ -386,7 +385,6 @@ function hdf5plot_write(plt::Plot{HDF5Backend}, path::AbstractString; name::Stri
end end
end end
#== _read(): Read data, but not type information. #== _read(): Read data, but not type information.
===============================================================================# ===============================================================================#
@ -417,7 +415,6 @@ function _read(::Type{Length}, ds::Dataset)
end end
_read(::Type{typeof(datetimeformatter)}, ds::Dataset) = datetimeformatter _read(::Type{typeof(datetimeformatter)}, ds::Dataset) = datetimeformatter
#== Helper functions for reading in complex data structures #== Helper functions for reading in complex data structures
===============================================================================# ===============================================================================#
@ -429,7 +426,7 @@ function _read_typed(grp::Group, name::String)
end end
#_readstructgeneric: Needs object values to be written out with _write_typed(): #_readstructgeneric: Needs object values to be written out with _write_typed():
function _readstructgeneric(::Type{T}, grp::Group) where T function _readstructgeneric(::Type{T}, grp::Group) where {T}
vlist = Array{Any}(nothing, fieldcount(T)) vlist = Array{Any}(nothing, fieldcount(T))
for (i, fname) in enumerate(fieldnames(T)) for (i, fname) in enumerate(fieldnames(T))
vlist[i] = _read_typed(grp, String(fname)) vlist[i] = _read_typed(grp, String(fname))
@ -454,7 +451,6 @@ function _read(::Type{KW}, grp::Group)
return d return d
end end
#== _read(): More complex structures. #== _read(): More complex structures.
===============================================================================# ===============================================================================#
@ -463,7 +459,9 @@ _read(T::Type, grp::Group) = _readstructgeneric(T, grp)
function _read(::Type{Array}, grp::Group) #Array{Any} function _read(::Type{Array}, grp::Group) #Array{Any}
sz = _read_size_attr(Array, grp) sz = _read_size_attr(Array, grp)
if tuple(0) == sz; return []; end if tuple(0) == sz
return []
end
result = Array{Any}(undef, sz) result = Array{Any}(undef, sz)
lidx = LinearIndices(sz) lidx = LinearIndices(sz)
@ -498,7 +496,9 @@ function _read(::Type{DefaultsDict}, grp::Group)
#User should set DefaultsDict.defaults to one of: #User should set DefaultsDict.defaults to one of:
# _plot_defaults, _subplot_defaults, _axis_defaults, _series_defaults # _plot_defaults, _subplot_defaults, _axis_defaults, _series_defaults
path = HDF5.name(ds) path = HDF5.name(ds)
@warn("Cannot yet read DefaultsDict using _read_typed():\n $path\nCannot fully reconstruct plot.") @warn(
"Cannot yet read DefaultsDict using _read_typed():\n $path\nCannot fully reconstruct plot."
)
end end
function _read(::Type{Axis}, grp::Group) function _read(::Type{Axis}, grp::Group)
#1st arg appears to be ref to subplots. Seems to work without it. #1st arg appears to be ref to subplots. Seems to work without it.
@ -510,7 +510,6 @@ function _read(::Type{Subplot}, grp::Group)
return HDF5PLOT_PLOTREF.ref.subplots[idx] return HDF5PLOT_PLOTREF.ref.subplots[idx]
end end
#== _read(): Main plot structures #== _read(): Main plot structures
===============================================================================# ===============================================================================#
@ -538,7 +537,7 @@ function _read_plot(grp::Group)
n = _read_length_attr(Vector, listgrp) n = _read_length_attr(Vector, listgrp)
#Construct new plot, +allocate subplots: #Construct new plot, +allocate subplots:
plt = plot(layout=n) plt = plot(layout = n)
HDF5PLOT_PLOTREF.ref = plt #Used when reading "layout" HDF5PLOT_PLOTREF.ref = plt #Used when reading "layout"
agrp = HDF5.open_group(grp, "attr") agrp = HDF5.open_group(grp, "attr")
@ -552,17 +551,15 @@ function _read_plot(grp::Group)
return plt return plt
end end
function hdf5plot_read(path::AbstractString; name::String="_unnamed") function hdf5plot_read(path::AbstractString; name::String = "_unnamed")
HDF5.h5open(path, "r") do file HDF5.h5open(path, "r") do file
grp = HDF5.open_group(file, h5plotpath("_unnamed")) grp = HDF5.open_group(file, h5plotpath("_unnamed"))
return _read_plot(grp) return _read_plot(grp)
end end
end end
end #module _hdf5_implementation end #module _hdf5_implementation
#==Implement Plots.jl backend interface for HDF5Backend #==Implement Plots.jl backend interface for HDF5Backend
===============================================================================# ===============================================================================#
@ -640,7 +637,8 @@ end
#==Interface actually required to use HDF5Backend #==Interface actually required to use HDF5Backend
===============================================================================# ===============================================================================#
hdf5plot_write(plt::Plot{HDF5Backend}, path::AbstractString) = _hdf5_implementation.hdf5plot_write(plt, path) hdf5plot_write(plt::Plot{HDF5Backend}, path::AbstractString) =
_hdf5_implementation.hdf5plot_write(plt, path)
hdf5plot_write(path::AbstractString) = _hdf5_implementation.hdf5plot_write(current(), path) hdf5plot_write(path::AbstractString) = _hdf5_implementation.hdf5plot_write(current(), path)
hdf5plot_read(path::AbstractString) = _hdf5_implementation.hdf5plot_read(path) hdf5plot_read(path::AbstractString) = _hdf5_implementation.hdf5plot_read(path)

View File

@ -44,16 +44,16 @@ _inspectdr_mapcolor(v::Colorant) = v
function _inspectdr_mapcolor(g::PlotUtils.ColorGradient) function _inspectdr_mapcolor(g::PlotUtils.ColorGradient)
@warn("Color gradients are currently unsupported in InspectDR.") @warn("Color gradients are currently unsupported in InspectDR.")
#Pick middle color: #Pick middle color:
_inspectdr_mapcolor(g.colors[div(1+end,2)]) _inspectdr_mapcolor(g.colors[div(1 + end, 2)])
end end
function _inspectdr_mapcolor(v::AVec) function _inspectdr_mapcolor(v::AVec)
@warn("Vectors of colors are currently unsupported in InspectDR.") @warn("Vectors of colors are currently unsupported in InspectDR.")
#Pick middle color: #Pick middle color:
_inspectdr_mapcolor(v[div(1+end,2)]) _inspectdr_mapcolor(v[div(1 + end, 2)])
end end
#Hack: suggested point size does not seem adequate relative to plot size, for some reason. #Hack: suggested point size does not seem adequate relative to plot size, for some reason.
_inspectdr_mapptsize(v) = 1.5*v _inspectdr_mapptsize(v) = 1.5 * v
function _inspectdr_add_annotations(plot, x, y, val) function _inspectdr_add_annotations(plot, x, y, val)
#What kind of annotation is this? #What kind of annotation is this?
@ -61,14 +61,21 @@ end
#plot::InspectDR.Plot2D #plot::InspectDR.Plot2D
function _inspectdr_add_annotations(plot, x, y, val::PlotText) function _inspectdr_add_annotations(plot, x, y, val::PlotText)
vmap = Dict{Symbol, Symbol}(:top=>:t, :bottom=>:b) #:vcenter vmap = Dict{Symbol,Symbol}(:top => :t, :bottom => :b) #:vcenter
hmap = Dict{Symbol, Symbol}(:left=>:l, :right=>:r) #:hcenter hmap = Dict{Symbol,Symbol}(:left => :l, :right => :r) #:hcenter
align = Symbol(get(vmap, val.font.valign, :c), get(hmap, val.font.halign, :c)) align = Symbol(get(vmap, val.font.valign, :c), get(hmap, val.font.halign, :c))
fnt = InspectDR.Font(val.font.family, val.font.pointsize, fnt = InspectDR.Font(
color =_inspectdr_mapcolor(val.font.color) val.font.family,
val.font.pointsize,
color = _inspectdr_mapcolor(val.font.color),
) )
ann = InspectDR.atext(val.str, x=x, y=y, ann = InspectDR.atext(
font=fnt, angle=val.font.rotation, align=align val.str,
x = x,
y = y,
font = fnt,
angle = val.font.rotation,
align = align,
) )
InspectDR.add(plot, ann) InspectDR.add(plot, ann)
return return
@ -84,7 +91,9 @@ function _inspectdr_getaxisticks(ticks, gridlines, xfrm)
if ticks == :native if ticks == :native
#keep current #keep current
elseif ttype == :ticks_and_labels elseif ttype == :ticks_and_labels
pos = ticks[1]; labels = ticks[2]; nticks = length(ticks[1]) pos = ticks[1]
labels = ticks[2]
nticks = length(ticks[1])
newticks = TickCustom[TickCustom(_xfrm(pos[i]), labels[i]) for i in 1:nticks] newticks = TickCustom[TickCustom(_xfrm(pos[i]), labels[i]) for i in 1:nticks]
gridlines = InspectDR.GridLinesCustom(gridlines) gridlines = InspectDR.GridLinesCustom(gridlines)
gridlines.major = newticks gridlines.major = newticks
@ -129,8 +138,8 @@ end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
function _inspectdr_getscale(s::Symbol, yaxis::Bool) function _inspectdr_getscale(s::Symbol, yaxis::Bool)
#TODO: Support :asinh, :sqrt #TODO: Support :asinh, :sqrt
kwargs = yaxis ? (:tgtmajor=>8, :tgtminor=>2) : () #More grid lines on y-axis kwargs = yaxis ? (:tgtmajor => 8, :tgtminor => 2) : () #More grid lines on y-axis
if :log2 == s if :log2 == s
return InspectDR.AxisScale(:log2; kwargs...) return InspectDR.AxisScale(:log2; kwargs...)
elseif :log10 == s elseif :log10 == s
@ -145,13 +154,12 @@ end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
#Glyph used when plotting "Shape"s: #Glyph used when plotting "Shape"s:
INSPECTDR_GLYPH_SHAPE = InspectDR.GlyphPolyline( INSPECTDR_GLYPH_SHAPE =
2*InspectDR.GLYPH_SQUARE.x, InspectDR.GLYPH_SQUARE.y InspectDR.GlyphPolyline(2 * InspectDR.GLYPH_SQUARE.x, InspectDR.GLYPH_SQUARE.y)
)
mutable struct InspecDRPlotRef mutable struct InspecDRPlotRef
mplot::Union{Nothing, InspectDR.Multiplot} mplot::Union{Nothing,InspectDR.Multiplot}
gui::Union{Nothing, InspectDR.GtkPlot} gui::Union{Nothing,InspectDR.GtkPlot}
end end
_inspectdr_getmplot(::Any) = nothing _inspectdr_getmplot(::Any) = nothing
@ -200,7 +208,9 @@ end
function _initialize_subplot(plt::Plot{InspectDRBackend}, sp::Subplot{InspectDRBackend}) function _initialize_subplot(plt::Plot{InspectDRBackend}, sp::Subplot{InspectDRBackend})
plot = sp.o plot = sp.o
#Don't do anything without a "subplot" object: Will process later. #Don't do anything without a "subplot" object: Will process later.
if nothing == plot; return; end if nothing == plot
return
end
plot.data = [] plot.data = []
plot.userannot = [] #Clear old markers/text annotation/polyline "annotation" plot.userannot = [] #Clear old markers/text annotation/polyline "annotation"
return plot return plot
@ -219,7 +229,9 @@ function _series_added(plt::Plot{InspectDRBackend}, series::Series)
clims = get_clims(sp, series) clims = get_clims(sp, series)
#Don't do anything without a "subplot" object: Will process later. #Don't do anything without a "subplot" object: Will process later.
if nothing == plot; return; end if nothing == plot
return
end
_vectorize(v) = isa(v, Vector) ? v : collect(v) #InspectDR only supports vectors _vectorize(v) = isa(v, Vector) ? v : collect(v) #InspectDR only supports vectors
x, y = if st == :straightline x, y = if st == :straightline
@ -230,30 +242,33 @@ function _series_added(plt::Plot{InspectDRBackend}, series::Series)
#No support for polar grid... but can still perform polar transformation: #No support for polar grid... but can still perform polar transformation:
if ispolar(sp) if ispolar(sp)
Θ = x; r = y Θ = x
x = r.*cos.(Θ); y = r.*sin.(Θ) r = y
x = r .* cos.(Θ)
y = r .* sin.(Θ)
end end
# doesn't handle mismatched x/y - wrap data (pyplot behaviour): # doesn't handle mismatched x/y - wrap data (pyplot behaviour):
nx = length(x); ny = length(y) nx = length(x)
ny = length(y)
if nx < ny if nx < ny
series[:x] = Float64[x[mod1(i,nx)] for i=1:ny] series[:x] = Float64[x[mod1(i, nx)] for i in 1:ny]
elseif ny > nx elseif ny > nx
series[:y] = Float64[y[mod1(i,ny)] for i=1:nx] series[:y] = Float64[y[mod1(i, ny)] for i in 1:nx]
end end
#= TODO: Eventually support #= TODO: Eventually support
series[:fillcolor] #I think this is fill under line series[:fillcolor] #I think this is fill under line
zorder = series[:series_plotindex] zorder = series[:series_plotindex]
For st in :shape: For st in :shape:
zorder = series[:series_plotindex], zorder = series[:series_plotindex],
=# =#
if st in (:shape,) if st in (:shape,)
x, y = shape_data(series) x, y = shape_data(series)
nmax = 0 nmax = 0
for (i,rng) in enumerate(iter_segments(x, y)) for (i, rng) in enumerate(iter_segments(x, y))
nmax = i nmax = i
if length(rng) > 1 if length(rng) > 1
linewidth = series[:linewidth] linewidth = series[:linewidth]
@ -261,11 +276,12 @@ For st in :shape:
linecolor = _inspectdr_mapcolor(_cycle(c, i)) linecolor = _inspectdr_mapcolor(_cycle(c, i))
c = plot_color(get_fillcolor(series), get_fillalpha(series)) c = plot_color(get_fillcolor(series), get_fillalpha(series))
fillcolor = _inspectdr_mapcolor(_cycle(c, i)) fillcolor = _inspectdr_mapcolor(_cycle(c, i))
line = InspectDR.line( line = InspectDR.line(style = :solid, width = linewidth, color = linecolor)
style=:solid, width=linewidth, color=linecolor
)
apline = InspectDR.PolylineAnnotation( apline = InspectDR.PolylineAnnotation(
x[rng], y[rng], line=line, fillcolor=fillcolor x[rng],
y[rng],
line = line,
fillcolor = fillcolor,
) )
InspectDR.add(plot, apline) InspectDR.add(plot, apline)
end end
@ -278,21 +294,24 @@ For st in :shape:
linecolor = _inspectdr_mapcolor(_cycle(c, i)) linecolor = _inspectdr_mapcolor(_cycle(c, i))
c = plot_color(get_fillcolor(series), get_fillalpha(series)) c = plot_color(get_fillcolor(series), get_fillalpha(series))
fillcolor = _inspectdr_mapcolor(_cycle(c, i)) fillcolor = _inspectdr_mapcolor(_cycle(c, i))
wfrm = InspectDR.add(plot, Float64[], Float64[], id=series[:label]) wfrm = InspectDR.add(plot, Float64[], Float64[], id = series[:label])
wfrm.line = InspectDR.line( wfrm.line = InspectDR.line(
style=:none, width=linewidth, #linewidth affects glyph style = :none,
width = linewidth, #linewidth affects glyph
) )
wfrm.glyph = InspectDR.glyph( wfrm.glyph = InspectDR.glyph(
shape = INSPECTDR_GLYPH_SHAPE, size = 8, shape = INSPECTDR_GLYPH_SHAPE,
color = linecolor, fillcolor = fillcolor size = 8,
color = linecolor,
fillcolor = fillcolor,
) )
end end
elseif st in (:path, :scatter, :straightline) #, :steppre, :stepmid, :steppost) elseif st in (:path, :scatter, :straightline) #, :steppre, :stepmid, :steppost)
#NOTE: In Plots.jl, :scatter plots have 0-linewidths (I think). #NOTE: In Plots.jl, :scatter plots have 0-linewidths (I think).
linewidth = series[:linewidth] linewidth = series[:linewidth]
#More efficient & allows some support for markerstrokewidth: #More efficient & allows some support for markerstrokewidth:
_style = (0==linewidth ? :none : series[:linestyle]) _style = (0 == linewidth ? :none : series[:linestyle])
wfrm = InspectDR.add(plot, x, y, id=series[:label]) wfrm = InspectDR.add(plot, x, y, id = series[:label])
wfrm.line = InspectDR.line( wfrm.line = InspectDR.line(
style = _style, style = _style,
width = series[:linewidth], width = series[:linewidth],
@ -306,14 +325,18 @@ For st in :shape:
wfrm.glyph = InspectDR.glyph( wfrm.glyph = InspectDR.glyph(
shape = _inspectdr_mapglyph(series[:markershape]), shape = _inspectdr_mapglyph(series[:markershape]),
size = _inspectdr_mapglyphsize(series[:markersize]), size = _inspectdr_mapglyphsize(series[:markersize]),
color = _inspectdr_mapcolor(plot_color(get_markerstrokecolor(series), get_markerstrokealpha(series))), color = _inspectdr_mapcolor(
fillcolor = _inspectdr_mapcolor(plot_color(get_markercolor(series, clims), get_markeralpha(series))), plot_color(get_markerstrokecolor(series), get_markerstrokealpha(series)),
),
fillcolor = _inspectdr_mapcolor(
plot_color(get_markercolor(series, clims), get_markeralpha(series)),
),
) )
end end
# this is all we need to add the series_annotations text # this is all we need to add the series_annotations text
anns = series[:series_annotations] anns = series[:series_annotations]
for (xi,yi,str,fnt) in EachAnn(anns, x, y) for (xi, yi, str, fnt) in EachAnn(anns, x, y)
_inspectdr_add_annotations(plot, xi, yi, PlotText(str, fnt)) _inspectdr_add_annotations(plot, xi, yi, PlotText(str, fnt))
end end
return return
@ -333,66 +356,72 @@ function _inspectdr_setupsubplot(sp::Subplot{InspectDRBackend})
plot = sp.o plot = sp.o
strip = plot.strips[1] #Only 1 strip supported with Plots.jl strip = plot.strips[1] #Only 1 strip supported with Plots.jl
xaxis = sp[:xaxis]; yaxis = sp[:yaxis] xaxis = sp[:xaxis]
yaxis = sp[:yaxis]
xgrid_show = xaxis[:grid] xgrid_show = xaxis[:grid]
ygrid_show = yaxis[:grid] ygrid_show = yaxis[:grid]
strip.grid = InspectDR.GridRect( strip.grid = InspectDR.GridRect(
vmajor=xgrid_show, # vminor=xgrid_show, vmajor = xgrid_show, # vminor=xgrid_show,
hmajor=ygrid_show, # hminor=ygrid_show, hmajor = ygrid_show, # hminor=ygrid_show,
) )
plot.xscale = _inspectdr_getscale(xaxis[:scale], false) plot.xscale = _inspectdr_getscale(xaxis[:scale], false)
strip.yscale = _inspectdr_getscale(yaxis[:scale], true) strip.yscale = _inspectdr_getscale(yaxis[:scale], true)
xmin, xmax = axis_limits(sp, :x) xmin, xmax = axis_limits(sp, :x)
ymin, ymax = axis_limits(sp, :y) ymin, ymax = axis_limits(sp, :y)
if ispolar(sp) if ispolar(sp)
#Plots.jl appears to give (xmin,xmax) ≜ (Θmin,Θmax) & (ymin,ymax) ≜ (rmin,rmax) #Plots.jl appears to give (xmin,xmax) ≜ (Θmin,Θmax) & (ymin,ymax) ≜ (rmin,rmax)
rmax = NaNMath.max(abs(ymin), abs(ymax)) rmax = NaNMath.max(abs(ymin), abs(ymax))
xmin, xmax = -rmax, rmax xmin, xmax = -rmax, rmax
ymin, ymax = -rmax, rmax ymin, ymax = -rmax, rmax
end end
plot.xext_full = InspectDR.PExtents1D(xmin, xmax) plot.xext_full = InspectDR.PExtents1D(xmin, xmax)
strip.yext_full = InspectDR.PExtents1D(ymin, ymax) strip.yext_full = InspectDR.PExtents1D(ymin, ymax)
#Set current extents = full extents (needed for _eval(strip.grid,...)) #Set current extents = full extents (needed for _eval(strip.grid,...))
plot.xext = plot.xext_full plot.xext = plot.xext_full
strip.yext = strip.yext_full strip.yext = strip.yext_full
_inspectdr_setticks(sp, plot, strip, xaxis, yaxis) _inspectdr_setticks(sp, plot, strip, xaxis, yaxis)
a = plot.annotation a = plot.annotation
a.title = sp[:title] a.title = sp[:title]
a.xlabel = xaxis[:guide]; a.ylabels = [yaxis[:guide]] a.xlabel = xaxis[:guide]
a.ylabels = [yaxis[:guide]]
#Modify base layout of new object: #Modify base layout of new object:
l = plot.layout.defaults = deepcopy(InspectDR.defaults.plotlayout) l = plot.layout.defaults = deepcopy(InspectDR.defaults.plotlayout)
#IMPORTANT: Must deepcopy to ensure we don't change layouts of other plots. #IMPORTANT: Must deepcopy to ensure we don't change layouts of other plots.
#Works because plot uses defaults (not user-overwritten `layout.values`) #Works because plot uses defaults (not user-overwritten `layout.values`)
l.frame_canvas.fillcolor = _inspectdr_mapcolor(sp[:background_color_subplot]) l.frame_canvas.fillcolor = _inspectdr_mapcolor(sp[:background_color_subplot])
l.frame_data.fillcolor = _inspectdr_mapcolor(sp[:background_color_inside]) l.frame_data.fillcolor = _inspectdr_mapcolor(sp[:background_color_inside])
l.frame_data.line.color = _inspectdr_mapcolor(xaxis[:foreground_color_axis]) l.frame_data.line.color = _inspectdr_mapcolor(xaxis[:foreground_color_axis])
l.font_title = InspectDR.Font(sp[:titlefontfamily], l.font_title = InspectDR.Font(
_inspectdr_mapptsize(sp[:titlefontsize]), sp[:titlefontfamily],
color = _inspectdr_mapcolor(sp[:titlefontcolor]) _inspectdr_mapptsize(sp[:titlefontsize]),
) color = _inspectdr_mapcolor(sp[:titlefontcolor]),
#Cannot independently control fonts of axes with InspectDR: )
l.font_axislabel = InspectDR.Font(xaxis[:guidefontfamily], #Cannot independently control fonts of axes with InspectDR:
_inspectdr_mapptsize(xaxis[:guidefontsize]), l.font_axislabel = InspectDR.Font(
color = _inspectdr_mapcolor(xaxis[:guidefontcolor]) xaxis[:guidefontfamily],
) _inspectdr_mapptsize(xaxis[:guidefontsize]),
l.font_ticklabel = InspectDR.Font(xaxis[:tickfontfamily], color = _inspectdr_mapcolor(xaxis[:guidefontcolor]),
_inspectdr_mapptsize(xaxis[:tickfontsize]), )
color = _inspectdr_mapcolor(xaxis[:tickfontcolor]) l.font_ticklabel = InspectDR.Font(
) xaxis[:tickfontfamily],
l.enable_legend = (sp[:legend] != :none) _inspectdr_mapptsize(xaxis[:tickfontsize]),
#l.halloc_legend = 150 #TODO: compute??? color = _inspectdr_mapcolor(xaxis[:tickfontcolor]),
l.font_legend = InspectDR.Font(sp[:legendfontfamily], )
_inspectdr_mapptsize(sp[:legendfontsize]), l.enable_legend = (sp[:legend] != :none)
color = _inspectdr_mapcolor(sp[:legendfontcolor]) #l.halloc_legend = 150 #TODO: compute???
) l.font_legend = InspectDR.Font(
l.frame_legend.fillcolor = _inspectdr_mapcolor(sp[:background_color_legend]) sp[:legendfontfamily],
_inspectdr_mapptsize(sp[:legendfontsize]),
color = _inspectdr_mapcolor(sp[:legendfontcolor]),
)
l.frame_legend.fillcolor = _inspectdr_mapcolor(sp[:background_color_legend])
#_round!() ensures values use integer spacings (looks better on screen): #_round!() ensures values use integer spacings (looks better on screen):
InspectDR._round!(InspectDR.autofit2font!(l, legend_width=10.0)) #10 "em"s wide InspectDR._round!(InspectDR.autofit2font!(l, legend_width = 10.0)) #10 "em"s wide
return return
end end
@ -400,7 +429,9 @@ end
# for the calcs # for the calcs
function _before_layout_calcs(plt::Plot{InspectDRBackend}) function _before_layout_calcs(plt::Plot{InspectDRBackend})
mplot = _inspectdr_getmplot(plt.o) mplot = _inspectdr_getmplot(plt.o)
if nothing == mplot; return; end if nothing == mplot
return
end
mplot.title = plt[:plot_title] mplot.title = plt[:plot_title]
if "" == mplot.title if "" == mplot.title
@ -409,7 +440,7 @@ function _before_layout_calcs(plt::Plot{InspectDRBackend})
end end
mplot.layout[:frame].fillcolor = _inspectdr_mapcolor(plt[:background_color_outside]) mplot.layout[:frame].fillcolor = _inspectdr_mapcolor(plt[:background_color_outside])
mplot.layout[:frame] = mplot.layout[:frame] #register changes mplot.layout[:frame] = mplot.layout[:frame] #register changes
resize!(mplot.subplots, length(plt.subplots)) resize!(mplot.subplots, length(plt.subplots))
nsubplots = length(plt.subplots) nsubplots = length(plt.subplots)
for (i, sp) in enumerate(plt.subplots) for (i, sp) in enumerate(plt.subplots)
@ -453,17 +484,19 @@ end
# to fit ticks, tick labels, guides, colorbars, etc. # to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot{InspectDRBackend}) function _update_min_padding!(sp::Subplot{InspectDRBackend})
plot = sp.o plot = sp.o
if !isa(plot, InspectDR.Plot2D); return sp.minpad; end if !isa(plot, InspectDR.Plot2D)
return sp.minpad
end
#Computing plotbounds with 0-BoundingBox returns required padding: #Computing plotbounds with 0-BoundingBox returns required padding:
bb = InspectDR.plotbounds(plot.layout.values, InspectDR.BoundingBox(0,0,0,0)) bb = InspectDR.plotbounds(plot.layout.values, InspectDR.BoundingBox(0, 0, 0, 0))
#NOTE: plotbounds always pads for titles, legends, etc. even if not in use. #NOTE: plotbounds always pads for titles, legends, etc. even if not in use.
#TODO: possibly zero-out items not in use?? #TODO: possibly zero-out items not in use??
# add in the user-specified margin to InspectDR padding: # add in the user-specified margin to InspectDR padding:
leftpad = abs(bb.xmin)*px + sp[:left_margin] leftpad = abs(bb.xmin) * px + sp[:left_margin]
toppad = abs(bb.ymin)*px + sp[:top_margin] toppad = abs(bb.ymin) * px + sp[:top_margin]
rightpad = abs(bb.xmax)*px + sp[:right_margin] rightpad = abs(bb.xmax) * px + sp[:right_margin]
bottompad = abs(bb.ymax)*px + sp[:bottom_margin] bottompad = abs(bb.ymax) * px + sp[:bottom_margin]
sp.minpad = (leftpad, toppad, rightpad, bottompad) sp.minpad = (leftpad, toppad, rightpad, bottompad)
end end
@ -472,21 +505,25 @@ end
# Override this to update plot items (title, xlabel, etc), and add annotations (plotattributes[:annotations]) # Override this to update plot items (title, xlabel, etc), and add annotations (plotattributes[:annotations])
function _update_plot_object(plt::Plot{InspectDRBackend}) function _update_plot_object(plt::Plot{InspectDRBackend})
mplot = _inspectdr_getmplot(plt.o) mplot = _inspectdr_getmplot(plt.o)
if nothing == mplot; return; end if nothing == mplot
return
end
mplot.bblist = InspectDR.BoundingBox[] mplot.bblist = InspectDR.BoundingBox[]
for (i, sp) in enumerate(plt.subplots) for (i, sp) in enumerate(plt.subplots)
figw, figh = sp.plt[:size] figw, figh = sp.plt[:size]
pcts = bbox_to_pcts(sp.bbox, figw*px, figh*px) pcts = bbox_to_pcts(sp.bbox, figw * px, figh * px)
_left, _bottom, _width, _height = pcts _left, _bottom, _width, _height = pcts
ymax = 1.0-_bottom ymax = 1.0 - _bottom
ymin = ymax - _height ymin = ymax - _height
bb = InspectDR.BoundingBox(_left, _left+_width, ymin, ymax) bb = InspectDR.BoundingBox(_left, _left + _width, ymin, ymax)
push!(mplot.bblist, bb) push!(mplot.bblist, bb)
end end
gplot = _inspectdr_getgui(plt.o) gplot = _inspectdr_getgui(plt.o)
if nothing == gplot; return; end if nothing == gplot
return
end
gplot.src = mplot #Ensure still references current plot gplot.src = mplot #Ensure still references current plot
InspectDR.refresh(gplot) InspectDR.refresh(gplot)
@ -522,7 +559,9 @@ end
# Display/show the plot (open a GUI window, or browser page, for example). # Display/show the plot (open a GUI window, or browser page, for example).
function _display(plt::Plot{InspectDRBackend}) function _display(plt::Plot{InspectDRBackend})
mplot = _inspectdr_getmplot(plt.o) mplot = _inspectdr_getmplot(plt.o)
if nothing == mplot; return; end if nothing == mplot
return
end
gplot = _inspectdr_getgui(plt.o) gplot = _inspectdr_getgui(plt.o)
if nothing == gplot if nothing == gplot

View File

@ -8,23 +8,11 @@ Base.@kwdef mutable struct PGFPlotsXPlot
function PGFPlotsXPlot(is_created, was_shown, the_plot) function PGFPlotsXPlot(is_created, was_shown, the_plot)
pgfx_plot = new(is_created, was_shown, the_plot) pgfx_plot = new(is_created, was_shown, the_plot)
# tikz libraries # tikz libraries
PGFPlotsX.push_preamble!( PGFPlotsX.push_preamble!(pgfx_plot.the_plot, "\\usetikzlibrary{arrows.meta}")
pgfx_plot.the_plot, PGFPlotsX.push_preamble!(pgfx_plot.the_plot, "\\usetikzlibrary{backgrounds}")
"\\usetikzlibrary{arrows.meta}",
)
PGFPlotsX.push_preamble!(
pgfx_plot.the_plot,
"\\usetikzlibrary{backgrounds}",
)
# pgfplots libraries # pgfplots libraries
PGFPlotsX.push_preamble!( PGFPlotsX.push_preamble!(pgfx_plot.the_plot, "\\usepgfplotslibrary{patchplots}")
pgfx_plot.the_plot, PGFPlotsX.push_preamble!(pgfx_plot.the_plot, "\\usepgfplotslibrary{fillbetween}")
"\\usepgfplotslibrary{patchplots}",
)
PGFPlotsX.push_preamble!(
pgfx_plot.the_plot,
"\\usepgfplotslibrary{fillbetween}",
)
# compatibility fixes # compatibility fixes
# add background layer to standard layers # add background layer to standard layers
PGFPlotsX.push_preamble!( PGFPlotsX.push_preamble!(
@ -89,7 +77,7 @@ function Base.push!(pgfx_plot::PGFPlotsXPlot, item)
end end
function pgfx_split_extra_opts(extra) function pgfx_split_extra_opts(extra)
return (get(extra, :add, nothing), filter( x-> first(x) != :add, extra)) return (get(extra, :add, nothing), filter(x -> first(x) != :add, extra))
end end
function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
if !pgfx_plot.is_created || pgfx_plot.was_shown if !pgfx_plot.is_created || pgfx_plot.was_shown
@ -101,8 +89,9 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
extra_plot = wraptuple(extra_plot) extra_plot = wraptuple(extra_plot)
push!(the_plot, extra_plot...) push!(the_plot, extra_plot...)
end end
bgc = plt.attr[:background_color_outside] == :match ? bgc =
plt.attr[:background_color] : plt.attr[:background_color_outside] plt.attr[:background_color_outside] == :match ? plt.attr[:background_color] :
plt.attr[:background_color_outside]
if bgc isa Colors.Colorant if bgc isa Colors.Colorant
cstr = plot_color(bgc) cstr = plot_color(bgc)
a = alpha(cstr) a = alpha(cstr)
@ -125,9 +114,14 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
if sp[:subplot_index] == plt[:plot_titleindex] if sp[:subplot_index] == plt[:plot_titleindex]
x = dx + sp_width / 2 - 10mm # FIXME: get rid of magic constant x = dx + sp_width / 2 - 10mm # FIXME: get rid of magic constant
y = dy + sp_height / 2 y = dy + sp_height / 2
pgfx_add_annotation!(the_plot, x, y, PlotText(plt[:plot_title], plottitlefont(plt)), pgfx_thickness_scaling(plt); pgfx_add_annotation!(
the_plot,
x,
y,
PlotText(plt[:plot_title], plottitlefont(plt)),
pgfx_thickness_scaling(plt);
cs = "", cs = "",
options = PGFPlotsX.Options("anchor" => "center") options = PGFPlotsX.Options("anchor" => "center"),
) )
continue continue
end end
@ -152,29 +146,22 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
"point meta min" => get_clims(sp)[1], "point meta min" => get_clims(sp)[1],
"title" => sp[:title], "title" => sp[:title],
"title style" => PGFPlotsX.Options( "title style" => PGFPlotsX.Options(
pgfx_get_title_pos(title_loc)..., pgfx_get_title_pos(title_loc)...,
"font" => pgfx_font( "font" => pgfx_font(sp[:titlefontsize], pgfx_thickness_scaling(sp)),
sp[:titlefontsize],
pgfx_thickness_scaling(sp),
),
"color" => title_cstr, "color" => title_cstr,
"draw opacity" => title_a, "draw opacity" => title_a,
"rotate" => sp[:titlefontrotation], "rotate" => sp[:titlefontrotation],
), ),
"legend style" => pgfx_get_legend_style(sp), "legend style" => pgfx_get_legend_style(sp),
"axis background/.style" => PGFPlotsX.Options( "axis background/.style" =>
"fill" => bgc_inside, PGFPlotsX.Options("fill" => bgc_inside, "opacity" => bgc_inside_a),
"opacity" => bgc_inside_a,
),
# These are for layouting # These are for layouting
"anchor" => "north west", "anchor" => "north west",
"xshift" => string(dx), "xshift" => string(dx),
"yshift" => string(-dy), "yshift" => string(-dy),
) )
sp_width > 0 * mm ? push!(axis_opt, "width" => string(axis_width)) : sp_width > 0 * mm ? push!(axis_opt, "width" => string(axis_width)) : nothing
nothing sp_height > 0 * mm ? push!(axis_opt, "height" => string(axis_height)) : nothing
sp_height > 0 * mm ? push!(axis_opt, "height" => string(axis_height)) :
nothing
for letter in (:x, :y, :z) for letter in (:x, :y, :z)
if letter != :z || RecipesPipeline.is3d(sp) if letter != :z || RecipesPipeline.is3d(sp)
pgfx_axis!(axis_opt, sp, letter) pgfx_axis!(axis_opt, sp, letter)
@ -216,11 +203,10 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
if hascolorbar(sp) if hascolorbar(sp)
cticks = get_colorbar_ticks(sp)[2] cticks = get_colorbar_ticks(sp)[2]
colorbar_style = PGFPlotsX.Options( colorbar_style = PGFPlotsX.Options("title" => sp[:colorbar_title])
"title" => sp[:colorbar_title],
)
if sp[:colorbar] === :top if sp[:colorbar] === :top
push!(colorbar_style, push!(
colorbar_style,
"at" => string((0.5, 1.05)), "at" => string((0.5, 1.05)),
"anchor" => "south", "anchor" => "south",
"xtick" => string("{", join(cticks, ","), "}"), "xtick" => string("{", join(cticks, ","), "}"),
@ -228,7 +214,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
"xticklabel style" => pgfx_get_colorbar_ticklabel_style(sp), "xticklabel style" => pgfx_get_colorbar_ticklabel_style(sp),
) )
else else
push!(colorbar_style, push!(
colorbar_style,
"ytick" => string("{", join(cticks, ","), "}"), "ytick" => string("{", join(cticks, ","), "}"),
"yticklabel style" => pgfx_get_colorbar_ticklabel_style(sp), "yticklabel style" => pgfx_get_colorbar_ticklabel_style(sp),
) )
@ -279,15 +266,19 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
"color" => single_color(opt[:linecolor]), "color" => single_color(opt[:linecolor]),
"name path" => string(series_id), "name path" => string(series_id),
) )
extra_series, extra_series_opt = pgfx_split_extra_opts(series[:extra_kwargs]) extra_series, extra_series_opt =
pgfx_split_extra_opts(series[:extra_kwargs])
series_opt = merge(series_opt, PGFPlotsX.Options(extra_series_opt...)) series_opt = merge(series_opt, PGFPlotsX.Options(extra_series_opt...))
if RecipesPipeline.is3d(series) || st in (:heatmap, :contour) || (st == :quiver && opt[:z] !== nothing) if (
RecipesPipeline.is3d(series) ||
st in (:heatmap, :contour) ||
(st == :quiver && opt[:z] !== nothing)
)
series_func = PGFPlotsX.Plot3 series_func = PGFPlotsX.Plot3
else else
series_func = PGFPlotsX.Plot series_func = PGFPlotsX.Plot
end end
if sf !== nothing && if sf !== nothing && !isfilledcontour(series) && series[:ribbon] === nothing
!isfilledcontour(series) && series[:ribbon] === nothing
push!(series_opt, "area legend" => nothing) push!(series_opt, "area legend" => nothing)
end end
pgfx_add_series!(Val(st), axis, series_opt, series, series_func, opt) pgfx_add_series!(Val(st), axis, series_opt, series, series_func, opt)
@ -337,7 +328,7 @@ end
function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, opt) function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, opt)
# treat segments # treat segments
segments = collect(series_segments(series, series[:seriestype]; check=true)) segments = collect(series_segments(series, series[:seriestype]; check = true))
sf = opt[:fillrange] sf = opt[:fillrange]
for (k, segment) in enumerate(segments) for (k, segment) in enumerate(segments)
i, rng = segment.attr_index, segment.range i, rng = segment.attr_index, segment.range
@ -351,10 +342,7 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o
scale_factor = 0.00125 scale_factor = 0.00125
mark_size = opt[:markersize] * scale_factor mark_size = opt[:markersize] * scale_factor
path = join( path = join(
[ ["($(x[i] * mark_size), $(y[i] * mark_size))" for i in eachindex(x)],
"($(x[i] * mark_size), $(y[i] * mark_size))"
for i in eachindex(x)
],
" -- ", " -- ",
) )
c = get_markercolor(series, i) c = get_markercolor(series, i)
@ -372,31 +360,26 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o
segment_opt = merge(segment_opt, pgfx_marker(opt, i)) segment_opt = merge(segment_opt, pgfx_marker(opt, i))
end end
# add fillrange # add fillrange
if sf !== nothing && if sf !== nothing && !isfilledcontour(series)
!isfilledcontour(series) if sf isa Number || sf isa AVec
if sf isa Number || sf isa AVec pgfx_fillrange_series!(axis, series, series_func, i, _cycle(sf, rng), rng)
pgfx_fillrange_series!(
axis,
series,
series_func,
i,
_cycle(sf, rng),
rng,
)
elseif sf isa Tuple && series[:ribbon] !== nothing elseif sf isa Tuple && series[:ribbon] !== nothing
for sfi in sf for sfi in sf
pgfx_fillrange_series!( pgfx_fillrange_series!(
axis, axis,
series, series,
series_func, series_func,
i, i,
_cycle(sfi, rng), _cycle(sfi, rng),
rng, rng,
) )
end end
end end
if k == 1 && if (
series[:subplot][:legend] != :none && pgfx_should_add_to_legend(series) k == 1 &&
series[:subplot][:legend] != :none &&
pgfx_should_add_to_legend(series)
)
pgfx_filllegend!(series_opt, opt) pgfx_filllegend!(series_opt, opt)
end end
end end
@ -405,46 +388,44 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o
if arrow isa Arrow if arrow isa Arrow
arrow_opt = merge( arrow_opt = merge(
segment_opt, segment_opt,
PGFPlotsX.Options("quiver" => PGFPlotsX.Options( PGFPlotsX.Options(
"u" => "\\thisrow{u}", "quiver" => PGFPlotsX.Options(
"v" => "\\thisrow{v}", "u" => "\\thisrow{u}",
pgfx_arrow(arrow, :head) => nothing, "v" => "\\thisrow{v}",
) pgfx_arrow(arrow, :head) => nothing,
) ),
),
) )
if arrow.side == :head if arrow.side == :head
x_arrow = opt[:x][rng][end-1:end] x_arrow = opt[:x][rng][(end - 1):end]
y_arrow = opt[:y][rng][end-1:end] y_arrow = opt[:y][rng][(end - 1):end]
x_path = opt[:x][rng][1:end-1] x_path = opt[:x][rng][1:(end - 1)]
y_path = opt[:y][rng][1:end-1] y_path = opt[:y][rng][1:(end - 1)]
elseif arrow.side == :tail elseif arrow.side == :tail
x_arrow = opt[:x][rng][2:-1:1] x_arrow = opt[:x][rng][2:-1:1]
y_arrow = opt[:y][rng][2:-1:1] y_arrow = opt[:y][rng][2:-1:1]
x_path = opt[:x][rng][2:end] x_path = opt[:x][rng][2:end]
y_path = opt[:y][rng][2:end] y_path = opt[:y][rng][2:end]
elseif arrow.side == :both elseif arrow.side == :both
x_arrow = opt[:x][rng][[2,1,end-1,end]] x_arrow = opt[:x][rng][[2, 1, end - 1, end]]
y_arrow = opt[:y][rng][[2,1,end-1,end]] y_arrow = opt[:y][rng][[2, 1, end - 1, end]]
x_path = opt[:x][rng][2:end-1] x_path = opt[:x][rng][2:(end - 1)]
y_path = opt[:y][rng][2:end-1] y_path = opt[:y][rng][2:(end - 1)]
end end
coordinates = PGFPlotsX.Table([ coordinates = PGFPlotsX.Table([
:x => x_arrow[1:2:end-1], :x => x_arrow[1:2:(end - 1)],
:y => y_arrow[1:2:end-1], :y => y_arrow[1:2:(end - 1)],
:u => [x_arrow[i] - x_arrow[i-1] for i in 2:2:lastindex(x_arrow)], :u => [x_arrow[i] - x_arrow[i - 1] for i in 2:2:lastindex(x_arrow)],
:v => [y_arrow[i] - y_arrow[i-1] for i in 2:2:lastindex(y_arrow)], :v => [y_arrow[i] - y_arrow[i - 1] for i in 2:2:lastindex(y_arrow)],
]) ])
arrow_plot = arrow_plot = series_func(merge(series_opt, arrow_opt), coordinates)
series_func(merge(series_opt, arrow_opt), coordinates)
push!(axis, arrow_plot) push!(axis, arrow_plot)
coordinates = PGFPlotsX.Table(x_path, y_path) coordinates = PGFPlotsX.Table(x_path, y_path)
segment_plot = segment_plot = series_func(merge(series_opt, segment_opt), coordinates)
series_func(merge(series_opt, segment_opt), coordinates)
push!(axis, segment_plot) push!(axis, segment_plot)
else else
coordinates = PGFPlotsX.Table(pgfx_series_arguments(series, opt, rng)...) coordinates = PGFPlotsX.Table(pgfx_series_arguments(series, opt, rng)...)
segment_plot = segment_plot = series_func(merge(series_opt, segment_opt), coordinates)
series_func(merge(series_opt, segment_opt), coordinates)
push!(axis, segment_plot) push!(axis, segment_plot)
end end
# fill between functions # fill between functions
@ -466,7 +447,10 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o
end # for segments end # for segments
# get that last marker # get that last marker
if !isnothing(opt[:y]) && !any(isnan, opt[:y]) && opt[:markershape] isa AVec if !isnothing(opt[:y]) && !any(isnan, opt[:y]) && opt[:markershape] isa AVec
additional_plot = PGFPlotsX.PlotInc(pgfx_marker(opt, length(segments) + 1), PGFPlotsX.Coordinates(tuple((last(opt[:x]), last(opt[:y]))))) additional_plot = PGFPlotsX.PlotInc(
pgfx_marker(opt, length(segments) + 1),
PGFPlotsX.Coordinates(tuple((last(opt[:x]), last(opt[:y])))),
)
push!(axis, additional_plot) push!(axis, additional_plot)
end end
end end
@ -503,9 +487,7 @@ function pgfx_add_series!(::Val{:surface}, axis, series_opt, series, series_func
end end
function pgfx_add_series!(::Val{:wireframe}, axis, series_opt, series, series_func, opt) function pgfx_add_series!(::Val{:wireframe}, axis, series_opt, series, series_func, opt)
push!(series_opt, "mesh" => nothing, push!(series_opt, "mesh" => nothing, "mesh/rows" => length(opt[:x]))
"mesh/rows" => length(opt[:x])
)
pgfx_add_series!(axis, series_opt, series, series_func, opt) pgfx_add_series!(axis, series_opt, series, series_func, opt)
end end
@ -530,12 +512,15 @@ function pgfx_add_series!(::Val{:heatmap}, axis, series_opt, series, series_func
end end
function pgfx_add_series!(::Val{:mesh3d}, axis, series_opt, series, series_func, opt) function pgfx_add_series!(::Val{:mesh3d}, axis, series_opt, series, series_func, opt)
ptable = join([ string(i, " ", j, " ", k, "\\\\") for (i, j, k) in zip(opt[:connections]...) ], "\n ") ptable = join(
[string(i, " ", j, " ", k, "\\\\") for (i, j, k) in zip(opt[:connections]...)],
"\n ",
)
push!( push!(
series_opt, series_opt,
"patch" => nothing, "patch" => nothing,
"table/row sep" => "\\\\", "table/row sep" => "\\\\",
"patch table" => ptable "patch table" => ptable,
) )
pgfx_add_series!(axis, series_opt, series, series_func, opt) pgfx_add_series!(axis, series_opt, series, series_func, opt)
end end
@ -570,9 +555,10 @@ function pgfx_add_series!(::Val{:contour3d}, axis, series_opt, series, series_fu
series_opt, series_opt,
"contour prepared" => PGFPlotsX.Options("labels" => opt[:contour_labels]), "contour prepared" => PGFPlotsX.Options("labels" => opt[:contour_labels]),
) )
series_opt = merge( series_opt, pgfx_linestyle(opt) ) series_opt = merge(series_opt, pgfx_linestyle(opt))
args = pgfx_series_arguments(series, opt) args = pgfx_series_arguments(series, opt)
series_plot = series_func(series_opt, PGFPlotsX.Table(Contour.contours(args..., opt[:levels]))) series_plot =
series_func(series_opt, PGFPlotsX.Table(Contour.contours(args..., opt[:levels])))
push!(axis, series_plot) push!(axis, series_plot)
pgfx_add_legend!(axis, series, opt) pgfx_add_legend!(axis, series, opt)
end end
@ -670,7 +656,8 @@ function pgfx_add_legend!(axis, series, opt, i = 1)
return nothing return nothing
end end
pgfx_series_arguments(series, opt, range) = (arg[range] for arg in pgfx_series_arguments(series, opt)) pgfx_series_arguments(series, opt, range) =
(arg[range] for arg in pgfx_series_arguments(series, opt))
function pgfx_series_arguments(series, opt) function pgfx_series_arguments(series, opt)
st = series[:seriestype] st = series[:seriestype]
return if st in (:contour, :contour3d) return if st in (:contour, :contour3d)
@ -708,7 +695,7 @@ pgfx_get_marker(k) = get(
none = "none", none = "none",
cross = "+", cross = "+",
xcross = "x", xcross = "x",
+ = "+", (+) = "+",
x = "x", x = "x",
utriangle = "triangle*", utriangle = "triangle*",
dtriangle = "triangle*", dtriangle = "triangle*",
@ -734,7 +721,7 @@ pgfx_get_xguide_pos(k) = get(
left = "at={(ticklabel* cs:-0.02)}, anchor=east,", left = "at={(ticklabel* cs:-0.02)}, anchor=east,",
), ),
k, k,
"at={(ticklabel cs:0.5)}, anchor=near ticklabel" "at={(ticklabel cs:0.5)}, anchor=near ticklabel",
) )
pgfx_get_yguide_pos(k) = get( pgfx_get_yguide_pos(k) = get(
@ -744,7 +731,7 @@ pgfx_get_yguide_pos(k) = get(
bottom = "at={(ticklabel* cs:-0.02)}, anchor=north,", bottom = "at={(ticklabel* cs:-0.02)}, anchor=north,",
), ),
k, k,
"at={(ticklabel cs:0.5)}, anchor=near ticklabel" "at={(ticklabel cs:0.5)}, anchor=near ticklabel",
) )
pgfx_get_legend_pos(k) = get( pgfx_get_legend_pos(k) = get(
@ -769,25 +756,27 @@ pgfx_get_legend_pos(k) = get(
Symbol(k), Symbol(k),
("at" => string((1.02, 1)), "anchor" => "north west"), ("at" => string((1.02, 1)), "anchor" => "north west"),
) )
pgfx_get_legend_pos(t::Tuple{S,T}) where {S<:Real,T<:Real} = ("at" => "{$(string(t))}", "anchor" => "north west") pgfx_get_legend_pos(t::Tuple{S,T}) where {S<:Real,T<:Real} =
pgfx_get_legend_pos(nt::NamedTuple) = ("at" => "{$(string(nt.at))}", "anchor" => string(nt.anchor)) ("at" => "{$(string(t))}", "anchor" => "north west")
pgfx_get_legend_pos(theta::Real) = pgfx_get_legend_pos((theta,:inner)) pgfx_get_legend_pos(nt::NamedTuple) =
function pgfx_get_legend_pos(v::Tuple{S,Symbol}) where S <: Real ("at" => "{$(string(nt.at))}", "anchor" => string(nt.anchor))
(s,c) = sincosd(v[1]) pgfx_get_legend_pos(theta::Real) = pgfx_get_legend_pos((theta, :inner))
function pgfx_get_legend_pos(v::Tuple{S,Symbol}) where {S<:Real}
(s, c) = sincosd(v[1])
anchors = [ anchors = [
"south west" "south" "south east"; "south west" "south" "south east"
"west" "center" "east"; "west" "center" "east"
"north west" "north" "north east"; "north west" "north" "north east"
] ]
if v[2] === :inner if v[2] === :inner
rect = (0.07,0.5,1.0,0.07,0.52,1.0) rect = (0.07, 0.5, 1.0, 0.07, 0.52, 1.0)
anchor = anchors[legend_anchor_index(s),legend_anchor_index(c)] anchor = anchors[legend_anchor_index(s), legend_anchor_index(c)]
else else
rect = (-0.15,0.5,1.05,-0.15,0.52,1.1) rect = (-0.15, 0.5, 1.05, -0.15, 0.52, 1.1)
anchor = anchors[4-legend_anchor_index(s),4-legend_anchor_index(c)] anchor = anchors[4 - legend_anchor_index(s), 4 - legend_anchor_index(c)]
end end
return ("at"=>"$(string(legend_pos_from_angle(v[1],rect...)))", "anchor"=>anchor) return ("at" => "$(string(legend_pos_from_angle(v[1],rect...)))", "anchor" => anchor)
end end
function pgfx_get_legend_style(sp) function pgfx_get_legend_style(sp)
@ -805,12 +794,15 @@ function pgfx_get_legend_style(sp)
"fill" => cstr, "fill" => cstr,
"fill opacity" => a, "fill opacity" => a,
"text opacity" => alpha(plot_color(sp[:legendfontcolor])), "text opacity" => alpha(plot_color(sp[:legendfontcolor])),
"font" => pgfx_font( "font" => pgfx_font(sp[:legendfontsize], pgfx_thickness_scaling(sp)),
sp[:legendfontsize],
pgfx_thickness_scaling(sp),
),
"text" => plot_color(sp[:legendfontcolor]), "text" => plot_color(sp[:legendfontcolor]),
"cells" => PGFPlotsX.Options("anchor" => get((left = "west", right = "east", hcenter = "center"), legfont.halign, "west")), "cells" => PGFPlotsX.Options(
"anchor" => get(
(left = "west", right = "east", hcenter = "center"),
legfont.halign,
"west",
),
),
pgfx_get_legend_pos(sp[:legend])..., pgfx_get_legend_pos(sp[:legend])...,
) )
end end
@ -819,21 +811,23 @@ pgfx_get_colorbar_pos(s) =
get((left = " left", bottom = " horizontal", top = " horizontal"), s, "") get((left = " left", bottom = " horizontal", top = " horizontal"), s, "")
pgfx_get_colorbar_pos(b::Bool) = "" pgfx_get_colorbar_pos(b::Bool) = ""
pgfx_get_title_pos(s) = pgfx_get_title_pos(s) = get(
get(( (
left = ("at" => "{(0,1)}", "anchor" => "south west"), left = ("at" => "{(0,1)}", "anchor" => "south west"),
right = ("at" => "{(1,1)}", "anchor" => "south east"), right = ("at" => "{(1,1)}", "anchor" => "south east"),
), s, ),
("at" => "{(0.5,1)}", "anchor" => "south")) s,
("at" => "{(0.5,1)}", "anchor" => "south"),
)
pgfx_get_title_pos(t::Tuple) = ("at" => "{$(string(t))}", "anchor" => "south") pgfx_get_title_pos(t::Tuple) = ("at" => "{$(string(t))}", "anchor" => "south")
pgfx_get_title_pos(nt::NamedTuple) = ("at" => "{$(string(nt.at))}", "anchor" => string(nt.anchor)) pgfx_get_title_pos(nt::NamedTuple) =
("at" => "{$(string(nt.at))}", "anchor" => string(nt.anchor))
function pgfx_get_ticklabel_style(sp, axis) function pgfx_get_ticklabel_style(sp, axis)
cstr = plot_color(axis[:tickfontcolor]) cstr = plot_color(axis[:tickfontcolor])
return PGFPlotsX.Options( return PGFPlotsX.Options(
"font" => pgfx_font( "font" => pgfx_font(axis[:tickfontsize], pgfx_thickness_scaling(sp)),
axis[:tickfontsize], pgfx_thickness_scaling(sp) "color" => cstr,
), "color" => cstr,
"draw opacity" => alpha(cstr), "draw opacity" => alpha(cstr),
"rotate" => axis[:tickfontrotation], "rotate" => axis[:tickfontrotation],
) )
@ -842,9 +836,8 @@ end
function pgfx_get_colorbar_ticklabel_style(sp) function pgfx_get_colorbar_ticklabel_style(sp)
cstr = plot_color(sp[:colorbar_tickfontcolor]) cstr = plot_color(sp[:colorbar_tickfontcolor])
return PGFPlotsX.Options( return PGFPlotsX.Options(
"font" => pgfx_font( "font" => pgfx_font(sp[:colorbar_tickfontsize], pgfx_thickness_scaling(sp)),
sp[:colorbar_tickfontsize], pgfx_thickness_scaling(sp) "color" => cstr,
), "color" => cstr,
"draw opacity" => alpha(cstr), "draw opacity" => alpha(cstr),
"rotate" => sp[:colorbar_tickfontrotation], "rotate" => sp[:colorbar_tickfontrotation],
) )
@ -888,12 +881,20 @@ function pgfx_colormap(v::Vector{<:Colorant})
end, "\n") end, "\n")
end end
function pgfx_colormap(cg::ColorGradient) function pgfx_colormap(cg::ColorGradient)
join(map(1:length(cg)) do i join(
@sprintf("rgb(%.8f)=(%.8f,%.8f,%.8f)", cg.values[i], red(cg.colors[i]), green(cg.colors[i]), blue(cg.colors[i])) map(1:length(cg)) do i
end, "\n") @sprintf(
"rgb(%.8f)=(%.8f,%.8f,%.8f)",
cg.values[i],
red(cg.colors[i]),
green(cg.colors[i]),
blue(cg.colors[i])
)
end,
"\n",
)
end end
function pgfx_framestyle(style::Symbol) function pgfx_framestyle(style::Symbol)
if style in (:box, :axes, :origin, :zerolines, :grid, :none) if style in (:box, :axes, :origin, :zerolines, :grid, :none)
return style return style
@ -950,20 +951,20 @@ end
function pgfx_should_add_to_legend(series::Series) function pgfx_should_add_to_legend(series::Series)
series.plotattributes[:primary] && series.plotattributes[:primary] &&
!( !(
series.plotattributes[:seriestype] in ( series.plotattributes[:seriestype] in (
:hexbin, :hexbin,
:bins2d, :bins2d,
:histogram2d, :histogram2d,
:hline, :hline,
:vline, :vline,
:contour, :contour,
:contourf, :contourf,
:contour3d, :contour3d,
:heatmap, :heatmap,
:image, :image,
)
) )
)
end end
function pgfx_marker(plotattributes, i = 1) function pgfx_marker(plotattributes, i = 1)
@ -980,8 +981,9 @@ function pgfx_marker(plotattributes, i = 1)
pgfx_thickness_scaling(plotattributes) * pgfx_thickness_scaling(plotattributes) *
0.75 * 0.75 *
_cycle(plotattributes[:markersize], i) _cycle(plotattributes[:markersize], i)
mark_freq = !any(isnan, plotattributes[:y]) && plotattributes[:markershape] isa AVec ? mark_freq =
length(plotattributes[:markershape]) : 1 !any(isnan, plotattributes[:y]) && plotattributes[:markershape] isa AVec ?
length(plotattributes[:markershape]) : 1
return PGFPlotsX.Options( return PGFPlotsX.Options(
"mark" => shape isa Shape ? "PlotsShape$i" : pgfx_get_marker(shape), "mark" => shape isa Shape ? "PlotsShape$i" : pgfx_get_marker(shape),
"mark size" => "$mark_size pt", "mark size" => "$mark_size pt",
@ -1004,12 +1006,21 @@ function pgfx_marker(plotattributes, i = 1)
else else
0 0
end, end,
pgfx_get_linestyle(_cycle(plotattributes[:markerstrokestyle], i)) => nothing, pgfx_get_linestyle(_cycle(plotattributes[:markerstrokestyle], i)) =>
nothing,
), ),
) )
end end
function pgfx_add_annotation!(o, x, y, val, thickness_scaling = 1; cs = "axis cs:", options = PGFPlotsX.Options()) function pgfx_add_annotation!(
o,
x,
y,
val,
thickness_scaling = 1;
cs = "axis cs:",
options = PGFPlotsX.Options(),
)
# Construct the style string. # Construct the style string.
cstr = val.font.color cstr = val.font.color
a = alpha(cstr) a = alpha(cstr)
@ -1017,17 +1028,23 @@ function pgfx_add_annotation!(o, x, y, val, thickness_scaling = 1; cs = "axis cs
o, o,
join([ join([
"\\node", "\\node",
sprint(PGFPlotsX.print_tex, merge( sprint(
PGFPlotsX.Options( PGFPlotsX.print_tex,
get((hcenter = "", left = "right", right = "left"), val.font.halign, "") => merge(
nothing, PGFPlotsX.Options(
"color" => cstr, get(
"draw opacity" => convert(Float16, a), (hcenter = "", left = "right", right = "left"),
"rotate" => val.font.rotation, val.font.halign,
"font" => pgfx_font(val.font.pointsize, thickness_scaling), "",
) => nothing,
"color" => cstr,
"draw opacity" => convert(Float16, a),
"rotate" => val.font.rotation,
"font" => pgfx_font(val.font.pointsize, thickness_scaling),
),
options,
), ),
options ),
)),
string(" at (", cs, x, ",", y, ") {", val.str, "};"), string(" at (", cs, x, ",", y, ") {", val.str, "};"),
]), ]),
) )
@ -1097,12 +1114,10 @@ function pgfx_fillrange_series!(axis, series, series_func, i, fillrange, rng)
push!(fillrange_opt, "mark" => "none") # no markers on fillranges push!(fillrange_opt, "mark" => "none") # no markers on fillranges
push!(fillrange_opt, "forget plot" => nothing) push!(fillrange_opt, "forget plot" => nothing)
opt = series.plotattributes opt = series.plotattributes
args = RecipesPipeline.is3d(series) ? (opt[:x][rng], opt[:y][rng], opt[:z][rng]) : args =
RecipesPipeline.is3d(series) ? (opt[:x][rng], opt[:y][rng], opt[:z][rng]) :
(opt[:x][rng], opt[:y][rng]) (opt[:x][rng], opt[:y][rng])
push!( push!(axis, PGFPlotsX.PlotInc(fillrange_opt, pgfx_fillrange_args(fillrange, args...)))
axis,
PGFPlotsX.PlotInc(fillrange_opt, pgfx_fillrange_args(fillrange, args...)),
)
return axis return axis
end end
@ -1132,8 +1147,8 @@ function pgfx_sanitize_string(s::AbstractString)
s = replace(s, r"\\?\{" => "\\{") s = replace(s, r"\\?\{" => "\\{")
s = replace(s, r"\\?\}" => "\\}") s = replace(s, r"\\?\}" => "\\}")
s = map(split(s, "")) do s s = map(split(s, "")) do s
isascii(s) ? s : latexify(s) isascii(s) ? s : latexify(s)
end |> join end |> join
end end
@require LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" begin @require LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" begin
using .LaTeXStrings using .LaTeXStrings
@ -1144,60 +1159,58 @@ end
end end
end end
function pgfx_sanitize_plot!(plt) function pgfx_sanitize_plot!(plt)
for (key, value) in plt.attr for (key, value) in plt.attr
if value isa Union{AbstractString, AbstractVector{<:AbstractString}} if value isa Union{AbstractString,AbstractVector{<:AbstractString}}
plt.attr[key] = pgfx_sanitize_string.(value) plt.attr[key] = pgfx_sanitize_string.(value)
end
end end
for subplot in plt.subplots end
for (key, value) in subplot.attr for subplot in plt.subplots
if key == :annotations && subplot.attr[:annotations] !== nothing for (key, value) in subplot.attr
old_ann = subplot.attr[key] if key == :annotations && subplot.attr[:annotations] !== nothing
for i in eachindex(old_ann) old_ann = subplot.attr[key]
subplot.attr[key][i] = (old_ann[i][1], old_ann[i][2], pgfx_sanitize_string(old_ann[i][3])) for i in eachindex(old_ann)
end subplot.attr[key][i] =
elseif value isa Union{AbstractString, AbstractVector{<:AbstractString}} (old_ann[i][1], old_ann[i][2], pgfx_sanitize_string(old_ann[i][3]))
subplot.attr[key] = pgfx_sanitize_string.(value)
end end
elseif value isa Union{AbstractString,AbstractVector{<:AbstractString}}
subplot.attr[key] = pgfx_sanitize_string.(value)
end end
end end
for series in plt.series_list end
for (key, value) in series.plotattributes for series in plt.series_list
if key == :series_annotations && series.plotattributes[:series_annotations] !== nothing for (key, value) in series.plotattributes
old_ann = series.plotattributes[key].strs if key == :series_annotations &&
for i in eachindex(old_ann) series.plotattributes[:series_annotations] !== nothing
series.plotattributes[key].strs[i] = pgfx_sanitize_string(old_ann[i]) old_ann = series.plotattributes[key].strs
end for i in eachindex(old_ann)
elseif value isa Union{AbstractString, AbstractVector{<:AbstractString}} series.plotattributes[key].strs[i] = pgfx_sanitize_string(old_ann[i])
series.plotattributes[key] = pgfx_sanitize_string.(value)
end end
elseif value isa Union{AbstractString,AbstractVector{<:AbstractString}}
series.plotattributes[key] = pgfx_sanitize_string.(value)
end end
end end
## end
##
end end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter) function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
axis = sp[Symbol(letter, :axis)] axis = sp[Symbol(letter, :axis)]
# turn off scaled ticks # turn off scaled ticks
push!(opt, "scaled $(letter) ticks" => "false", string(letter, :label) => axis[:guide])
tick_color = plot_color(axis[:foreground_color_axis])
push!( push!(
opt, opt,
"scaled $(letter) ticks" => "false", "$(letter) tick style" =>
string(letter, :label) => axis[:guide], PGFPlotsX.Options("color" => color(tick_color), "opacity" => alpha(tick_color)),
)
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]) tick_label_color = plot_color(axis[:tickfontcolor])
push!(opt, push!(
opt,
"$(letter) tick label style" => PGFPlotsX.Options( "$(letter) tick label style" => PGFPlotsX.Options(
"color" => color(tick_color), "color" => color(tick_color),
"opacity" => alpha(tick_color), "opacity" => alpha(tick_color),
"rotate" => axis[:rotation] "rotate" => axis[:rotation],
), ),
) )
@ -1219,7 +1232,7 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
opt, opt,
string(letter, "label style") => PGFPlotsX.Options( string(letter, "label style") => PGFPlotsX.Options(
labelpos => nothing, labelpos => nothing,
"font" => pgfx_font(axis[:guidefontsize], pgfx_thickness_scaling(sp)), "font" => pgfx_font(axis[:guidefontsize], pgfx_thickness_scaling(sp)),
"color" => cstr, "color" => cstr,
"draw opacity" => α, "draw opacity" => α,
"rotate" => axis[:guidefontrotation], "rotate" => axis[:guidefontrotation],
@ -1233,8 +1246,7 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
scale = axis[:scale] scale = axis[:scale]
if scale in (:log2, :ln, :log10) if scale in (:log2, :ln, :log10)
push!(opt, string(letter, :mode) => "log") push!(opt, string(letter, :mode) => "log")
scale == :ln || scale == :ln || push!(opt, "log basis $letter" => "$(scale == :log2 ? 2 : 10)")
push!(opt, "log basis $letter" => "$(scale == :log2 ? 2 : 10)")
end end
# ticks on or off # ticks on or off
@ -1252,7 +1264,8 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
end end
# limits # limits
lims = ispolar(sp) && letter == :x ? rad2deg.(axis_limits(sp, :x)) : lims =
ispolar(sp) && letter == :x ? rad2deg.(axis_limits(sp, :x)) :
axis_limits(sp, letter) axis_limits(sp, letter)
push!(opt, string(letter, :min) => lims[1], string(letter, :max) => lims[2]) push!(opt, string(letter, :min) => lims[1], string(letter, :max) => lims[2])
@ -1263,12 +1276,8 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
tick_values = tick_values =
ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] : ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] :
ticks[1] ticks[1]
push!( push!(opt, string(letter, "tick") => string("{", join(tick_values, ","), "}"))
opt, if axis[:showaxis] && axis[:scale] in (:ln, :log2, :log10) && axis[:ticks] == :auto
string(letter, "tick") => string("{", join(tick_values, ","), "}"),
)
if axis[:showaxis] &&
axis[:scale] in (:ln, :log2, :log10) && axis[:ticks] == :auto
# wrap the power part of label with } # wrap the power part of label with }
tick_labels = Vector{String}(undef, length(ticks[2])) tick_labels = Vector{String}(undef, length(ticks[2]))
for (i, label) in enumerate(ticks[2]) for (i, label) in enumerate(ticks[2])
@ -1283,25 +1292,20 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
) )
elseif axis[:showaxis] elseif axis[:showaxis]
tick_labels = tick_labels =
ispolar(sp) && letter == :x ? [ticks[2][3:end]..., "0", "45"] : ispolar(sp) && letter == :x ? [ticks[2][3:end]..., "0", "45"] : ticks[2]
ticks[2]
if axis[:formatter] in (:scientific, :auto) if axis[:formatter] in (:scientific, :auto)
tick_labels = string.("\$", convert_sci_unicode.(tick_labels), "\$") tick_labels = string.("\$", convert_sci_unicode.(tick_labels), "\$")
tick_labels = replace.(tick_labels, Ref("×" => "\\times")) tick_labels = replace.(tick_labels, Ref("×" => "\\times"))
end end
push!( push!(
opt, opt,
string(letter, "ticklabels") => string(letter, "ticklabels") => string("{", join(tick_labels, ","), "}"),
string("{", join(tick_labels, ","), "}"),
) )
else else
push!(opt, string(letter, "ticklabels") => "{}") push!(opt, string(letter, "ticklabels") => "{}")
end end
if axis[:tick_direction] === :none if axis[:tick_direction] === :none
push!( push!(opt, string(letter, "tick style") => "draw=none")
opt,
string(letter, "tick style") => "draw=none",
)
else else
push!( push!(
opt, opt,
@ -1309,9 +1313,7 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
(axis[:tick_direction] == :out ? "outside" : "inside"), (axis[:tick_direction] == :out ? "outside" : "inside"),
) )
end end
push!( push!(opt, string(letter, "ticklabel style") => pgfx_get_ticklabel_style(sp, axis))
opt, string(letter, "ticklabel style") => pgfx_get_ticklabel_style(sp, axis)
)
push!( push!(
opt, opt,
string(letter, " grid style") => pgfx_linestyle( string(letter, " grid style") => pgfx_linestyle(
@ -1335,7 +1337,8 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
minor_ticks minor_ticks
push!( push!(
opt, opt,
string("extra ", letter, " ticks") => string("{", join(minor_ticks, ","), "}"), string("extra ", letter, " ticks") =>
string("{", join(minor_ticks, ","), "}"),
) )
push!(opt, string("extra ", letter, " tick labels") => "") push!(opt, string("extra ", letter, " tick labels") => "")
push!( push!(
@ -1348,7 +1351,9 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
axis[:minorgridalpha], axis[:minorgridalpha],
axis[:minorgridstyle], axis[:minorgridstyle],
), ),
"major tick length" => typeof(axis[:minorticks]) <: Integer && axis[:minorticks] > 1 || axis[:minorticks] ? "0.1cm" : "0" "major tick length" =>
typeof(axis[:minorticks]) <: Integer && axis[:minorticks] > 1 ||
axis[:minorticks] ? "0.1cm" : "0",
), ),
) )
end end
@ -1405,7 +1410,8 @@ end
# to fit ticks, tick labels, guides, colorbars, etc. # to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot{PGFPlotsXBackend}) function _update_min_padding!(sp::Subplot{PGFPlotsXBackend})
leg = sp[:legend] leg = sp[:legend]
if leg in (:best, :outertopright, :outerright, :outerbottomright) || (leg isa Tuple && leg[1] >= 1) if leg in (:best, :outertopright, :outerright, :outerbottomright) ||
(leg isa Tuple && leg[1] >= 1)
sp.minpad = (0mm, 0mm, 5mm, 0mm) sp.minpad = (0mm, 0mm, 5mm, 0mm)
else else
sp.minpad = (0mm, 0mm, 0mm, 0mm) sp.minpad = (0mm, 0mm, 0mm, 0mm)
@ -1425,33 +1431,21 @@ function _update_plot_object(plt::Plot{PGFPlotsXBackend})
end end
for mime in ("application/pdf", "image/svg+xml") for mime in ("application/pdf", "image/svg+xml")
@eval function _show( @eval function _show(io::IO, mime::MIME{Symbol($mime)}, plt::Plot{PGFPlotsXBackend})
io::IO,
mime::MIME{Symbol($mime)},
plt::Plot{PGFPlotsXBackend},
)
plt.o.was_shown = true plt.o.was_shown = true
show(io, mime, plt.o.the_plot) show(io, mime, plt.o.the_plot)
end end
end end
function _show( function _show(io::IO, mime::MIME{Symbol("image/png")}, plt::Plot{PGFPlotsXBackend})
io::IO, plt.o.was_shown = true
mime::MIME{Symbol("image/png")}, plt_file = tempname() * ".png"
plt::Plot{PGFPlotsXBackend}, PGFPlotsX.pgfsave(plt_file, plt.o.the_plot; dpi = plt[:dpi])
) write(io, read(plt_file))
plt.o.was_shown = true rm(plt_file; force = true)
plt_file = tempname() * ".png" end
PGFPlotsX.pgfsave(plt_file, plt.o.the_plot; dpi=plt[:dpi])
write(io, read(plt_file))
rm(plt_file; force = true)
end
function _show( function _show(io::IO, mime::MIME{Symbol("application/x-tex")}, plt::Plot{PGFPlotsXBackend})
io::IO,
mime::MIME{Symbol("application/x-tex")},
plt::Plot{PGFPlotsXBackend},
)
plt.o.was_shown = true plt.o.was_shown = true
PGFPlotsX.print_tex( PGFPlotsX.print_tex(
io, io,

View File

@ -7,12 +7,13 @@ function _plotly_framestyle(style::Symbol)
return style return style
else else
default_style = get((semi = :box, origin = :zerolines), style, :axes) default_style = get((semi = :box, origin = :zerolines), style, :axes)
@warn("Framestyle :$style is not supported by Plotly and PlotlyJS. :$default_style was chosen instead.") @warn(
"Framestyle :$style is not supported by Plotly and PlotlyJS. :$default_style was chosen instead."
)
default_style default_style
end end
end end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
using UUIDs using UUIDs
@ -23,7 +24,7 @@ function labelfunc(scale::Symbol, backend::PlotlyBackend)
texfunc = labelfunc_tex(scale) texfunc = labelfunc_tex(scale)
function (x) function (x)
tex_x = texfunc(x) tex_x = texfunc(x)
sup_x = replace( tex_x, r"\^{(.*)}"=>s"<sup>\1</sup>" ) sup_x = replace(tex_x, r"\^{(.*)}" => s"<sup>\1</sup>")
# replace dash with \minus (U+2212) # replace dash with \minus (U+2212)
replace(sup_x, "-" => "") replace(sup_x, "-" => "")
end end
@ -32,30 +33,25 @@ end
function plotly_font(font::Font, color = font.color) function plotly_font(font::Font, color = font.color)
KW( KW(
:family => font.family, :family => font.family,
:size => round(Int, font.pointsize*1.4), :size => round(Int, font.pointsize * 1.4),
:color => rgba_string(color), :color => rgba_string(color),
) )
end end
function plotly_annotation_dict(x, y, val; xref = "paper", yref = "paper")
function plotly_annotation_dict(x, y, val; xref="paper", yref="paper") KW(:text => val, :xref => xref, :x => x, :yref => yref, :y => y, :showarrow => false)
KW(
:text => val,
:xref => xref,
:x => x,
:yref => yref,
:y => y,
:showarrow => false,
)
end end
function plotly_annotation_dict(x, y, ptxt::PlotText; xref="paper", yref="paper") function plotly_annotation_dict(x, y, ptxt::PlotText; xref = "paper", yref = "paper")
merge(plotly_annotation_dict(x, y, ptxt.str; xref=xref, yref=yref), KW( merge(
:font => plotly_font(ptxt.font), plotly_annotation_dict(x, y, ptxt.str; xref = xref, yref = yref),
:xanchor => ptxt.font.halign == :hcenter ? :center : ptxt.font.halign, KW(
:yanchor => ptxt.font.valign == :vcenter ? :middle : ptxt.font.valign, :font => plotly_font(ptxt.font),
:rotation => -ptxt.font.rotation, :xanchor => ptxt.font.halign == :hcenter ? :center : ptxt.font.halign,
)) :yanchor => ptxt.font.valign == :vcenter ? :middle : ptxt.font.valign,
:rotation => -ptxt.font.rotation,
),
)
end end
# function get_annotation_dict_for_arrow(plotattributes::KW, xyprev::Tuple, xy::Tuple, a::Arrow) # function get_annotation_dict_for_arrow(plotattributes::KW, xyprev::Tuple, xy::Tuple, a::Arrow)
@ -100,9 +96,9 @@ function plotly_apply_aspect_ratio(sp::Subplot, plotarea, pcts)
if aspect_ratio == :equal if aspect_ratio == :equal
aspect_ratio = 1.0 aspect_ratio = 1.0
end end
xmin,xmax = axis_limits(sp, :x) xmin, xmax = axis_limits(sp, :x)
ymin,ymax = axis_limits(sp, :y) ymin, ymax = axis_limits(sp, :y)
want_ratio = ((xmax-xmin) / (ymax-ymin)) / aspect_ratio want_ratio = ((xmax - xmin) / (ymax - ymin)) / aspect_ratio
parea_ratio = width(plotarea) / height(plotarea) parea_ratio = width(plotarea) / height(plotarea)
if want_ratio > parea_ratio if want_ratio > parea_ratio
# need to shrink y # need to shrink y
@ -118,34 +114,34 @@ function plotly_apply_aspect_ratio(sp::Subplot, plotarea, pcts)
pcts pcts
end end
# this method gets the start/end in percentage of the canvas for this axis direction # this method gets the start/end in percentage of the canvas for this axis direction
function plotly_domain(sp::Subplot) function plotly_domain(sp::Subplot)
figw, figh = sp.plt[:size] figw, figh = sp.plt[:size]
pcts = bbox_to_pcts(sp.plotarea, figw*px, figh*px) pcts = bbox_to_pcts(sp.plotarea, figw * px, figh * px)
pcts = plotly_apply_aspect_ratio(sp, sp.plotarea, pcts) pcts = plotly_apply_aspect_ratio(sp, sp.plotarea, pcts)
x_domain = [pcts[1], pcts[1] + pcts[3]] x_domain = [pcts[1], pcts[1] + pcts[3]]
y_domain = [pcts[2], pcts[2] + pcts[4]] y_domain = [pcts[2], pcts[2] + pcts[4]]
return x_domain, y_domain return x_domain, y_domain
end end
function plotly_axis(axis, sp, anchor = nothing, domain = nothing) function plotly_axis(axis, sp, anchor = nothing, domain = nothing)
letter = axis[:letter] letter = axis[:letter]
framestyle = sp[:framestyle] framestyle = sp[:framestyle]
ax = KW( ax = KW(
:visible => framestyle != :none, :visible => framestyle != :none,
:title => axis[:guide], :title => axis[:guide],
:showgrid => axis[:grid], :showgrid => axis[:grid],
:gridcolor => rgba_string(plot_color(axis[:foreground_color_grid], axis[:gridalpha])), :gridcolor =>
:gridwidth => axis[:gridlinewidth], rgba_string(plot_color(axis[:foreground_color_grid], axis[:gridalpha])),
:zeroline => framestyle == :zerolines, :gridwidth => axis[:gridlinewidth],
:zeroline => framestyle == :zerolines,
:zerolinecolor => rgba_string(axis[:foreground_color_axis]), :zerolinecolor => rgba_string(axis[:foreground_color_axis]),
:showline => framestyle in (:box, :axes) && axis[:showaxis], :showline => framestyle in (:box, :axes) && axis[:showaxis],
:linecolor => rgba_string(plot_color(axis[:foreground_color_axis])), :linecolor => rgba_string(plot_color(axis[:foreground_color_axis])),
:ticks => axis[:tick_direction] === :out ? "outside" : :ticks =>
axis[:tick_direction] === :in ? "inside" : "", axis[:tick_direction] === :out ? "outside" :
:mirror => framestyle == :box, axis[:tick_direction] === :in ? "inside" : "",
:mirror => framestyle == :box,
:showticklabels => axis[:showaxis], :showticklabels => axis[:showaxis],
) )
if anchor !== nothing if anchor !== nothing
@ -166,7 +162,9 @@ function plotly_axis(axis, sp, anchor = nothing, domain = nothing)
if !(axis[:ticks] in (nothing, :none, false)) if !(axis[:ticks] in (nothing, :none, false))
ax[:titlefont] = plotly_font(guidefont(axis)) ax[:titlefont] = plotly_font(guidefont(axis))
ax[:tickfont] = plotly_font(tickfont(axis)) ax[:tickfont] = plotly_font(tickfont(axis))
ax[:tickcolor] = framestyle in (:zerolines, :grid) || !axis[:showaxis] ? rgba_string(invisible()) : rgb_string(axis[:foreground_color_axis]) ax[:tickcolor] =
framestyle in (:zerolines, :grid) || !axis[:showaxis] ?
rgba_string(invisible()) : rgb_string(axis[:foreground_color_axis])
ax[:linecolor] = rgba_string(axis[:foreground_color_axis]) ax[:linecolor] = rgba_string(axis[:foreground_color_axis])
# ticks # ticks
@ -195,10 +193,7 @@ function plotly_axis(axis, sp, anchor = nothing, domain = nothing)
end end
function plotly_polaraxis(sp::Subplot, axis::Axis) function plotly_polaraxis(sp::Subplot, axis::Axis)
ax = KW( ax = KW(:visible => axis[:showaxis], :showline => axis[:grid])
:visible => axis[:showaxis],
:showline => axis[:grid],
)
if axis[:letter] == :x if axis[:letter] == :x
ax[:range] = rad2deg.(axis_limits(sp, :x)) ax[:range] = rad2deg.(axis_limits(sp, :x))
@ -216,7 +211,7 @@ function plotly_layout(plt::Plot)
w, h = plt[:size] w, h = plt[:size]
plotattributes_out[:width], plotattributes_out[:height] = w, h plotattributes_out[:width], plotattributes_out[:height] = w, h
plotattributes_out[:paper_bgcolor] = rgba_string(plt[:background_color_outside]) plotattributes_out[:paper_bgcolor] = rgba_string(plt[:background_color_outside])
plotattributes_out[:margin] = KW(:l=>0, :b=>20, :r=>0, :t=>20) plotattributes_out[:margin] = KW(:l => 0, :b => 20, :r => 0, :t => 20)
plotattributes_out[:annotations] = KW[] plotattributes_out[:annotations] = KW[]
@ -237,9 +232,12 @@ function plotly_layout(plt::Plot)
else else
0.5 * (left(bb) + right(bb)) 0.5 * (left(bb) + right(bb))
end end
titlex, titley = xy_mm_to_pcts(xmm, top(bbox(sp)), w*px, h*px) titlex, titley = xy_mm_to_pcts(xmm, top(bbox(sp)), w * px, h * px)
title_font = font(titlefont(sp), :top) title_font = font(titlefont(sp), :top)
push!(plotattributes_out[:annotations], plotly_annotation_dict(titlex, titley, text(sp[:title], title_font))) push!(
plotattributes_out[:annotations],
plotly_annotation_dict(titlex, titley, text(sp[:title], title_font)),
)
end end
plotattributes_out[:plot_bgcolor] = rgba_string(sp[:background_color_inside]) plotattributes_out[:plot_bgcolor] = rgba_string(sp[:background_color_inside])
@ -248,8 +246,10 @@ function plotly_layout(plt::Plot)
sp[:framestyle] = _plotly_framestyle(sp[:framestyle]) sp[:framestyle] = _plotly_framestyle(sp[:framestyle])
if ispolar(sp) if ispolar(sp)
plotattributes_out[Symbol("angularaxis$(spidx)")] = plotly_polaraxis(sp, sp[:xaxis]) plotattributes_out[Symbol("angularaxis$(spidx)")] =
plotattributes_out[Symbol("radialaxis$(spidx)")] = plotly_polaraxis(sp, sp[:yaxis]) plotly_polaraxis(sp, sp[:xaxis])
plotattributes_out[Symbol("radialaxis$(spidx)")] =
plotly_polaraxis(sp, sp[:yaxis])
else else
x_domain, y_domain = plotly_domain(sp) x_domain, y_domain = plotly_domain(sp)
if RecipesPipeline.is3d(sp) if RecipesPipeline.is3d(sp)
@ -264,15 +264,15 @@ function plotly_layout(plt::Plot)
#2.6 multiplier set camera eye such that whole plot can be seen #2.6 multiplier set camera eye such that whole plot can be seen
:camera => KW( :camera => KW(
:eye => KW( :eye => KW(
:x => cosd(azim)*sind(theta)*2.6, :x => cosd(azim) * sind(theta) * 2.6,
:y => sind(azim)*sind(theta)*2.6, :y => sind(azim) * sind(theta) * 2.6,
:z => cosd(theta)*2.6, :z => cosd(theta) * 2.6,
), ),
), ),
) )
else else
plotattributes_out[Symbol("xaxis$(x_idx)")] = plotattributes_out[Symbol("xaxis$(x_idx)")] =
plotly_axis(sp[:xaxis], sp, string("y", y_idx) , x_domain) plotly_axis(sp[:xaxis], sp, string("y", y_idx), x_domain)
# don't allow yaxis to be reupdated/reanchored in a linked subplot # don't allow yaxis to be reupdated/reanchored in a linked subplot
if spidx == y_idx if spidx == y_idx
plotattributes_out[Symbol("yaxis$(y_idx)")] = plotattributes_out[Symbol("yaxis$(y_idx)")] =
@ -286,16 +286,28 @@ function plotly_layout(plt::Plot)
# annotations # annotations
for ann in sp[:annotations] for ann in sp[:annotations]
append!(plotattributes_out[:annotations], KW[plotly_annotation_dict(locate_annotation(sp, ann...)...; xref = "x$(x_idx)", yref = "y$(y_idx)")]) append!(
plotattributes_out[:annotations],
KW[plotly_annotation_dict(
locate_annotation(sp, ann...)...;
xref = "x$(x_idx)",
yref = "y$(y_idx)",
)],
)
end end
# series_annotations # series_annotations
for series in series_list(sp) for series in series_list(sp)
anns = series[:series_annotations] anns = series[:series_annotations]
for (xi,yi,str,fnt) in EachAnn(anns, series[:x], series[:y]) for (xi, yi, str, fnt) in EachAnn(anns, series[:x], series[:y])
push!(plotattributes_out[:annotations], plotly_annotation_dict( push!(
xi, plotattributes_out[:annotations],
yi, plotly_annotation_dict(
PlotText(str,fnt); xref = "x$(x_idx)", yref = "y$(y_idx)") xi,
yi,
PlotText(str, fnt);
xref = "x$(x_idx)",
yref = "y$(y_idx)",
),
) )
end end
end end
@ -318,26 +330,25 @@ function plotly_layout(plt::Plot)
end end
# turn off hover if nothing's using it # turn off hover if nothing's using it
if all(series -> series.plotattributes[:hover] in (false,:none), plt.series_list) if all(series -> series.plotattributes[:hover] in (false, :none), plt.series_list)
plotattributes_out[:hovermode] = "none" plotattributes_out[:hovermode] = "none"
end end
plotattributes_out = recursive_merge(plotattributes_out, plt.attr[:extra_plot_kwargs]) plotattributes_out = recursive_merge(plotattributes_out, plt.attr[:extra_plot_kwargs])
end end
function plotly_add_legend!(plotattributes_out::KW, sp::Subplot) function plotly_add_legend!(plotattributes_out::KW, sp::Subplot)
plotattributes_out[:showlegend] = sp[:legend] != :none plotattributes_out[:showlegend] = sp[:legend] != :none
legend_position = plotly_legend_pos(sp[:legend]) legend_position = plotly_legend_pos(sp[:legend])
if sp[:legend] != :none if sp[:legend] != :none
plotattributes_out[:legend] = KW( plotattributes_out[:legend] = KW(
:bgcolor => rgba_string(sp[:background_color_legend]), :bgcolor => rgba_string(sp[:background_color_legend]),
:bordercolor => rgba_string(sp[:foreground_color_legend]), :bordercolor => rgba_string(sp[:foreground_color_legend]),
:borderwidth => 1, :borderwidth => 1,
:traceorder => "normal", :traceorder => "normal",
:xanchor => legend_position.xanchor, :xanchor => legend_position.xanchor,
:yanchor => legend_position.yanchor, :yanchor => legend_position.yanchor,
:font => plotly_font(legendfont(sp)), :font => plotly_font(legendfont(sp)),
:tracegroupgap => 0, :tracegroupgap => 0,
:x => legend_position.coords[1], :x => legend_position.coords[1],
:y => legend_position.coords[2], :y => legend_position.coords[2],
@ -361,55 +372,71 @@ function plotly_legend_pos(pos::Symbol)
xouterright = 1.05 xouterright = 1.05
xouterleft = -0.15 xouterleft = -0.15
plotly_legend_position_mapping = ( plotly_legend_position_mapping = (
right = (coords = [1.0, ycenter], xanchor = "right", yanchor = "middle"), right = (coords = [1.0, ycenter], xanchor = "right", yanchor = "middle"),
left = (coords = [xleft, ycenter], xanchor = "left", yanchor = "middle"), left = (coords = [xleft, ycenter], xanchor = "left", yanchor = "middle"),
top = (coords = [xcenter, ytop], xanchor = "center", yanchor = "top"), top = (coords = [xcenter, ytop], xanchor = "center", yanchor = "top"),
bottom = (coords = [xcenter, ybot], xanchor = "center", yanchor = "bottom"), bottom = (coords = [xcenter, ybot], xanchor = "center", yanchor = "bottom"),
bottomleft = (coords = [xleft, ybot], xanchor = "left", yanchor = "bottom"), bottomleft = (coords = [xleft, ybot], xanchor = "left", yanchor = "bottom"),
bottomright = (coords = [1.0, ybot], xanchor = "right", yanchor = "bottom"), bottomright = (coords = [1.0, ybot], xanchor = "right", yanchor = "bottom"),
topright = (coords = [1.0, 1.0], xanchor = "right", yanchor = "top"), topright = (coords = [1.0, 1.0], xanchor = "right", yanchor = "top"),
topleft = (coords = [xleft, 1.0], xanchor = "left", yanchor = "top"), topleft = (coords = [xleft, 1.0], xanchor = "left", yanchor = "top"),
outertop =(coords = [center, youtertop ], xanchor = "upper", yanchor = "middle"), outertop = (coords = [center, youtertop], xanchor = "upper", yanchor = "middle"),
outerbottom =(coords = [center, youterbot], xanchor = "lower", yanchor = "middle"), outerbottom = (coords = [center, youterbot], xanchor = "lower", yanchor = "middle"),
outerleft =(coords = [xouterleft, center], xanchor = "left", yanchor = "top"), outerleft = (coords = [xouterleft, center], xanchor = "left", yanchor = "top"),
outerright =(coords = [xouterright, center], xanchor = "right", yanchor = "top"), outerright = (coords = [xouterright, center], xanchor = "right", yanchor = "top"),
outertopleft =(coords = [xouterleft, ytop], xanchor = "upper", yanchor = "left"), outertopleft = (coords = [xouterleft, ytop], xanchor = "upper", yanchor = "left"),
outertopright = (coords = [xouterright, ytop], xanchor = "upper", yanchor = "right"), outertopright = (
outerbottomleft =(coords = [xouterleft, ybot], xanchor = "lower", yanchor = "left"), coords = [xouterright, ytop],
outerbottomright =(coords = [xouterright, ybot], xanchor = "lower", yanchor = "right"), xanchor = "upper",
default = (coords = [1.0, 1.0], xanchor = "auto", yanchor = "auto") yanchor = "right",
),
outerbottomleft = (
coords = [xouterleft, ybot],
xanchor = "lower",
yanchor = "left",
),
outerbottomright = (
coords = [xouterright, ybot],
xanchor = "lower",
yanchor = "right",
),
default = (coords = [1.0, 1.0], xanchor = "auto", yanchor = "auto"),
) )
legend_position = get(plotly_legend_position_mapping, pos, plotly_legend_position_mapping.default) legend_position =
get(plotly_legend_position_mapping, pos, plotly_legend_position_mapping.default)
end end
plotly_legend_pos(v::Tuple{S,T}) where {S<:Real, T<:Real} = (coords=v, xanchor="left", yanchor="top") plotly_legend_pos(v::Tuple{S,T}) where {S<:Real,T<:Real} =
(coords = v, xanchor = "left", yanchor = "top")
plotly_legend_pos(theta::Real) = plotly_legend_pos((theta, :inner)) plotly_legend_pos(theta::Real) = plotly_legend_pos((theta, :inner))
function plotly_legend_pos(v::Tuple{S,Symbol}) where S<:Real function plotly_legend_pos(v::Tuple{S,Symbol}) where {S<:Real}
(s,c) = sincosd(v[1]) (s, c) = sincosd(v[1])
xanchors = ["left", "center", "right"] xanchors = ["left", "center", "right"]
yanchors = ["bottom", "middle", "top"] yanchors = ["bottom", "middle", "top"]
if v[2] === :inner if v[2] === :inner
rect = (0.07,0.5,1.0,0.07,0.52,1.0) rect = (0.07, 0.5, 1.0, 0.07, 0.52, 1.0)
xanchor = xanchors[legend_anchor_index(c)] xanchor = xanchors[legend_anchor_index(c)]
yanchor = yanchors[legend_anchor_index(s)] yanchor = yanchors[legend_anchor_index(s)]
else else
rect = (-0.15,0.5,1.05,-0.15,0.52,1.1) rect = (-0.15, 0.5, 1.05, -0.15, 0.52, 1.1)
xanchor = xanchors[4-legend_anchor_index(c)] xanchor = xanchors[4 - legend_anchor_index(c)]
yanchor = yanchors[4-legend_anchor_index(s)] yanchor = yanchors[4 - legend_anchor_index(s)]
end end
return (coords=legend_pos_from_angle(v[1],rect...), xanchor=xanchor, yanchor=yanchor) return (
coords = legend_pos_from_angle(v[1], rect...),
xanchor = xanchor,
yanchor = yanchor,
)
end end
function plotly_layout_json(plt::Plot) function plotly_layout_json(plt::Plot)
JSON.json(plotly_layout(plt), 4) JSON.json(plotly_layout(plt), 4)
end end
plotly_colorscale(cg::ColorGradient, α = nothing) = plotly_colorscale(cg::ColorGradient, α = nothing) =
[[v, rgba_string(plot_color(cg.colors[v], α))] for v in cg.values] [[v, rgba_string(plot_color(cg.colors[v], α))] for v in cg.values]
function plotly_colorscale(c::AbstractVector{<:Colorant}, α = nothing) function plotly_colorscale(c::AbstractVector{<:Colorant}, α = nothing)
@ -428,13 +455,12 @@ function plotly_colorscale(cg::PlotUtils.CategoricalColorGradient, α = nothing)
cinds = repeat(1:n, inner = 2) cinds = repeat(1:n, inner = 2)
vinds = vcat((i:(i + 1) for i in 1:n)...) vinds = vcat((i:(i + 1) for i in 1:n)...)
return [ return [
[cg.values[vinds[i]], rgba_string(plot_color(color_list(cg)[cinds[i]], α))] [cg.values[vinds[i]], rgba_string(plot_color(color_list(cg)[cinds[i]], α))] for
for i in eachindex(cinds) i in eachindex(cinds)
] ]
end end
plotly_colorscale(c, α = nothing) = plotly_colorscale(_as_gradient(c), α) plotly_colorscale(c, α = nothing) = plotly_colorscale(_as_gradient(c), α)
get_plotly_marker(k, def) = get( get_plotly_marker(k, def) = get(
( (
rect = "square", rect = "square",
@ -461,12 +487,11 @@ function plotly_link_indicies(plt::Plot, sp::Subplot)
x_idx, y_idx x_idx, y_idx
end end
# the Shape contructor will automatically close the shape. since we need it closed, # the Shape contructor will automatically close the shape. since we need it closed,
# we split by NaNs and then construct/destruct the shapes to get the closed coords # we split by NaNs and then construct/destruct the shapes to get the closed coords
function plotly_close_shapes(x, y) function plotly_close_shapes(x, y)
xs, ys = nansplit(x), nansplit(y) xs, ys = nansplit(x), nansplit(y)
for i=eachindex(xs) for i in eachindex(xs)
shape = Shape(xs[i], ys[i]) shape = Shape(xs[i], ys[i])
xs[i], ys[i] = coords(shape) xs[i], ys[i] = coords(shape)
end end
@ -479,7 +504,7 @@ function plotly_data(series::Series, letter::Symbol, data)
data = if axis[:ticks] == :native && data !== nothing data = if axis[:ticks] == :native && data !== nothing
plotly_native_data(axis, data) plotly_native_data(axis, data)
else else
data data
end end
if series[:seriestype] in (:heatmap, :contour, :surface, :wireframe, :mesh3d) if series[:seriestype] in (:heatmap, :contour, :surface, :wireframe, :mesh3d)
@ -512,7 +537,9 @@ function plotly_convert_to_datetime(x::AbstractArray, formatter::Function)
elseif formatter == timeformatter elseif formatter == timeformatter
map(xi -> string(Dates.Date(Dates.now()), " ", formatter(xi)), x) map(xi -> string(Dates.Date(Dates.now()), " ", formatter(xi)), x)
else else
error("Invalid DateTime formatter. Expected Plots.datetime/date/time formatter but got $formatter") error(
"Invalid DateTime formatter. Expected Plots.datetime/date/time formatter but got $formatter",
)
end end
end end
#ensures that a gradient is called if a single color is supplied where a gradient is needed (e.g. if a series recipe defines marker_z) #ensures that a gradient is called if a single color is supplied where a gradient is needed (e.g. if a series recipe defines marker_z)
@ -550,11 +577,12 @@ function plotly_series(plt::Plot, series::Series)
x, y = straightline_data(series, 100) x, y = straightline_data(series, 100)
z = series[:z] z = series[:z]
else else
x, y, z = series[:x], series[:y], series[:z] x, y, z = series[:x], series[:y], series[:z]
end end
x, y, z = (plotly_data(series, letter, data) x, y, z = (
for (letter, data) in zip((:x, :y, :z), (x, y, z)) plotly_data(series, letter, data) for
(letter, data) in zip((:x, :y, :z), (x, y, z))
) )
plotattributes_out[:name] = series[:label] plotattributes_out[:name] = series[:label]
@ -562,7 +590,8 @@ function plotly_series(plt::Plot, series::Series)
isscatter = st in (:scatter, :scatter3d, :scattergl) isscatter = st in (:scatter, :scatter3d, :scattergl)
hasmarker = isscatter || series[:markershape] != :none hasmarker = isscatter || series[:markershape] != :none
hasline = st in (:path, :path3d, :straightline) hasline = st in (:path, :path3d, :straightline)
hasfillrange = st in (:path, :scatter, :scattergl, :straightline) && hasfillrange =
st in (:path, :scatter, :scattergl, :straightline) &&
(isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple)) (isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple))
plotattributes_out[:colorbar] = KW(:title => sp[:colorbar_title]) plotattributes_out[:colorbar] = KW(:title => sp[:colorbar_title])
@ -580,7 +609,8 @@ function plotly_series(plt::Plot, series::Series)
y = heatmap_edges(y, sp[:yaxis][:scale]) y = heatmap_edges(y, sp[:yaxis][:scale])
plotattributes_out[:type] = "heatmap" plotattributes_out[:type] = "heatmap"
plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z
plotattributes_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) plotattributes_out[:colorscale] =
plotly_colorscale(series[:fillcolor], series[:fillalpha])
plotattributes_out[:showscale] = hascolorbar(sp) plotattributes_out[:showscale] = hascolorbar(sp)
elseif st == :contour elseif st == :contour
@ -588,24 +618,31 @@ function plotly_series(plt::Plot, series::Series)
plotattributes_out[:type] = "contour" plotattributes_out[:type] = "contour"
plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z
plotattributes_out[:ncontours] = series[:levels] + 2 plotattributes_out[:ncontours] = series[:levels] + 2
plotattributes_out[:contours] = KW(:coloring => filled ? "fill" : "lines", :showlabels => series[:contour_labels] == true) plotattributes_out[:contours] = KW(
plotattributes_out[:colorscale] = plotly_colorscale(series[:linecolor], series[:linealpha]) :coloring => filled ? "fill" : "lines",
:showlabels => series[:contour_labels] == true,
)
plotattributes_out[:colorscale] =
plotly_colorscale(series[:linecolor], series[:linealpha])
plotattributes_out[:showscale] = hascolorbar(sp) && hascolorbar(series) plotattributes_out[:showscale] = hascolorbar(sp) && hascolorbar(series)
elseif st in (:surface, :wireframe) elseif st in (:surface, :wireframe)
plotattributes_out[:type] = "surface" plotattributes_out[:type] = "surface"
plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z
if st == :wireframe if st == :wireframe
plotattributes_out[:hidesurface] = true plotattributes_out[:hidesurface] = true
wirelines = KW( wirelines = KW(
:show => true, :show => true,
:color => rgba_string(plot_color(series[:linecolor], series[:linealpha])), :color =>
rgba_string(plot_color(series[:linecolor], series[:linealpha])),
:highlightwidth => series[:linewidth], :highlightwidth => series[:linewidth],
) )
plotattributes_out[:contours] = KW(:x => wirelines, :y => wirelines, :z => wirelines) plotattributes_out[:contours] =
KW(:x => wirelines, :y => wirelines, :z => wirelines)
plotattributes_out[:showscale] = false plotattributes_out[:showscale] = false
else else
plotattributes_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) plotattributes_out[:colorscale] =
plotly_colorscale(series[:fillcolor], series[:fillalpha])
plotattributes_out[:opacity] = series[:fillalpha] plotattributes_out[:opacity] = series[:fillalpha]
if series[:fill_z] !== nothing if series[:fill_z] !== nothing
plotattributes_out[:surfacecolor] = handle_surface(series[:fill_z]) plotattributes_out[:surfacecolor] = handle_surface(series[:fill_z])
@ -613,24 +650,34 @@ function plotly_series(plt::Plot, series::Series)
plotattributes_out[:showscale] = hascolorbar(sp) plotattributes_out[:showscale] = hascolorbar(sp)
end end
elseif st == :mesh3d elseif st == :mesh3d
plotattributes_out[:type] = "mesh3d" plotattributes_out[:type] = "mesh3d"
plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z
if series[:connections] !== nothing if series[:connections] !== nothing
if typeof(series[:connections]) <: Tuple{Array,Array,Array} if typeof(series[:connections]) <: Tuple{Array,Array,Array}
i,j,k = series[:connections] i, j, k = series[:connections]
if !(length(i) == length(j) == length(k)) if !(length(i) == length(j) == length(k))
throw(ArgumentError("Argument connections must consist of equally sized arrays.")) throw(
end ArgumentError(
plotattributes_out[:i] = i "Argument connections must consist of equally sized arrays.",
plotattributes_out[:j] = j ),
plotattributes_out[:k] = k )
else end
throw(ArgumentError("Argument connections has to be a tuple of three arrays.")) plotattributes_out[:i] = i
end plotattributes_out[:j] = j
end plotattributes_out[:k] = k
plotattributes_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) else
plotattributes_out[:color] = rgba_string(plot_color(series[:fillcolor], series[:fillalpha])) throw(
ArgumentError(
"Argument connections has to be a tuple of three arrays.",
),
)
end
end
plotattributes_out[:colorscale] =
plotly_colorscale(series[:fillcolor], series[:fillalpha])
plotattributes_out[:color] =
rgba_string(plot_color(series[:fillcolor], series[:fillalpha]))
plotattributes_out[:opacity] = series[:fillalpha] plotattributes_out[:opacity] = series[:fillalpha]
if series[:fill_z] !== nothing if series[:fill_z] !== nothing
plotattributes_out[:surfacecolor] = handle_surface(series[:fill_z]) plotattributes_out[:surfacecolor] = handle_surface(series[:fill_z])
@ -645,12 +692,25 @@ function plotly_series(plt::Plot, series::Series)
if hasmarker if hasmarker
inds = eachindex(x) inds = eachindex(x)
plotattributes_out[:marker] = KW( plotattributes_out[:marker] = KW(
:symbol => get_plotly_marker(series[:markershape], string(series[:markershape])), :symbol =>
get_plotly_marker(series[:markershape], string(series[:markershape])),
# :opacity => series[:markeralpha], # :opacity => series[:markeralpha],
:size => 2 * _cycle(series[:markersize], inds), :size => 2 * _cycle(series[:markersize], inds),
:color => rgba_string.(plot_color.(get_markercolor.(series, inds), get_markeralpha.(series, inds))), :color =>
rgba_string.(
plot_color.(
get_markercolor.(series, inds),
get_markeralpha.(series, inds),
),
),
:line => KW( :line => KW(
:color => rgba_string.(plot_color.(get_markerstrokecolor.(series, inds), get_markerstrokealpha.(series, inds))), :color =>
rgba_string.(
plot_color.(
get_markerstrokecolor.(series, inds),
get_markerstrokealpha.(series, inds),
),
),
:width => _cycle(series[:markerstrokewidth], inds), :width => _cycle(series[:markerstrokewidth], inds),
), ),
) )
@ -663,7 +723,7 @@ function plotly_series(plt::Plot, series::Series)
end end
function plotly_series_shapes(plt::Plot, series::Series, clims) function plotly_series_shapes(plt::Plot, series::Series, clims)
segments = series_segments(series; check=true) segments = series_segments(series; check = true)
plotattributes_outs = Vector{KW}(undef, length(segments)) plotattributes_outs = Vector{KW}(undef, length(segments))
# TODO: create a plotattributes_out for each polygon # TODO: create a plotattributes_out for each polygon
@ -678,8 +738,9 @@ function plotly_series_shapes(plt::Plot, series::Series, clims)
:legendgroup => series[:label], :legendgroup => series[:label],
) )
x, y = (plotly_data(series, letter, data) x, y = (
for (letter, data) in zip((:x, :y), shape_data(series, 100)) plotly_data(series, letter, data) for
(letter, data) in zip((:x, :y), shape_data(series, 100))
) )
for (k, segment) in enumerate(segments) for (k, segment) in enumerate(segments)
@ -687,22 +748,29 @@ function plotly_series_shapes(plt::Plot, series::Series, clims)
length(rng) < 2 && continue length(rng) < 2 && continue
# to draw polygons, we actually draw lines with fill # to draw polygons, we actually draw lines with fill
plotattributes_out = merge(plotattributes_base, KW( plotattributes_out = merge(
:type => "scatter", plotattributes_base,
:mode => "lines", KW(
:x => vcat(x[rng], x[rng[1]]), :type => "scatter",
:y => vcat(y[rng], y[rng[1]]), :mode => "lines",
:fill => "tozeroy", :x => vcat(x[rng], x[rng[1]]),
:fillcolor => rgba_string(plot_color(get_fillcolor(series, clims, i), get_fillalpha(series, i))), :y => vcat(y[rng], y[rng[1]]),
)) :fill => "tozeroy",
:fillcolor => rgba_string(
plot_color(get_fillcolor(series, clims, i), get_fillalpha(series, i)),
),
),
)
if series[:markerstrokewidth] > 0 if series[:markerstrokewidth] > 0
plotattributes_out[:line] = KW( plotattributes_out[:line] = KW(
:color => rgba_string(plot_color(get_linecolor(series, clims, i), get_linealpha(series, i))), :color => rgba_string(
plot_color(get_linecolor(series, clims, i), get_linealpha(series, i)),
),
:width => get_linewidth(series, i), :width => get_linewidth(series, i),
:dash => string(get_linestyle(series, i)), :dash => string(get_linestyle(series, i)),
) )
end end
plotattributes_out[:showlegend] = k==1 ? should_add_to_legend(series) : false plotattributes_out[:showlegend] = k == 1 ? should_add_to_legend(series) : false
plotly_polar!(plotattributes_out, series) plotly_polar!(plotattributes_out, series)
plotly_hover!(plotattributes_out, _cycle(series[:hover], i)) plotly_hover!(plotattributes_out, _cycle(series[:hover], i))
plotattributes_outs[k] = plotattributes_out plotattributes_outs[k] = plotattributes_out
@ -712,7 +780,10 @@ function plotly_series_shapes(plt::Plot, series::Series, clims)
elseif series[:line_z] !== nothing elseif series[:line_z] !== nothing
push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :line)) push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :line))
elseif series[:marker_z] !== nothing elseif series[:marker_z] !== nothing
push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :marker)) push!(
plotattributes_outs,
plotly_colorbar_hack(series, plotattributes_base, :marker),
)
end end
plotattributes_outs plotattributes_outs
end end
@ -723,37 +794,46 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z
isscatter = st in (:scatter, :scatter3d, :scattergl) isscatter = st in (:scatter, :scatter3d, :scattergl)
hasmarker = isscatter || series[:markershape] != :none hasmarker = isscatter || series[:markershape] != :none
hasline = st in (:path, :path3d, :straightline) hasline = st in (:path, :path3d, :straightline)
hasfillrange = st in (:path, :scatter, :scattergl, :straightline) && hasfillrange =
st in (:path, :scatter, :scattergl, :straightline) &&
(isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple)) (isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple))
segments = collect(series_segments(series, st)) segments = collect(series_segments(series, st))
plotattributes_outs = fill(KW(), (hasfillrange ? 2 : 1 ) * length(segments)) plotattributes_outs = fill(KW(), (hasfillrange ? 2 : 1) * length(segments))
needs_scatter_fix = !isscatter && hasmarker && !any(isnan,y) && length(segments) > 1 needs_scatter_fix = !isscatter && hasmarker && !any(isnan, y) && length(segments) > 1
for (k, segment) in enumerate(segments) for (k, segment) in enumerate(segments)
i, rng = segment.attr_index, segment.range i, rng = segment.attr_index, segment.range
plotattributes_out = deepcopy(plotattributes_base) plotattributes_out = deepcopy(plotattributes_base)
plotattributes_out[:showlegend] = k==1 ? should_add_to_legend(series) : false plotattributes_out[:showlegend] = k == 1 ? should_add_to_legend(series) : false
plotattributes_out[:legendgroup] = series[:label] plotattributes_out[:legendgroup] = series[:label]
# set the type # set the type
if st in (:path, :scatter, :scattergl, :straightline) if st in (:path, :scatter, :scattergl, :straightline)
plotattributes_out[:type] = st==:scattergl ? "scattergl" : "scatter" plotattributes_out[:type] = st == :scattergl ? "scattergl" : "scatter"
plotattributes_out[:mode] = if hasmarker plotattributes_out[:mode] = if hasmarker
hasline ? "lines+markers" : "markers" hasline ? "lines+markers" : "markers"
else else
hasline ? "lines" : "none" hasline ? "lines" : "none"
end end
if series[:fillrange] == true || series[:fillrange] == 0 || isa(series[:fillrange], Tuple) if series[:fillrange] == true ||
series[:fillrange] == 0 ||
isa(series[:fillrange], Tuple)
plotattributes_out[:fill] = "tozeroy" plotattributes_out[:fill] = "tozeroy"
plotattributes_out[:fillcolor] = rgba_string(plot_color(get_fillcolor(series, clims, i), get_fillalpha(series, i))) plotattributes_out[:fillcolor] = rgba_string(
elseif typeof(series[:fillrange]) <: Union{AbstractVector{<:Real}, Real} plot_color(get_fillcolor(series, clims, i), get_fillalpha(series, i)),
)
elseif typeof(series[:fillrange]) <: Union{AbstractVector{<:Real},Real}
plotattributes_out[:fill] = "tonexty" plotattributes_out[:fill] = "tonexty"
plotattributes_out[:fillcolor] = rgba_string(plot_color(get_fillcolor(series, clims, i), get_fillalpha(series, i))) plotattributes_out[:fillcolor] = rgba_string(
plot_color(get_fillcolor(series, clims, i), get_fillalpha(series, i)),
)
elseif !(series[:fillrange] in (false, nothing)) elseif !(series[:fillrange] in (false, nothing))
@warn("fillrange ignored... plotly only supports filling to zero and to a vector of values. fillrange: $(series[:fillrange])") @warn(
"fillrange ignored... plotly only supports filling to zero and to a vector of values. fillrange: $(series[:fillrange])"
)
end end
plotattributes_out[:x], plotattributes_out[:y] = x[rng], y[rng] plotattributes_out[:x], plotattributes_out[:y] = x[rng], y[rng]
@ -764,20 +844,32 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z
else else
hasline ? "lines" : "none" hasline ? "lines" : "none"
end end
plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x[rng], y[rng], z[rng] plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] =
x[rng], y[rng], z[rng]
end end
# add "marker" # add "marker"
if hasmarker if hasmarker
mcolor = rgba_string(plot_color(get_markercolor(series, clims, i), get_markeralpha(series, i))) mcolor = rgba_string(
lcolor = rgba_string(plot_color(get_markerstrokecolor(series, i), get_markerstrokealpha(series, i))) plot_color(get_markercolor(series, clims, i), get_markeralpha(series, i)),
)
lcolor = rgba_string(
plot_color(
get_markerstrokecolor(series, i),
get_markerstrokealpha(series, i),
),
)
plotattributes_out[:marker] = KW( plotattributes_out[:marker] = KW(
:symbol => get_plotly_marker(_cycle(series[:markershape], i), string(_cycle(series[:markershape], i))), :symbol => get_plotly_marker(
_cycle(series[:markershape], i),
string(_cycle(series[:markershape], i)),
),
# :opacity => needs_scatter_fix ? [1, 0] : 1, # :opacity => needs_scatter_fix ? [1, 0] : 1,
:size => 2 * _cycle(series[:markersize], i), :size => 2 * _cycle(series[:markersize], i),
:color => needs_scatter_fix ? [mcolor, "rgba(0, 0, 0, 0.000)"] : mcolor, :color => needs_scatter_fix ? [mcolor, "rgba(0, 0, 0, 0.000)"] : mcolor,
:line => KW( :line => KW(
:color => needs_scatter_fix ? [lcolor, "rgba(0, 0, 0, 0.000)"] : lcolor, :color =>
needs_scatter_fix ? [lcolor, "rgba(0, 0, 0, 0.000)"] : lcolor,
:width => _cycle(series[:markerstrokewidth], i), :width => _cycle(series[:markerstrokewidth], i),
), ),
) )
@ -786,7 +878,9 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z
# add "line" # add "line"
if hasline if hasline
plotattributes_out[:line] = KW( plotattributes_out[:line] = KW(
:color => rgba_string(plot_color(get_linecolor(series, clims, i), get_linealpha(series, i))), :color => rgba_string(
plot_color(get_linecolor(series, clims, i), get_linealpha(series, i)),
),
:width => get_linewidth(series, i), :width => get_linewidth(series, i),
:shape => if st == :steppre :shape => if st == :steppre
"vh" "vh"
@ -813,8 +907,12 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z
if typeof(series[:fillrange]) <: Real if typeof(series[:fillrange]) <: Real
series[:fillrange] = fill(series[:fillrange], length(rng)) series[:fillrange] = fill(series[:fillrange], length(rng))
elseif typeof(series[:fillrange]) <: Tuple elseif typeof(series[:fillrange]) <: Tuple
f1 = typeof(series[:fillrange][1]) <: Real ? fill(series[:fillrange][1], length(rng)) : series[:fillrange][1][rng] f1 =
f2 = typeof(series[:fillrange][2]) <: Real ? fill(series[:fillrange][2], length(rng)) : series[:fillrange][2][rng] typeof(series[:fillrange][1]) <: Real ?
fill(series[:fillrange][1], length(rng)) : series[:fillrange][1][rng]
f2 =
typeof(series[:fillrange][2]) <: Real ?
fill(series[:fillrange][2], length(rng)) : series[:fillrange][2][rng]
series[:fillrange] = (f1, f2) series[:fillrange] = (f1, f2)
end end
if isa(series[:fillrange], AbstractVector) if isa(series[:fillrange], AbstractVector)
@ -824,13 +922,15 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z
else else
# if fillrange is a tuple with upper and lower limit, plotattributes_out_fillrange # if fillrange is a tuple with upper and lower limit, plotattributes_out_fillrange
# is the series that will do the filling # is the series that will do the filling
plotattributes_out_fillrange[:x], plotattributes_out_fillrange[:y] = concatenate_fillrange(x[rng], series[:fillrange]) plotattributes_out_fillrange[:x], plotattributes_out_fillrange[:y] =
concatenate_fillrange(x[rng], series[:fillrange])
plotattributes_out_fillrange[:line][:width] = 0 plotattributes_out_fillrange[:line][:width] = 0
delete!(plotattributes_out, :fill) delete!(plotattributes_out, :fill)
delete!(plotattributes_out, :fillcolor) delete!(plotattributes_out, :fillcolor)
end end
plotattributes_outs[(2k-1):(2k)] = [plotattributes_out_fillrange, plotattributes_out] plotattributes_outs[(2k - 1):(2k)] =
[plotattributes_out_fillrange, plotattributes_out]
else else
plotattributes_outs[k] = plotattributes_out plotattributes_outs[k] = plotattributes_out
end end
@ -841,7 +941,10 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z
elseif series[:fill_z] !== nothing elseif series[:fill_z] !== nothing
push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :fill)) push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :fill))
elseif series[:marker_z] !== nothing elseif series[:marker_z] !== nothing
push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :marker)) push!(
plotattributes_outs,
plotly_colorbar_hack(series, plotattributes_base, :marker),
)
end end
plotattributes_outs plotattributes_outs
@ -871,7 +974,6 @@ function plotly_colorbar_hack(series::Series, plotattributes_base::KW, sym::Symb
return plotattributes_out return plotattributes_out
end end
function plotly_polar!(plotattributes_out::KW, series::Series) function plotly_polar!(plotattributes_out::KW, series::Series)
# convert polar plots x/y to theta/radius # convert polar plots x/y to theta/radius
if ispolar(series[:subplot]) if ispolar(series[:subplot])
@ -910,11 +1012,15 @@ html_body(plt::Plot{PlotlyBackend}) = plotly_html_body(plt)
function plotly_html_head(plt::Plot) function plotly_html_head(plt::Plot)
plotly = plotly =
use_local_dependencies[] ? ("file:///" * plotly_local_file_path[]) : "https://cdn.plot.ly/$(_plotly_min_js_filename)" use_local_dependencies[] ? ("file:///" * plotly_local_file_path[]) :
"https://cdn.plot.ly/$(_plotly_min_js_filename)"
include_mathjax = get(plt[:extra_plot_kwargs], :include_mathjax, "") include_mathjax = get(plt[:extra_plot_kwargs], :include_mathjax, "")
mathjax_file = include_mathjax != "cdn" ? ("file://" * include_mathjax) : "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML" mathjax_file =
mathjax_head = include_mathjax == "" ? "" : "<script src=\"$mathjax_file\"></script>\n\t\t" include_mathjax != "cdn" ? ("file://" * include_mathjax) :
"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"
mathjax_head =
include_mathjax == "" ? "" : "<script src=\"$mathjax_file\"></script>\n\t\t"
if isijulia() if isijulia()
mathjax_head mathjax_head
@ -934,8 +1040,9 @@ function plotly_html_body(plt, style = nothing)
if isijulia() if isijulia()
# require.js adds .js automatically # require.js adds .js automatically
plotly_no_ext = plotly_no_ext =
use_local_dependencies[] ? ("file:///" * plotly_local_file_path[]) : "https://cdn.plot.ly/$(_plotly_min_js_filename)" use_local_dependencies[] ? ("file:///" * plotly_local_file_path[]) :
plotly_no_ext = plotly_no_ext[1:end-3] "https://cdn.plot.ly/$(_plotly_min_js_filename)"
plotly_no_ext = plotly_no_ext[1:(end - 3)]
requirejs_prefix = """ requirejs_prefix = """
requirejs.config({ requirejs.config({
@ -984,12 +1091,10 @@ function _show(io::IO, ::MIME"application/vnd.plotly.v1+json", plot::Plot{Plotly
plotly_show_js(io, plot) plotly_show_js(io, plot)
end end
function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyBackend}) function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyBackend})
write(io, embeddable_html(plt)) write(io, embeddable_html(plt))
end end
function _display(plt::Plot{PlotlyBackend}) function _display(plt::Plot{PlotlyBackend})
standalone_html_window(plt) standalone_html_window(plt)
end end

View File

@ -25,16 +25,19 @@ for (mime, fmt) in (
"image/svg+xml" => "svg", "image/svg+xml" => "svg",
"image/eps" => "eps", "image/eps" => "eps",
) )
@eval _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plotlyjs_syncplot(plt), format = $fmt) @eval _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{PlotlyJSBackend}) =
PlotlyJS.savefig(io, plotlyjs_syncplot(plt), format = $fmt)
end end
# Use the Plotly implementation for json and html: # Use the Plotly implementation for json and html:
_show(io::IO, mime::MIME"application/vnd.plotly.v1+json", plt::Plot{PlotlyJSBackend}) = plotly_show_js(io, plt) _show(io::IO, mime::MIME"application/vnd.plotly.v1+json", plt::Plot{PlotlyJSBackend}) =
plotly_show_js(io, plt)
html_head(plt::Plot{PlotlyJSBackend}) = plotly_html_head(plt) html_head(plt::Plot{PlotlyJSBackend}) = plotly_html_head(plt)
html_body(plt::Plot{PlotlyJSBackend}) = plotly_html_body(plt) html_body(plt::Plot{PlotlyJSBackend}) = plotly_html_body(plt)
_show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) = write(io, embeddable_html(plt)) _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) =
write(io, embeddable_html(plt))
_display(plt::Plot{PlotlyJSBackend}) = display(plotlyjs_syncplot(plt)) _display(plt::Plot{PlotlyJSBackend}) = display(plotlyjs_syncplot(plt))

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,6 @@
# https://github.com/Evizero/UnicodePlots.jl # https://github.com/Evizero/UnicodePlots.jl
# don't warn on unsupported... there's just too many warnings!! # don't warn on unsupported... there's just too many warnings!!
warn_on_unsupported_args(::UnicodePlotsBackend, plotattributes::KW) = nothing warn_on_unsupported_args(::UnicodePlotsBackend, plotattributes::KW) = nothing
@ -17,7 +16,6 @@ function _canvas_map()
) )
end end
# do all the magic here... build it all at once, since we need to know about all the series at the very beginning # do all the magic here... build it all at once, since we need to know about all the series at the very beginning
function rebuildUnicodePlot!(plt::Plot, width, height) function rebuildUnicodePlot!(plt::Plot, width, height)
plt.o = [] plt.o = []
@ -25,8 +23,8 @@ function rebuildUnicodePlot!(plt::Plot, width, height)
for sp in plt.subplots for sp in plt.subplots
xaxis = sp[:xaxis] xaxis = sp[:xaxis]
yaxis = sp[:yaxis] yaxis = sp[:yaxis]
xlim = axis_limits(sp, :x) xlim = axis_limits(sp, :x)
ylim = axis_limits(sp, :y) ylim = axis_limits(sp, :y)
# make vectors # make vectors
xlim = [xlim[1], xlim[2]] xlim = [xlim[1], xlim[2]]
@ -49,13 +47,16 @@ function rebuildUnicodePlot!(plt::Plot, width, height)
if length(sp.series_list) == 1 if length(sp.series_list) == 1
series = sp.series_list[1] series = sp.series_list[1]
if series[:seriestype] == :spy if series[:seriestype] == :spy
push!(plt.o, UnicodePlots.spy( push!(
series[:z].surf, plt.o,
width = width, UnicodePlots.spy(
height = height, series[:z].surf,
title = sp[:title], width = width,
canvas = canvas_type height = height,
)) title = sp[:title],
canvas = canvas_type,
),
)
continue continue
end end
end end
@ -65,13 +66,16 @@ function rebuildUnicodePlot!(plt::Plot, width, height)
# canvas_type = UnicodePlots.BarplotGraphics # canvas_type = UnicodePlots.BarplotGraphics
# end # end
o = UnicodePlots.Plot(x, y, canvas_type; o = UnicodePlots.Plot(
x,
y,
canvas_type;
width = width, width = width,
height = height, height = height,
title = sp[:title], title = sp[:title],
xlim = xlim, xlim = xlim,
ylim = ylim, ylim = ylim,
border = isijulia() ? :ascii : :solid border = isijulia() ? :ascii : :solid,
) )
# set the axis labels # set the axis labels
@ -88,7 +92,6 @@ function rebuildUnicodePlot!(plt::Plot, width, height)
end end
end end
# add a single series # add a single series
function addUnicodeSeries!(o, plotattributes, addlegend::Bool, xlim, ylim) function addUnicodeSeries!(o, plotattributes, addlegend::Bool, xlim, ylim)
# get the function, or special handling for step/bar/hist # get the function, or special handling for step/bar/hist
@ -102,8 +105,8 @@ function addUnicodeSeries!(o, plotattributes, addlegend::Bool, xlim, ylim)
func = UnicodePlots.lineplot! func = UnicodePlots.lineplot!
elseif st == :scatter || plotattributes[:markershape] != :none elseif st == :scatter || plotattributes[:markershape] != :none
func = UnicodePlots.scatterplot! func = UnicodePlots.scatterplot!
# elseif st == :bar # elseif st == :bar
# func = UnicodePlots.barplot! # func = UnicodePlots.barplot!
elseif st == :shape elseif st == :shape
func = UnicodePlots.lineplot! func = UnicodePlots.lineplot!
else else
@ -121,10 +124,14 @@ function addUnicodeSeries!(o, plotattributes, addlegend::Bool, xlim, ylim)
label = addlegend ? plotattributes[:label] : "" label = addlegend ? plotattributes[:label] : ""
# if we happen to pass in allowed color symbols, great... otherwise let UnicodePlots decide # if we happen to pass in allowed color symbols, great... otherwise let UnicodePlots decide
color = plotattributes[:linecolor] in UnicodePlots.color_cycle ? plotattributes[:linecolor] : :auto color =
plotattributes[:linecolor] in UnicodePlots.color_cycle ?
plotattributes[:linecolor] : :auto
# add the series # add the series
x, y = RecipesPipeline.unzip(collect(Base.Iterators.filter(xy->isfinite(xy[1])&&isfinite(xy[2]), zip(x,y)))) x, y = RecipesPipeline.unzip(
collect(Base.Iterators.filter(xy -> isfinite(xy[1]) && isfinite(xy[2]), zip(x, y))),
)
func(o, x, y; color = color, name = label) func(o, x, y; color = color, name = label)
end end
@ -162,7 +169,7 @@ end
function unicodeplots_rebuild(plt::Plot{UnicodePlotsBackend}) function unicodeplots_rebuild(plt::Plot{UnicodePlotsBackend})
w, h = plt[:size] w, h = plt[:size]
plt.attr[:color_palette] = [RGB(0,0,0)] plt.attr[:color_palette] = [RGB(0, 0, 0)]
rebuildUnicodePlot!(plt, div(w, 10), div(h, 20)) rebuildUnicodePlot!(plt, div(w, 10), div(h, 20))
end end
@ -172,7 +179,6 @@ function _show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend})
nothing nothing
end end
function _display(plt::Plot{UnicodePlotsBackend}) function _display(plt::Plot{UnicodePlotsBackend})
unicodeplots_rebuild(plt) unicodeplots_rebuild(plt)
map(show, plt.o) map(show, plt.o)

View File

@ -3,7 +3,10 @@
# CREDIT: parts of this implementation were inspired by @joshday's PlotlyLocal.jl # CREDIT: parts of this implementation were inspired by @joshday's PlotlyLocal.jl
function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.attr, :window_title, "Plots.jl")) function standalone_html(
plt::AbstractPlot;
title::AbstractString = get(plt.attr, :window_title, "Plots.jl"),
)
""" """
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@ -49,7 +52,8 @@ function standalone_html_window(plt::AbstractPlot)
old = use_local_dependencies[] # save state to restore afterwards old = use_local_dependencies[] # save state to restore afterwards
# if we open a browser ourself, we can host local files, so # if we open a browser ourself, we can host local files, so
# when we have a local plotly downloaded this is the way to go! # when we have a local plotly downloaded this is the way to go!
use_local_dependencies[] = plotly_local_file_path[] === nothing ? false : isfile(plotly_local_file_path[]) use_local_dependencies[] =
plotly_local_file_path[] === nothing ? false : isfile(plotly_local_file_path[])
filename = write_temp_html(plt) filename = write_temp_html(plt)
open_browser_window(filename) open_browser_window(filename)
# restore for other backends # restore for other backends
@ -58,7 +62,9 @@ end
# uses wkhtmltopdf/wkhtmltoimage: http://wkhtmltopdf.org/downloads.html # uses wkhtmltopdf/wkhtmltoimage: http://wkhtmltopdf.org/downloads.html
function html_to_png(html_fn, png_fn, w, h) function html_to_png(html_fn, png_fn, w, h)
run(`wkhtmltoimage -f png -q --width $w --height $h --disable-smart-width $html_fn $png_fn`) run(
`wkhtmltoimage -f png -q --width $w --height $h --disable-smart-width $html_fn $png_fn`,
)
end end
function show_png_from_html(io::IO, plt::AbstractPlot) function show_png_from_html(io::IO, plt::AbstractPlot)

View File

@ -1,10 +1,11 @@
# These functions return an operator for use in `get_clims(::Seres, op)` # These functions return an operator for use in `get_clims(::Seres, op)`
process_clims(lims::Tuple{<:Number,<:Number}) = (zlims -> ifelse.(isfinite.(lims), lims, zlims)) ignorenan_extrema process_clims(lims::Tuple{<:Number,<:Number}) =
(zlims -> ifelse.(isfinite.(lims), lims, zlims)) ignorenan_extrema
process_clims(s::Union{Symbol,Nothing,Missing}) = ignorenan_extrema process_clims(s::Union{Symbol,Nothing,Missing}) = ignorenan_extrema
# don't specialize on ::Function otherwise python functions won't work # don't specialize on ::Function otherwise python functions won't work
process_clims(f) = f process_clims(f) = f
function get_clims(sp::Subplot, op=process_clims(sp[:clims])) function get_clims(sp::Subplot, op = process_clims(sp[:clims]))
zmin, zmax = Inf, -Inf zmin, zmax = Inf, -Inf
for series in series_list(sp) for series in series_list(sp)
if series[:colorbar_entry] if series[:colorbar_entry]
@ -14,7 +15,7 @@ function get_clims(sp::Subplot, op=process_clims(sp[:clims]))
return zmin <= zmax ? (zmin, zmax) : (NaN, NaN) return zmin <= zmax ? (zmin, zmax) : (NaN, NaN)
end end
function get_clims(sp::Subplot, series::Series, op=process_clims(sp[:clims])) function get_clims(sp::Subplot, series::Series, op = process_clims(sp[:clims]))
zmin, zmax = if series[:colorbar_entry] zmin, zmax = if series[:colorbar_entry]
get_clims(sp, op) get_clims(sp, op)
else else
@ -30,13 +31,18 @@ Finds the limits for the colorbar by taking the "z-values" for the series and pa
which must return the tuple `(zmin, zmax)`. The default op is the extrema of the finite which must return the tuple `(zmin, zmax)`. The default op is the extrema of the finite
values of the input. values of the input.
""" """
function get_clims(series::Series, op=ignorenan_extrema) function get_clims(series::Series, op = ignorenan_extrema)
zmin, zmax = Inf, -Inf zmin, zmax = Inf, -Inf
z_colored_series = (:contour, :contour3d, :heatmap, :histogram2d, :surface, :hexbin) z_colored_series = (:contour, :contour3d, :heatmap, :histogram2d, :surface, :hexbin)
for vals in (series[:seriestype] in z_colored_series ? series[:z] : nothing, series[:line_z], series[:marker_z], series[:fill_z]) for vals in (
if (typeof(vals) <: AbstractSurface) && (eltype(vals.surf) <: Union{Missing, Real}) series[:seriestype] in z_colored_series ? series[:z] : nothing,
series[:line_z],
series[:marker_z],
series[:fill_z],
)
if (typeof(vals) <: AbstractSurface) && (eltype(vals.surf) <: Union{Missing,Real})
zmin, zmax = _update_clims(zmin, zmax, op(vals.surf)...) zmin, zmax = _update_clims(zmin, zmax, op(vals.surf)...)
elseif (vals !== nothing) && (eltype(vals) <: Union{Missing, Real}) elseif (vals !== nothing) && (eltype(vals) <: Union{Missing,Real})
zmin, zmax = _update_clims(zmin, zmax, op(vals)...) zmin, zmax = _update_clims(zmin, zmax, op(vals)...)
end end
end end
@ -60,8 +66,8 @@ function colorbar_style(series::Series)
cbar_fill cbar_fill
elseif iscontour(series) elseif iscontour(series)
cbar_lines cbar_lines
elseif series[:seriestype] (:heatmap,:surface) || elseif series[:seriestype] (:heatmap, :surface) ||
any(series[z] !== nothing for z [:marker_z,:line_z,:fill_z]) any(series[z] !== nothing for z in [:marker_z, :line_z, :fill_z])
cbar_gradient cbar_gradient
else else
nothing nothing
@ -69,7 +75,8 @@ function colorbar_style(series::Series)
end end
hascolorbar(series::Series) = colorbar_style(series) !== nothing hascolorbar(series::Series) = colorbar_style(series) !== nothing
hascolorbar(sp::Subplot) = sp[:colorbar] != :none && any(hascolorbar(s) for s in series_list(sp)) hascolorbar(sp::Subplot) =
sp[:colorbar] != :none && any(hascolorbar(s) for s in series_list(sp))
function get_colorbar_ticks(sp::Subplot; update = true) function get_colorbar_ticks(sp::Subplot; update = true)
if update || !haskey(sp.attr, :colorbar_optimized_ticks) if update || !haskey(sp.attr, :colorbar_optimized_ticks)

View File

@ -12,7 +12,7 @@ compute_angle(v::P2) = (angle = atan(v[2], v[1]); angle < 0 ? 2π - angle : angl
# ------------------------------------------------------------- # -------------------------------------------------------------
struct Shape{X<:Number, Y<:Number} struct Shape{X<:Number,Y<:Number}
x::Vector{X} x::Vector{X}
y::Vector{Y} y::Vector{Y}
# function Shape(x::AVec, y::AVec) # function Shape(x::AVec, y::AVec)
@ -52,75 +52,83 @@ function coords(shapes::AVec{<:Shape})
end end
"get an array of tuples of points on a circle with radius `r`" "get an array of tuples of points on a circle with radius `r`"
partialcircle(start_θ, end_θ, n=20, r=1) = [ partialcircle(start_θ, end_θ, n = 20, r = 1) =
(r*cos(u), r*sin(u)) for u in range(start_θ, stop=end_θ, length=n) [(r * cos(u), r * sin(u)) for u in range(start_θ, stop = end_θ, length = n)]
]
"interleave 2 vectors into each other (like a zipper's teeth)" "interleave 2 vectors into each other (like a zipper's teeth)"
function weave(x, y; ordering=Vector[x, y]) function weave(x, y; ordering = Vector[x, y])
ret = eltype(x)[] ret = eltype(x)[]
done = false done = false
while !done while !done
for o in ordering for o in ordering
try try
push!(ret, popfirst!(o)) push!(ret, popfirst!(o))
catch catch
end end
end
done = isempty(x) && isempty(y)
end end
done = isempty(x) && isempty(y) ret
end
ret
end end
"create a star by weaving together points from an outer and inner circle. `n` is the number of arms" "create a star by weaving together points from an outer and inner circle. `n` is the number of arms"
function makestar(n; offset=-0.5, radius=1.0) function makestar(n; offset = -0.5, radius = 1.0)
z1 = offset * π z1 = offset * π
z2 = z1 + π / (n) z2 = z1 + π / (n)
outercircle = partialcircle(z1, z1 + 2π, n+1, radius) outercircle = partialcircle(z1, z1 + 2π, n + 1, radius)
innercircle = partialcircle(z2, z2 + 2π, n+1, 0.4radius) innercircle = partialcircle(z2, z2 + 2π, n + 1, 0.4radius)
Shape(weave(outercircle, innercircle)) Shape(weave(outercircle, innercircle))
end end
"create a shape by picking points around the unit circle. `n` is the number of point/sides, `offset` is the starting angle" "create a shape by picking points around the unit circle. `n` is the number of point/sides, `offset` is the starting angle"
makeshape(n; offset=-0.5, radius=1.0) = Shape( makeshape(n; offset = -0.5, radius = 1.0) =
partialcircle(offset * π, offset * π + 2π, n+1, radius) Shape(partialcircle(offset * π, offset * π + 2π, n + 1, radius))
)
function makecross(; offset=-0.5, radius=1.0) function makecross(; offset = -0.5, radius = 1.0)
z2 = offset * π z2 = offset * π
z1 = z2 - π/8 z1 = z2 - π / 8
outercircle = partialcircle(z1, z1 + 2π, 9, radius) outercircle = partialcircle(z1, z1 + 2π, 9, radius)
innercircle = partialcircle(z2, z2 + 2π, 5, 0.5radius) innercircle = partialcircle(z2, z2 + 2π, 5, 0.5radius)
Shape(weave(outercircle, innercircle, Shape(
ordering=Vector[outercircle, innercircle, outercircle])) weave(
outercircle,
innercircle,
ordering = Vector[outercircle, innercircle, outercircle],
),
)
end end
from_polar(angle, dist) = P2(dist*cos(angle), dist*sin(angle)) from_polar(angle, dist) = P2(dist * cos(angle), dist * sin(angle))
makearrowhead(angle; h=2.0, w=0.4, tip=from_polar(angle, h)) = Shape( makearrowhead(angle; h = 2.0, w = 0.4, tip = from_polar(angle, h)) = Shape(
P2[(0, 0), from_polar(angle - 0.5π, w) - tip, from_polar(angle + 0.5π, w) - tip, (0, 0)] P2[
(0, 0),
from_polar(angle - 0.5π, w) - tip,
from_polar(angle + 0.5π, w) - tip,
(0, 0),
],
) )
const _shapes = KW( const _shapes = KW(
:circle => makeshape(20), :circle => makeshape(20),
:rect => makeshape(4, offset=-0.25), :rect => makeshape(4, offset = -0.25),
:diamond => makeshape(4), :diamond => makeshape(4),
:utriangle => makeshape(3, offset=0.5), :utriangle => makeshape(3, offset = 0.5),
:dtriangle => makeshape(3, offset=-0.5), :dtriangle => makeshape(3, offset = -0.5),
:rtriangle => makeshape(3, offset=0.0), :rtriangle => makeshape(3, offset = 0.0),
:ltriangle => makeshape(3, offset=1.0), :ltriangle => makeshape(3, offset = 1.0),
:pentagon => makeshape(5), :pentagon => makeshape(5),
:hexagon => makeshape(6), :hexagon => makeshape(6),
:heptagon => makeshape(7), :heptagon => makeshape(7),
:octagon => makeshape(8), :octagon => makeshape(8),
:cross => makecross(offset=-0.25), :cross => makecross(offset = -0.25),
:xcross => makecross(), :xcross => makecross(),
:vline => Shape([(0, 1), (0, -1)]), :vline => Shape([(0, 1), (0, -1)]),
:hline => Shape([(1, 0), (-1, 0)]), :hline => Shape([(1, 0), (-1, 0)]),
) )
for n in 4:8 for n in 4:8
_shapes[Symbol("star$n")] = makestar(n) _shapes[Symbol("star$n")] = makestar(n)
end end
Shape(k::Symbol) = deepcopy(_shapes[k]) Shape(k::Symbol) = deepcopy(_shapes[k])
@ -133,13 +141,13 @@ function center(shape::Shape)
x, y = coords(shape) x, y = coords(shape)
n = length(x) n = length(x)
A, Cx, Cy = 0, 0, 0 A, Cx, Cy = 0, 0, 0
for i 1:n for i in 1:n
ip1 = i == n ? 1 : i+1 ip1 = i == n ? 1 : i + 1
A += x[i] * y[ip1] - x[ip1] * y[i] A += x[i] * y[ip1] - x[ip1] * y[i]
end end
A *= 0.5 A *= 0.5
for i 1:n for i in 1:n
ip1 = i == n ? 1 : i+1 ip1 = i == n ? 1 : i + 1
m = (x[i] * y[ip1] - x[ip1] * y[i]) m = (x[i] * y[ip1] - x[ip1] * y[i])
Cx += (x[i] + x[ip1]) * m Cx += (x[i] + x[ip1]) * m
Cy += (y[i] + y[ip1]) * m Cy += (y[i] + y[ip1]) * m
@ -147,47 +155,43 @@ function center(shape::Shape)
Cx / 6A, Cy / 6A Cx / 6A, Cy / 6A
end end
function scale!(shape::Shape, x::Real, y::Real=x, c=center(shape)) function scale!(shape::Shape, x::Real, y::Real = x, c = center(shape))
sx, sy = coords(shape) sx, sy = coords(shape)
cx, cy = c cx, cy = c
for i eachindex(sx) for i in eachindex(sx)
sx[i] = (sx[i] - cx) * x + cx sx[i] = (sx[i] - cx) * x + cx
sy[i] = (sy[i] - cy) * y + cy sy[i] = (sy[i] - cy) * y + cy
end end
shape shape
end end
scale(shape::Shape, x::Real, y::Real=x, c=center(shape)) = scale!(deepcopy(shape), x, y, c) scale(shape::Shape, x::Real, y::Real = x, c = center(shape)) =
scale!(deepcopy(shape), x, y, c)
"translate a Shape in space" "translate a Shape in space"
function translate!(shape::Shape, x::Real, y::Real=x) function translate!(shape::Shape, x::Real, y::Real = x)
sx, sy = coords(shape) sx, sy = coords(shape)
for i eachindex(sx) for i in eachindex(sx)
sx[i] += x sx[i] += x
sy[i] += y sy[i] += y
end end
shape shape
end end
translate(shape::Shape, x::Real, y::Real=x) = translate!(deepcopy(shape), x, y) translate(shape::Shape, x::Real, y::Real = x) = translate!(deepcopy(shape), x, y)
rotate_x(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) = ( rotate_x(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) =
(x - centerx) * cos(Θ) - (y - centery) * sin(Θ) + centerx ((x - centerx) * cos(Θ) - (y - centery) * sin(Θ) + centerx)
)
rotate_y(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) = ( rotate_y(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) =
(y - centery) * cos(Θ) + (x - centerx) * sin(Θ) + centery ((y - centery) * cos(Θ) + (x - centerx) * sin(Θ) + centery)
)
rotate(x::Real, y::Real, θ::Real, c=center(shape)) = ( rotate(x::Real, y::Real, θ::Real, c = center(shape)) =
rotate_x(x, y, Θ, c...), (rotate_x(x, y, Θ, c...), rotate_y(x, y, Θ, c...))
rotate_y(x, y, Θ, c...),
)
function rotate!(shape::Shape, Θ::Real, c=center(shape)) function rotate!(shape::Shape, Θ::Real, c = center(shape))
x, y = coords(shape) x, y = coords(shape)
for i eachindex(x) for i in eachindex(x)
xi = rotate_x(x[i], y[i], Θ, c...) xi = rotate_x(x[i], y[i], Θ, c...)
yi = rotate_y(x[i], y[i], Θ, c...) yi = rotate_y(x[i], y[i], Θ, c...)
x[i], y[i] = xi, yi x[i], y[i] = xi, yi
@ -196,7 +200,7 @@ function rotate!(shape::Shape, Θ::Real, c=center(shape))
end end
"rotate an object in space" "rotate an object in space"
function rotate(shape::Shape, θ::Real, c=center(shape)) function rotate(shape::Shape, θ::Real, c = center(shape))
x, y = coords(shape) x, y = coords(shape)
x_new = rotate_x.(x, y, θ, c...) x_new = rotate_x.(x, y, θ, c...)
y_new = rotate_y.(x, y, θ, c...) y_new = rotate_y.(x, y, θ, c...)
@ -206,12 +210,12 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
mutable struct Font mutable struct Font
family::AbstractString family::AbstractString
pointsize::Int pointsize::Int
halign::Symbol halign::Symbol
valign::Symbol valign::Symbol
rotation::Float64 rotation::Float64
color::Colorant color::Colorant
end end
""" """
@ -232,71 +236,71 @@ julia> font(family="serif", halign=:center, rotation=45.0)
``` ```
""" """
function font(args...; kw...) function font(args...; kw...)
# defaults # defaults
family = "sans-serif" family = "sans-serif"
pointsize = 14 pointsize = 14
halign = :hcenter halign = :hcenter
valign = :vcenter valign = :vcenter
rotation = 0 rotation = 0
color = colorant"black" color = colorant"black"
for arg in args for arg in args
T = typeof(arg) T = typeof(arg)
if T == Font if T == Font
family = arg.family family = arg.family
pointsize = arg.pointsize pointsize = arg.pointsize
halign = arg.halign halign = arg.halign
valign = arg.valign valign = arg.valign
rotation = arg.rotation rotation = arg.rotation
color = arg.color color = arg.color
elseif arg == :center elseif arg == :center
halign = :hcenter halign = :hcenter
valign = :vcenter valign = :vcenter
elseif arg _haligns elseif arg _haligns
halign = arg halign = arg
elseif arg _valigns elseif arg _valigns
valign = arg valign = arg
elseif T <: Colorant elseif T <: Colorant
color = arg color = arg
elseif T <: Symbol || T <: AbstractString elseif T <: Symbol || T <: AbstractString
try try
color = parse(Colorant, string(arg)) color = parse(Colorant, string(arg))
catch catch
family = string(arg) family = string(arg)
end end
elseif typeof(arg) <: Integer elseif typeof(arg) <: Integer
pointsize = arg pointsize = arg
elseif typeof(arg) <: Real elseif typeof(arg) <: Real
rotation = convert(Float64, arg) rotation = convert(Float64, arg)
else else
@warn "Unused font arg: $arg ($(typeof(arg)))" @warn "Unused font arg: $arg ($(typeof(arg)))"
end
end end
end
for sym in keys(kw) for sym in keys(kw)
if sym == :family if sym == :family
family = string(kw[sym]) family = string(kw[sym])
elseif sym == :pointsize elseif sym == :pointsize
pointsize = kw[sym] pointsize = kw[sym]
elseif sym == :halign elseif sym == :halign
halign = kw[sym] halign = kw[sym]
halign == :center && (halign = :hcenter) halign == :center && (halign = :hcenter)
@assert halign _haligns @assert halign _haligns
elseif sym == :valign elseif sym == :valign
valign = kw[sym] valign = kw[sym]
valign == :center && (valign = :vcenter) valign == :center && (valign = :vcenter)
@assert valign _valigns @assert valign _valigns
elseif sym == :rotation elseif sym == :rotation
rotation = kw[sym] rotation = kw[sym]
elseif sym == :color elseif sym == :color
color = parse(Colorant, kw[sym]) color = parse(Colorant, kw[sym])
else else
@warn "Unused font kwarg: $sym" @warn "Unused font kwarg: $sym"
end
end end
end
Font(family, pointsize, halign, valign, rotation, color) Font(family, pointsize, halign, valign, rotation, color)
end end
function scalefontsize(k::Symbol, factor::Number) function scalefontsize(k::Symbol, factor::Number)
@ -332,7 +336,7 @@ function scalefontsizes()
f = default(k) f = default(k)
if k in keys(_initial_fontsizes) if k in keys(_initial_fontsizes)
factor = f / _initial_fontsizes[k] factor = f / _initial_fontsizes[k]
scalefontsize(k, 1.0/factor) scalefontsize(k, 1.0 / factor)
end end
end end
@ -341,7 +345,7 @@ function scalefontsizes()
if k in keys(_initial_fontsizes) if k in keys(_initial_fontsizes)
f = default(Symbol(letter, k)) f = default(Symbol(letter, k))
factor = f / _initial_fontsizes[k] factor = f / _initial_fontsizes[k]
scalefontsize(Symbol(letter, k), 1.0/factor) scalefontsize(Symbol(letter, k), 1.0 / factor)
end end
end end
end end
@ -351,8 +355,8 @@ resetfontsizes() = scalefontsizes()
"Wrap a string with font info" "Wrap a string with font info"
struct PlotText struct PlotText
str::AbstractString str::AbstractString
font::Font font::Font
end end
PlotText(str) = PlotText(string(str), font()) PlotText(str) = PlotText(string(str), font())
@ -372,10 +376,10 @@ Base.length(t::PlotText) = length(t.str)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
struct Stroke struct Stroke
width width
color color
alpha alpha
style style
end end
""" """
@ -383,67 +387,66 @@ end
Define the properties of the stroke used in plotting lines Define the properties of the stroke used in plotting lines
""" """
function stroke(args...; alpha=nothing) function stroke(args...; alpha = nothing)
width = 1 width = 1
color = :black color = :black
style = :solid style = :solid
for arg in args for arg in args
T = typeof(arg) T = typeof(arg)
# if arg in _allStyles # if arg in _allStyles
if allStyles(arg) if allStyles(arg)
style = arg style = arg
elseif T <: Colorant elseif T <: Colorant
color = arg color = arg
elseif T <: Symbol || T <: AbstractString elseif T <: Symbol || T <: AbstractString
try try
color = parse(Colorant, string(arg)) color = parse(Colorant, string(arg))
catch catch
end end
elseif allAlphas(arg) elseif allAlphas(arg)
alpha = arg alpha = arg
elseif allReals(arg) elseif allReals(arg)
width = arg width = arg
else else
@warn "Unused stroke arg: $arg ($(typeof(arg)))" @warn "Unused stroke arg: $arg ($(typeof(arg)))"
end
end end
end
Stroke(width, color, alpha, style) Stroke(width, color, alpha, style)
end end
struct Brush struct Brush
size # fillrange, markersize, or any other sizey attribute size # fillrange, markersize, or any other sizey attribute
color color
alpha alpha
end end
function brush(args...; alpha=nothing) function brush(args...; alpha = nothing)
size = 1 size = 1
color = :black color = :black
for arg in args for arg in args
T = typeof(arg) T = typeof(arg)
if T <: Colorant if T <: Colorant
color = arg color = arg
elseif T <: Symbol || T <: AbstractString elseif T <: Symbol || T <: AbstractString
try try
color = parse(Colorant, string(arg)) color = parse(Colorant, string(arg))
catch catch
end end
elseif allAlphas(arg) elseif allAlphas(arg)
alpha = arg alpha = arg
elseif allReals(arg) elseif allReals(arg)
size = arg size = arg
else else
@warn "Unused brush arg: $arg ($(typeof(arg)))" @warn "Unused brush arg: $arg ($(typeof(arg)))"
end
end end
end
Brush(size, color, alpha) Brush(size, color, alpha)
end end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -451,7 +454,7 @@ end
mutable struct SeriesAnnotations mutable struct SeriesAnnotations
strs::AVec # the labels/names strs::AVec # the labels/names
font::Font font::Font
baseshape::Union{Shape, AVec{Shape}, Nothing} baseshape::Union{Shape,AVec{Shape},Nothing}
scalefactor::Tuple scalefactor::Tuple
end end
@ -490,10 +493,10 @@ function series_annotations(strs::AVec, args...)
# scale!(s, scalefactor, scalefactor, (0, 0)) # scale!(s, scalefactor, scalefactor, (0, 0))
# end # end
# end # end
SeriesAnnotations([_text_label(s, fnt) for s strs], fnt, shp, scalefactor) SeriesAnnotations([_text_label(s, fnt) for s in strs], fnt, shp, scalefactor)
end end
function series_annotations_shapes!(series::Series, scaletype::Symbol=:pixels) function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels)
anns = series[:series_annotations] anns = series[:series_annotations]
# msw, msh = anns.scalefactor # msw, msh = anns.scalefactor
# ms = series[:markersize] # ms = series[:markersize]
@ -512,7 +515,7 @@ function series_annotations_shapes!(series::Series, scaletype::Symbol=:pixels)
msw, msh = anns.scalefactor msw, msh = anns.scalefactor
msize = Float64[] msize = Float64[]
shapes = Vector{Shape}(undef, length(anns.strs)) shapes = Vector{Shape}(undef, length(anns.strs))
for i eachindex(anns.strs) for i in eachindex(anns.strs)
str = _cycle(anns.strs, i) str = _cycle(anns.strs, i)
# get the width and height of the string (in mm) # get the width and height of the string (in mm)
@ -530,7 +533,8 @@ function series_annotations_shapes!(series::Series, scaletype::Symbol=:pixels)
maxscale = max(xscale, yscale) maxscale = max(xscale, yscale)
push!(msize, maxscale) push!(msize, maxscale)
baseshape = _cycle(anns.baseshape, i) baseshape = _cycle(anns.baseshape, i)
shapes[i] = scale(baseshape, msw*xscale/maxscale, msh*yscale/maxscale, (0, 0)) shapes[i] =
scale(baseshape, msw * xscale / maxscale, msh * yscale / maxscale, (0, 0))
end end
series[:markershape] = shapes series[:markershape] = shapes
series[:markersize] = msize series[:markersize] = msize
@ -544,7 +548,7 @@ mutable struct EachAnn
y y
end end
function Base.iterate(ea::EachAnn, i=1) function Base.iterate(ea::EachAnn, i = 1)
if ea.anns === nothing || isempty(ea.anns.strs) || i > length(ea.y) if ea.anns === nothing || isempty(ea.anns.strs) || i > length(ea.y)
return return
end end
@ -555,7 +559,7 @@ function Base.iterate(ea::EachAnn, i=1)
else else
tmp, ea.anns.font tmp, ea.anns.font
end end
((_cycle(ea.x, i), _cycle(ea.y, i), str, fnt), i+1) ((_cycle(ea.x, i), _cycle(ea.y, i), str, fnt), i + 1)
end end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -566,36 +570,41 @@ annotations(anns) = Any[anns]
annotations(::Nothing) = [] annotations(::Nothing) = []
_annotationfont(sp::Subplot) = Plots.font(; _annotationfont(sp::Subplot) = Plots.font(;
family=sp[:annotationfontfamily], family = sp[:annotationfontfamily],
pointsize=sp[:annotationfontsize], pointsize = sp[:annotationfontsize],
halign=sp[:annotationhalign], halign = sp[:annotationhalign],
valign=sp[:annotationvalign], valign = sp[:annotationvalign],
rotation=sp[:annotationrotation], rotation = sp[:annotationrotation],
color=sp[:annotationcolor], color = sp[:annotationcolor],
) )
_annotation(sp::Subplot, font, lab, pos...; alphabet="abcdefghijklmnopqrstuvwxyz") = ( _annotation(sp::Subplot, font, lab, pos...; alphabet = "abcdefghijklmnopqrstuvwxyz") = (
pos..., pos...,
lab == :auto ? text("($(alphabet[sp[:subplot_index]]))", font) : _text_label(lab, font) lab == :auto ? text("($(alphabet[sp[:subplot_index]]))", font) : _text_label(lab, font),
) )
# Expand arrays of coordinates, positions and labels into individual annotations # Expand arrays of coordinates, positions and labels into individual annotations
# and make sure labels are of type PlotText # and make sure labels are of type PlotText
function process_annotation(sp::Subplot, xs, ys, labs, font=_annotationfont(sp)) function process_annotation(sp::Subplot, xs, ys, labs, font = _annotationfont(sp))
anns = [] anns = []
labs = makevec(labs) labs = makevec(labs)
xlength = length(methods(length, (typeof(xs),))) == 0 ? 1 : length(xs) xlength = length(methods(length, (typeof(xs),))) == 0 ? 1 : length(xs)
ylength = length(methods(length, (typeof(ys),))) == 0 ? 1 : length(ys) ylength = length(methods(length, (typeof(ys),))) == 0 ? 1 : length(ys)
for i in 1:max(xlength, ylength, length(labs)) 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 x = typeof(x) <: TimeType ? Dates.value(x) : x
y = typeof(y) <: TimeType ? Dates.value(y) : y y = typeof(y) <: TimeType ? Dates.value(y) : y
push!(anns, _annotation(sp, font, lab, x, y)) push!(anns, _annotation(sp, font, lab, x, y))
end end
anns anns
end end
function process_annotation(sp::Subplot, positions::Union{AVec{Symbol}, Symbol, Tuple}, labs, font=_annotationfont(sp)) function process_annotation(
sp::Subplot,
positions::Union{AVec{Symbol},Symbol,Tuple},
labs,
font = _annotationfont(sp),
)
anns = [] anns = []
positions, labs = makevec(positions), makevec(labs) positions, labs = makevec(positions), makevec(labs)
for i in 1:max(length(positions), length(labs)) for i in 1:max(length(positions), length(labs))
@ -609,47 +618,52 @@ _relative_position(xmin, xmax, pos::Length{:pct}) = xmin + pos.value * (xmax - x
# Give each annotation coordinates based on specified position # Give each annotation coordinates based on specified position
function locate_annotation( function locate_annotation(
sp::Subplot, pos::Symbol, label::PlotText; sp::Subplot,
position_multiplier=Dict{Symbol, Tuple{Float64, Float64}}( pos::Symbol,
:topleft => (0.1pct, 0.9pct), label::PlotText;
:topcenter => (0.5pct, 0.9pct), position_multiplier = Dict{Symbol,Tuple{Float64,Float64}}(
:topright => (0.9pct, 0.9pct), :topleft => (0.1pct, 0.9pct),
:bottomleft => (0.1pct, 0.1pct), :topcenter => (0.5pct, 0.9pct),
:bottomcenter => (0.5pct, 0.1pct), :topright => (0.9pct, 0.9pct),
:bottomright => (0.9pct, 0.1pct), :bottomleft => (0.1pct, 0.1pct),
) :bottomcenter => (0.5pct, 0.1pct),
:bottomright => (0.9pct, 0.1pct),
),
) )
x, y = position_multiplier[pos] x, y = position_multiplier[pos]
( (
_relative_position(axis_limits(sp, :x)..., x), _relative_position(axis_limits(sp, :x)..., x),
_relative_position(axis_limits(sp, :y)..., y), _relative_position(axis_limits(sp, :y)..., y),
label label,
) )
end end
locate_annotation(sp::Subplot, x, y, label::PlotText) = (x, y, label) locate_annotation(sp::Subplot, x, y, label::PlotText) = (x, y, label)
locate_annotation(sp::Subplot, x, y, z, label::PlotText) = (x, y, z, label) locate_annotation(sp::Subplot, x, y, z, label::PlotText) = (x, y, z, label)
locate_annotation(sp::Subplot, rel::NTuple{2, <:Number}, label::PlotText) = ( locate_annotation(sp::Subplot, rel::NTuple{2,<:Number}, label::PlotText) = (
_relative_position(axis_limits(sp, :x)..., rel[1] * Plots.pct), _relative_position(axis_limits(sp, :x)..., rel[1] * Plots.pct),
_relative_position(axis_limits(sp, :y)..., rel[2] * Plots.pct), _relative_position(axis_limits(sp, :y)..., rel[2] * Plots.pct),
label label,
) )
locate_annotation(sp::Subplot, rel::NTuple{3, <:Number}, label::PlotText) = ( locate_annotation(sp::Subplot, rel::NTuple{3,<:Number}, label::PlotText) = (
_relative_position(axis_limits(sp, :x)..., rel[1] * Plots.pct), _relative_position(axis_limits(sp, :x)..., rel[1] * Plots.pct),
_relative_position(axis_limits(sp, :y)..., rel[2] * Plots.pct), _relative_position(axis_limits(sp, :y)..., rel[2] * Plots.pct),
_relative_position(axis_limits(sp, :z)..., rel[3] * Plots.pct), _relative_position(axis_limits(sp, :z)..., rel[3] * Plots.pct),
label label,
) )
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
"type which represents z-values for colors and sizes (and anything else that might come up)" "type which represents z-values for colors and sizes (and anything else that might come up)"
struct ZValues struct ZValues
values::Vector{Float64} values::Vector{Float64}
zrange::Tuple{Float64, Float64} zrange::Tuple{Float64,Float64}
end end
function zvalues(values::AVec{T}, zrange::Tuple{T, T}=(ignorenan_minimum(values), ignorenan_maximum(values))) where T<:Real function zvalues(
ZValues(collect(float(values)), map(Float64, zrange)) values::AVec{T},
zrange::Tuple{T,T} = (ignorenan_minimum(values), ignorenan_maximum(values)),
) where {T<:Real}
ZValues(collect(float(values)), map(Float64, zrange))
end end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -667,7 +681,6 @@ struct SurfaceFunction <: AbstractSurface
f::Function f::Function
end end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# # I don't want to clash with ValidatedNumerics, but this would be nice: # # I don't want to clash with ValidatedNumerics, but this would be nice:
@ -718,15 +731,14 @@ function arrow(args...)
Arrow(style, side, headlength, headwidth) Arrow(style, side, headlength, headwidth)
end end
# allow for do-block notation which gets called on every valid start/end pair which # allow for do-block notation which gets called on every valid start/end pair which
# we need to draw an arrow # we need to draw an arrow
function add_arrows(func::Function, x::AVec, y::AVec) function add_arrows(func::Function, x::AVec, y::AVec)
for i 2:length(x) for i in 2:length(x)
xyprev = (x[i-1], y[i-1]) xyprev = (x[i - 1], y[i - 1])
xy = (x[i], y[i]) xy = (x[i], y[i])
if ok(xyprev) && ok(xy) if ok(xyprev) && ok(xy)
if i == length(x) || !ok(x[i+1], y[i+1]) if i == length(x) || !ok(x[i + 1], y[i + 1])
# add the arrow from xyprev to xy # add the arrow from xyprev to xy
func(xyprev, xy) func(xyprev, xy)
end end
@ -736,28 +748,27 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
"create a BezierCurve for plotting" "create a BezierCurve for plotting"
mutable struct BezierCurve{T <: GeometryBasics.Point} mutable struct BezierCurve{T<:GeometryBasics.Point}
control_points::Vector{T} control_points::Vector{T}
end end
function (bc::BezierCurve)(t::Real) function (bc::BezierCurve)(t::Real)
p = zero(P2) p = zero(P2)
n = length(bc.control_points)-1 n = length(bc.control_points) - 1
for i in 0:n for i in 0:n
p += bc.control_points[i+1] * binomial(n, i) * (1-t)^(n-i) * t^i p += bc.control_points[i + 1] * binomial(n, i) * (1 - t)^(n - i) * t^i
end end
p p
end end
@deprecate curve_points coords @deprecate curve_points coords
coords(curve::BezierCurve, n::Integer=30; range=[0, 1]) = map( coords(curve::BezierCurve, n::Integer = 30; range = [0, 1]) =
curve, Base.range(first(range), stop=last(range), length=n) map(curve, Base.range(first(range), stop = last(range), length = n))
)
function extrema_plus_buffer(v, buffmult=0.2) function extrema_plus_buffer(v, buffmult = 0.2)
vmin, vmax = ignorenan_extrema(v) vmin, vmax = ignorenan_extrema(v)
vdiff = vmax-vmin vdiff = vmax - vmin
buffer = vdiff * buffmult buffer = vdiff * buffmult
vmin - buffer, vmax + buffer vmin - buffer, vmax + buffer
end end

View File

@ -28,35 +28,33 @@ const _examples = PlotExample[
to generate the animation.) Use command `gif(anim, filename, fps=15)` to save the to generate the animation.) Use command `gif(anim, filename, fps=15)` to save the
animation. animation.
""", """,
[:( [
begin :(
p = plot([sin, cos], zeros(0), leg = false, xlims = (0, 2π), ylims = (-1, 1)) begin
anim = Animation() p = plot(
for x in range(0, stop = 2π, length = 20) [sin, cos],
push!(p, x, Float64[sin(x), cos(x)]) zeros(0),
frame(anim) leg = false,
xlims = (0, 2π),
ylims = (-1, 1),
)
anim = Animation()
for x in range(0, stop = 2π, length = 20)
push!(p, x, Float64[sin(x), cos(x)])
frame(anim)
end
end end
end ),
)], ],
), ),
PlotExample( # 3 PlotExample( # 3
"Parametric plots", "Parametric plots",
"Plot function pair (x(u), y(u)).", "Plot function pair (x(u), y(u)).",
[ [:(
:(
begin begin
plot( plot(sin, x -> sin(2x), 0, 2π, line = 4, leg = false, fill = (0, :orange))
sin,
x -> sin(2x),
0,
2π,
line = 4,
leg = false,
fill = (0, :orange),
)
end end
), )],
],
), ),
PlotExample( # 4 PlotExample( # 4
"Colors", "Colors",
@ -120,7 +118,6 @@ const _examples = PlotExample[
), ),
], ],
), ),
PlotExample( # 6 PlotExample( # 6
"Images", "Images",
"Plot an image. y-axis is set to flipped", "Plot an image. y-axis is set to flipped",
@ -128,7 +125,9 @@ const _examples = PlotExample[
:( :(
begin begin
import FileIO import FileIO
path = download("http://juliaplots.org/PlotReferenceImages.jl/Plots/pyplot/0.7.0/ref1.png") path = download(
"http://juliaplots.org/PlotReferenceImages.jl/Plots/pyplot/0.7.0/ref1.png",
)
img = FileIO.load(path) img = FileIO.load(path)
plot(img) plot(img)
end end
@ -189,12 +188,11 @@ const _examples = PlotExample[
PlotExample( # 11 PlotExample( # 11
"Line types", "Line types",
"", "",
[ [:(
:(
begin begin
linetypes = [:path :steppre :steppost :sticks :scatter] linetypes = [:path :steppre :steppost :sticks :scatter]
n = length(linetypes) n = length(linetypes)
x = Vector[sort(rand(20)) for i = 1:n] x = Vector[sort(rand(20)) for i in 1:n]
y = rand(20, n) y = rand(20, n)
plot( plot(
x, x,
@ -204,8 +202,7 @@ const _examples = PlotExample[
ms = 15, ms = 15,
) )
end end
), )],
],
), ),
PlotExample( # 12 PlotExample( # 12
"Line styles", "Line styles",
@ -236,10 +233,8 @@ const _examples = PlotExample[
[ [
:( :(
begin begin
markers = filter( markers =
m -> m in Plots.supported_markers(), filter(m -> m in Plots.supported_markers(), Plots._shape_keys)
Plots._shape_keys,
)
markers = permutedims(markers) markers = permutedims(markers)
n = length(markers) n = length(markers)
x = range(0, stop = 10, length = n + 2)[2:(end - 1)] x = range(0, stop = 10, length = n + 2)[2:(end - 1)]
@ -270,17 +265,11 @@ const _examples = PlotExample[
PlotExample( # 15 PlotExample( # 15
"Histogram", "Histogram",
"", "",
[ [:(
:(
begin begin
histogram( histogram(randn(1000), bins = :scott, weights = repeat(1:5, outer = 200))
randn(1000),
bins = :scott,
weights = repeat(1:5, outer = 200),
)
end end
), )],
],
), ),
PlotExample( # 16 PlotExample( # 16
"Subplots", "Subplots",
@ -326,15 +315,13 @@ const _examples = PlotExample[
PlotExample( # 18 PlotExample( # 18
"", "",
"", "",
[ [:(
:( begin
begin using Random
using Random Random.seed!(111)
Random.seed!(111) plot!(Plots.fakedata(100, 10))
plot!(Plots.fakedata(100, 10)) end
end )],
)
]
), ),
PlotExample( # 19 PlotExample( # 19
"Open/High/Low/Close", "Open/High/Low/Close",
@ -357,8 +344,7 @@ const _examples = PlotExample[
bot[i] + hgt[i], bot[i] + hgt[i],
bot[i], bot[i],
closepct[i] * hgt[i] + bot[i], closepct[i] * hgt[i] + bot[i],
) ) for i in 1:n
for i = 1:n
] ]
ohlc(y) ohlc(y)
end end
@ -396,11 +382,7 @@ const _examples = PlotExample[
) )
annotate!([ annotate!([
(5, y[5], ("this is #5", 16, :red, :center)), (5, y[5], ("this is #5", 16, :red, :center)),
( (10, y[10], ("this is #10", :right, 20, "courier")),
10,
y[10],
("this is #10", :right, 20, "courier"),
),
]) ])
scatter!( scatter!(
range(2, stop = 8, length = 6), range(2, stop = 8, length = 6),
@ -579,8 +561,8 @@ const _examples = PlotExample[
"", "",
[:( [:(
begin begin
xs = [string("x", i) for i = 1:10] xs = [string("x", i) for i in 1:10]
ys = [string("y", i) for i = 1:4] ys = [string("y", i) for i in 1:4]
z = float((1:4) * reshape(1:10, 1, :)) z = float((1:4) * reshape(1:10, 1, :))
heatmap(xs, ys, z, aspect_ratio = 1) heatmap(xs, ys, z, aspect_ratio = 1)
end end
@ -615,12 +597,7 @@ const _examples = PlotExample[
begin begin
import RDatasets import RDatasets
singers = RDatasets.dataset("lattice", "singer") singers = RDatasets.dataset("lattice", "singer")
@df singers violin( @df singers violin(:VoicePart, :Height, line = 0, fill = (0.2, :blue))
:VoicePart,
:Height,
line = 0,
fill = (0.2, :blue),
)
@df singers boxplot!( @df singers boxplot!(
:VoicePart, :VoicePart,
:Height, :Height,
@ -648,11 +625,7 @@ const _examples = PlotExample[
anim = Animation() anim = Animation()
for x in range(1, stop = 2π, length = 20) for x in range(1, stop = 2π, length = 20)
plot(push!( plot(push!(p, x, Float64[sin(x), cos(x), atan(x), cos(x), log(x)]))
p,
x,
Float64[sin(x), cos(x), atan(x), cos(x), log(x)],
))
frame(anim) frame(anim)
end end
end end
@ -677,18 +650,8 @@ const _examples = PlotExample[
10 => ones(40), 10 => ones(40),
-10 => ones(40), -10 => ones(40),
) )
b = spdiagm( b = spdiagm(0 => 1:50, 1 => 1:49, -1 => 1:49, 10 => 1:40, -10 => 1:40)
0 => 1:50, plot(spy(a), spy(b), title = ["Unique nonzeros" "Different nonzeros"])
1 => 1:49,
-1 => 1:49,
10 => 1:40,
-10 => 1:40,
)
plot(
spy(a),
spy(b),
title = ["Unique nonzeros" "Different nonzeros"],
)
end end
), ),
], ],
@ -753,51 +716,37 @@ const _examples = PlotExample[
You can use the `line_z` and `marker_z` properties to associate a color with You can use the `line_z` and `marker_z` properties to associate a color with
each line segment or marker in the plot. each line segment or marker in the plot.
""", """,
[ [:(
:( begin
begin t = range(0, stop = 1, length = 100)
t = range(0, stop = 1, length = 100) θ = 6π .* t
θ = 6π .* t x = t .* cos.(θ)
x = t .* cos.(θ) y = t .* sin.(θ)
y = t .* sin.(θ) p1 = plot(x, y, line_z = t, linewidth = 3, legend = false)
p1 = plot(x, y, line_z = t, linewidth = 3, legend = false) p2 = scatter(x, y, marker_z = +, color = :bluesreds, legend = false)
p2 = scatter( plot(p1, p2)
x, end
y, )],
marker_z = +,
color = :bluesreds,
legend = false,
)
plot(p1, p2)
end
),
],
), ),
PlotExample( # 36 PlotExample( # 36
"Portfolio Composition maps", "Portfolio Composition maps",
""" """
see: http://stackoverflow.com/a/37732384/5075246 see: http://stackoverflow.com/a/37732384/5075246
""", """,
[ [:(
:( begin
begin using Random
using Random Random.seed!(111)
Random.seed!(111) tickers = ["IBM", "Google", "Apple", "Intel"]
tickers = ["IBM", "Google", "Apple", "Intel"] N = 10
N = 10 D = length(tickers)
D = length(tickers) weights = rand(N, D)
weights = rand(N, D) weights ./= sum(weights, dims = 2)
weights ./= sum(weights, dims = 2) returns = sort!((1:N) + D * randn(N))
returns = sort!((1:N) + D * randn(N))
portfoliocomposition( portfoliocomposition(weights, returns, labels = permutedims(tickers))
weights, end
returns, )],
labels = permutedims(tickers),
)
end
),
],
), ),
PlotExample( # 37 PlotExample( # 37
"Ribbons", "Ribbons",
@ -810,10 +759,7 @@ const _examples = PlotExample[
:( :(
begin begin
plot( plot(
plot( plot(0:10; ribbon = (LinRange(0, 2, 11), LinRange(0, 1, 11))),
0:10;
ribbon = (LinRange(0, 2, 11), LinRange(0, 1, 11)),
),
plot(0:10; ribbon = 0:0.5:5), plot(0:10; ribbon = 0:0.5:5),
plot(0:10; ribbon = sqrt), plot(0:10; ribbon = sqrt),
plot(0:10; ribbon = 1), plot(0:10; ribbon = 1),
@ -878,11 +824,7 @@ const _examples = PlotExample[
plot!([(0, 0), (0, 0.9), (2, 0.9), (3, 1), (4, 0.9), (80, 0)]) plot!([(0, 0), (0, 0.9), (2, 0.9), (3, 1), (4, 0.9), (80, 0)])
plot!([(0, 0), (0, 0.9), (3, 0.9), (4, 1), (5, 0.9), (80, 0)]) plot!([(0, 0), (0, 0.9), (3, 0.9), (4, 1), (5, 0.9), (80, 0)])
plot!([(0, 0), (0, 0.9), (4, 0.9), (5, 1), (6, 0.9), (80, 0)]) plot!([(0, 0), (0, 0.9), (4, 0.9), (5, 1), (6, 0.9), (80, 0)])
lens!( lens!([1, 6], [0.9, 1.1], inset = (1, bbox(0.5, 0.0, 0.4, 0.4)))
[1, 6],
[0.9, 1.1],
inset = (1, bbox(0.5, 0.0, 0.4, 0.4)),
)
end end
end, end,
], ],
@ -890,17 +832,15 @@ const _examples = PlotExample[
PlotExample( # 41 PlotExample( # 41
"Array Types", "Array Types",
"Plots supports different `Array` types that follow the `AbstractArray` interface, like `StaticArrays` and `OffsetArrays.`", "Plots supports different `Array` types that follow the `AbstractArray` interface, like `StaticArrays` and `OffsetArrays.`",
[ [quote
quote begin
begin using StaticArrays, OffsetArrays
using StaticArrays, OffsetArrays sv = SVector{10}(rand(10))
sv = SVector{10}(rand(10)) ov = OffsetVector(rand(10), -2)
ov = OffsetVector(rand(10), -2) plot([sv, ov], label = ["StaticArray" "OffsetArray"])
plot([sv, ov], label = ["StaticArray" "OffsetArray"]) plot!(3ov, ribbon = ov, label = "OffsetArray ribbon")
plot!(3ov, ribbon=ov, label="OffsetArray ribbon") end
end end],
end,
],
), ),
PlotExample( # 42 PlotExample( # 42
"Setting defaults and font arguments", "Setting defaults and font arguments",
@ -916,7 +856,7 @@ const _examples = PlotExample[
tickfont = (12, :orange), tickfont = (12, :orange),
guide = "x", guide = "x",
framestyle = :zerolines, framestyle = :zerolines,
yminorgrid = true yminorgrid = true,
) )
plot( plot(
[sin, cos], [sin, cos],
@ -935,29 +875,25 @@ const _examples = PlotExample[
PlotExample( # 43 PlotExample( # 43
"Heatmap with DateTime axis", "Heatmap with DateTime axis",
"", "",
[ [quote
quote begin
begin using Dates
using Dates z = rand(5, 5)
z = rand(5, 5) x = DateTime.(2016:2020)
x = DateTime.(2016:2020) y = 1:5
y = 1:5 heatmap(x, y, z)
heatmap(x, y, z) end
end end],
end,
],
), ),
PlotExample( # 44 PlotExample( # 44
"Linked axes", "Linked axes",
"", "",
[ [quote
quote begin
begin x = -5:0.1:5
x = -5:0.1:5 plot(plot(x, x -> x^2), plot(x, x -> sin(x)), layout = 2, link = :y)
plot(plot(x, x->x^2), plot(x, x->sin(x)), layout = 2, link = :y) end
end end],
end,
],
), ),
PlotExample( # 45 PlotExample( # 45
"Error bars and array type recipes", "Error bars and array type recipes",
@ -972,8 +908,21 @@ const _examples = PlotExample[
value(m::Measurement) = m.val value(m::Measurement) = m.val
uncertainty(m::Measurement) = m.err uncertainty(m::Measurement) = m.err
@recipe function f(::Type{T}, m::T) where T <: AbstractArray{<:Measurement} @recipe function f(
if !(get(plotattributes, :seriestype, :path) in [:contour, :contourf, :contour3d, :heatmap, :surface, :wireframe, :image]) ::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) error_sym = Symbol(plotattributes[:letter], :error)
plotattributes[error_sym] = uncertainty.(m) plotattributes[error_sym] = uncertainty.(m)
end end
@ -983,14 +932,14 @@ const _examples = PlotExample[
x = Measurement.(10sort(rand(10)), rand(10)) x = Measurement.(10sort(rand(10)), rand(10))
y = Measurement.(10sort(rand(10)), rand(10)) y = Measurement.(10sort(rand(10)), rand(10))
z = Measurement.(10sort(rand(10)), rand(10)) z = Measurement.(10sort(rand(10)), rand(10))
surf = Measurement.((1:10) .* (1:10)', rand(10,10)) surf = Measurement.((1:10) .* (1:10)', rand(10, 10))
plot( plot(
scatter(x, [x y]), scatter(x, [x y]),
scatter(x, y, z), scatter(x, y, z),
heatmap(x, y, surf), heatmap(x, y, surf),
wireframe(x, y, surf), wireframe(x, y, surf),
legend = :topleft legend = :topleft,
) )
end end
end, end,
@ -1003,9 +952,9 @@ const _examples = PlotExample[
using GeometryBasics using GeometryBasics
using Distributions using Distributions
d = MvNormal([1.0 0.75; 0.75 2.0]) d = MvNormal([1.0 0.75; 0.75 2.0])
plot([(1,2),(3,2),(2,1),(2,3)]) plot([(1, 2), (3, 2), (2, 1), (2, 3)])
scatter!(Point2.(eachcol(rand(d,1000))), alpha=0.25) scatter!(Point2.(eachcol(rand(d, 1000))), alpha = 0.25)
end] end],
), ),
PlotExample( # 47 PlotExample( # 47
"Mesh3d", "Mesh3d",
@ -1019,22 +968,29 @@ const _examples = PlotExample[
:( :(
begin begin
# specify the vertices # specify the vertices
x=[0, 1, 2, 0] x = [0, 1, 2, 0]
y=[0, 0, 1, 2] y = [0, 0, 1, 2]
z=[0, 2, 0, 1] z = [0, 2, 0, 1]
# specify the triangles # specify the triangles
# every column is one triangle, # every column is one triangle,
# where the values denote the indices of the vertices of the triangle # where the values denote the indices of the vertices of the triangle
i=[0, 0, 0, 1] i = [0, 0, 0, 1]
j=[1, 2, 3, 2] j = [1, 2, 3, 2]
k=[2, 3, 1, 3] k = [2, 3, 1, 3]
# the four triangles gives above give a tetrahedron # the four triangles gives above give a tetrahedron
mesh3d( mesh3d(
x, y, z; connections=(i, j, k), x,
title="triangles", xlabel="x", ylabel="y", zlabel="z", y,
legend=:none, margin=2Plots.mm z;
connections = (i, j, k),
title = "triangles",
xlabel = "x",
ylabel = "y",
zlabel = "z",
legend = :none,
margin = 2Plots.mm,
) )
end end
), ),
@ -1043,75 +999,90 @@ const _examples = PlotExample[
PlotExample( # 48 PlotExample( # 48
"Vectors of markershapes and segments", "Vectors of markershapes and segments",
"", "",
[quote [
using Base.Iterators: cycle, take quote
using Base.Iterators: cycle, take
yv = ones(9) yv = ones(9)
ys = [1; 1; NaN; ones(6)] ys = [1; 1; NaN; ones(6)]
y = 5 .- [yv 2ys 3yv 4ys] y = 5 .- [yv 2ys 3yv 4ys]
plt_color_rows = plot( plt_color_rows = plot(
y, y,
seriestype = [:path :path :scatter :scatter], seriestype = [:path :path :scatter :scatter],
markershape = collect(take(cycle((:utriangle, :rect)), 9)), markershape = collect(take(cycle((:utriangle, :rect)), 9)),
markersize = 8, markersize = 8,
color = collect(take(cycle((:red, :black)), 9)) color = collect(take(cycle((:red, :black)), 9)),
) )
plt_z_cols = plot( plt_z_cols = plot(
y, y,
markershape = [:utriangle :x :circle :square], markershape = [:utriangle :x :circle :square],
markersize = [5 10 10 5], markersize = [5 10 10 5],
marker_z = [5 4 3 2], marker_z = [5 4 3 2],
line_z = [1 3 3 1], line_z = [1 3 3 1],
linewidth = [1 10 5 1] linewidth = [1 10 5 1],
) )
plot(plt_color_rows, plt_z_cols) plot(plt_color_rows, plt_z_cols)
end] end,
],
), ),
PlotExample( # 49 PlotExample( # 49
"Polar heatmaps", "Polar heatmaps",
"", "",
[quote [quote
x = range(0, 2π, length=9) x = range(0, 2π, length = 9)
y = 0:4 y = 0:4
z = (1:4) .+ (1:8)' z = (1:4) .+ (1:8)'
heatmap(x, y, z, projection = :polar) heatmap(x, y, z, projection = :polar)
end] end],
), ),
PlotExample( # 50 PlotExample( # 50
"3D surface with axis guides", "3D surface with axis guides",
"", "",
[quote [
f(x,a) = 1/x + a*x^2 quote
xs = collect(0.1:0.05:2.0); f(x, a) = 1 / x + a * x^2
as = collect(0.2:0.1:2.0); xs = collect(0.1:0.05:2.0)
as = collect(0.2:0.1:2.0)
x_grid = [x for x in xs for y in as]; x_grid = [x for x in xs for y in as]
a_grid = [y for x in xs for y in as]; a_grid = [y for x in xs for y in as]
plot(x_grid, a_grid, f.(x_grid,a_grid), plot(
st = :surface, x_grid,
xlabel = "longer xlabel", a_grid,
ylabel = "longer ylabel", f.(x_grid, a_grid),
zlabel = "longer zlabel", st = :surface,
) xlabel = "longer xlabel",
end] ylabel = "longer ylabel",
zlabel = "longer zlabel",
)
end,
],
), ),
PlotExample( # 51 PlotExample( # 51
"Images with custom axes", "Images with custom axes",
"", "",
[quote [
using Plots quote
using TestImages using Plots
img = testimage("lighthouse") using TestImages
img = testimage("lighthouse")
# plot the image reversing the first dimension and setting yflip = false # plot the image reversing the first dimension and setting yflip = false
plot([-π, π], [-1, 1], reverse(img, dims=1), yflip=false, aspect_ratio=:none) plot(
# plot other data [-π, π],
plot!(sin, -π, π, lw=3, color=:red) [-1, 1],
end] reverse(img, dims = 1),
yflip = false,
aspect_ratio = :none,
)
# plot other data
plot!(sin, -π, π, lw = 3, color = :red)
end,
],
), ),
PlotExample( # 52 PlotExample( # 52
"3d quiver", "3d quiver",
@ -1119,9 +1090,9 @@ const _examples = PlotExample[
[quote [quote
using Plots using Plots
ϕs = range(-π, π, length=50) ϕs = range(-π, π, length = 50)
θs = range(0, π, length=25) θs = range(0, π, length = 25)
θqs = range(1, π-1, length=25) θqs = range(1, π - 1, length = 25)
x = vec([sin(θ) * cos(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θs)]) x = vec([sin(θ) * cos(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θs)])
y = vec([sin(θ) * sin(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θs)]) y = vec([sin(θ) * sin(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θs)])
@ -1131,25 +1102,43 @@ const _examples = PlotExample[
v = 0.1 * vec([sin(θ) * sin(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θqs)]) v = 0.1 * vec([sin(θ) * sin(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θqs)])
w = 0.1 * vec([cos(θ) for (ϕ, θ) in Iterators.product(ϕs, θqs)]) w = 0.1 * vec([cos(θ) for (ϕ, θ) in Iterators.product(ϕs, θqs)])
quiver(x,y,z, quiver=(u,v,w)) quiver(x, y, z, quiver = (u, v, w))
end] end],
), ),
PlotExample( # 53 PlotExample( # 53
"Step Types", "Step Types",
"A comparison of the various step-like `seriestype`s", "A comparison of the various step-like `seriestype`s",
[ [
:( :(
begin begin
x = 1:5 x = 1:5
y = [1, 2, 3, 2, 1] y = [1, 2, 3, 2, 1]
default(shape=:circle) default(shape = :circle)
plot( plot(
plot(x, y, markershape=:circle, seriestype=:steppre, label="steppre"), plot(
plot(x, y, markershape=:circle, seriestype=:stepmid, label="stepmid"), x,
plot(x, y, markershape=:circle, seriestype=:steppost, label="steppost"), y,
layout=(3,1) markershape = :circle,
) seriestype = :steppre,
end label = "steppre",
),
plot(
x,
y,
markershape = :circle,
seriestype = :stepmid,
label = "stepmid",
),
plot(
x,
y,
markershape = :circle,
seriestype = :steppost,
label = "steppost",
),
layout = (3, 1),
)
end
), ),
], ],
), ),
@ -1158,22 +1147,22 @@ const _examples = PlotExample[
"", "",
[ [
:( :(
begin begin
plot( plot(
rand(10, 4), rand(10, 4),
layout=4, layout = 4,
xguide="x guide", xguide = "x guide",
yguide="y guide", yguide = "y guide",
xguidefonthalign=[:left :right :right :left], xguidefonthalign = [:left :right :right :left],
yguidefontvalign=[:top :bottom :bottom :top], yguidefontvalign = [:top :bottom :bottom :top],
xguideposition=:top, xguideposition = :top,
yguideposition=[:right :left :right :left], yguideposition = [:right :left :right :left],
ymirror=[false true true false], ymirror = [false true true false],
xmirror=[false false true true], xmirror = [false false true true],
legend=false, legend = false,
seriestype=[:bar :scatter :path :stepmid] seriestype = [:bar :scatter :path :stepmid],
) )
end end
), ),
], ],
), ),
@ -1182,44 +1171,60 @@ const _examples = PlotExample[
"", "",
[ [
:( :(
begin begin
using LinearAlgebra using LinearAlgebra
scalefontsizes(.4) scalefontsizes(0.4)
x, y = collect(-6:0.5:10), collect(-8:0.5:8) x, y = collect(-6:0.5:10), collect(-8:0.5:8)
args = x, y, (x, y) -> sinc(norm([x, y]) / π) args = x, y, (x, y) -> sinc(norm([x, y]) / π)
kwargs = Dict(:xlabel=>"x", :ylabel=>"y", :zlabel=>"z", :grid=>true, :minorgrid=>true) kwargs = Dict(
:xlabel => "x",
:ylabel => "y",
:zlabel => "z",
:grid => true,
:minorgrid => true,
)
plots = [wireframe(args..., title = "wire"; kwargs...)] plots = [wireframe(args..., title = "wire"; kwargs...)]
for ax (:x, :y, :z) for ax in (:x, :y, :z)
push!(plots, wireframe( push!(
args..., plots,
title = "wire-flip-$ax", wireframe(
xflip = ax == :x, args...,
yflip = ax == :y, title = "wire-flip-$ax",
zflip = ax == :z; xflip = ax == :x,
kwargs..., yflip = ax == :y,
)) zflip = ax == :z;
kwargs...,
),
)
end
for ax in (:x, :y, :z)
push!(
plots,
wireframe(
args...,
title = "wire-mirror-$ax",
xmirror = ax == :x,
ymirror = ax == :y,
zmirror = ax == :z;
kwargs...,
),
)
end
plt = plot(
plots...,
layout = (@layout [_ ° _; ° ° °; ° ° °]),
margin = 0Plots.px,
)
resetfontsizes()
plt
end end
for ax (:x, :y, :z)
push!(plots, wireframe(
args...,
title = "wire-mirror-$ax",
xmirror = ax == :x,
ymirror = ax == :y,
zmirror = ax == :z;
kwargs...,
))
end
plt = plot(plots..., layout=(@layout [_ ° _; ° ° °; ° ° °]), margin=0Plots.px)
resetfontsizes()
plt
end
), ),
], ],
), ),
@ -1252,8 +1257,6 @@ _backend_skips = Dict(
], ],
) )
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
# make and display one plot # make and display one plot

View File

@ -1,8 +1,10 @@
# --------------------------------------------------------- # ---------------------------------------------------------
# A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert # A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert
_fileio_load(@nospecialize(filename::AbstractString)) = FileIO.load(filename::AbstractString) _fileio_load(@nospecialize(filename::AbstractString)) =
_fileio_save(@nospecialize(filename::AbstractString), @nospecialize(x)) = FileIO.save(filename::AbstractString, x) FileIO.load(filename::AbstractString)
_fileio_save(@nospecialize(filename::AbstractString), @nospecialize(x)) =
FileIO.save(filename::AbstractString, x)
function _show_pdfbackends(io::IO, ::MIME"image/png", plt::Plot) function _show_pdfbackends(io::IO, ::MIME"image/png", plt::Plot)
fn = tempname() fn = tempname()
@ -21,4 +23,5 @@ function _show_pdfbackends(io::IO, ::MIME"image/png", plt::Plot)
write(io, read(open(pngfn), String)) write(io, read(open(pngfn), String))
end end
const PDFBackends = Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend} const PDFBackends =
Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend}

View File

@ -1,15 +1,14 @@
const use_local_dependencies = Ref(false) const use_local_dependencies = Ref(false)
const use_local_plotlyjs = Ref(false) const use_local_plotlyjs = Ref(false)
function _init_ijulia_plotting() function _init_ijulia_plotting()
# IJulia is more stable with local file # IJulia is more stable with local file
use_local_plotlyjs[] = plotly_local_file_path[] === nothing ? false : isfile(plotly_local_file_path[]) use_local_plotlyjs[] =
plotly_local_file_path[] === nothing ? false : isfile(plotly_local_file_path[])
ENV["MPLBACKEND"] = "Agg" ENV["MPLBACKEND"] = "Agg"
end end
""" """
Add extra jupyter mimetypes to display_dict based on the plot backed. Add extra jupyter mimetypes to display_dict based on the plot backed.
@ -20,22 +19,17 @@ frontends like jupyterlab and nteract.
_ijulia__extra_mime_info!(plt::Plot, out::Dict) = out _ijulia__extra_mime_info!(plt::Plot, out::Dict) = out
function _ijulia__extra_mime_info!(plt::Plot{PlotlyJSBackend}, out::Dict) function _ijulia__extra_mime_info!(plt::Plot{PlotlyJSBackend}, out::Dict)
out["application/vnd.plotly.v1+json"] = Dict( out["application/vnd.plotly.v1+json"] =
:data => plotly_series(plt), Dict(:data => plotly_series(plt), :layout => plotly_layout(plt))
:layout => plotly_layout(plt)
)
out out
end end
function _ijulia__extra_mime_info!(plt::Plot{PlotlyBackend}, out::Dict) function _ijulia__extra_mime_info!(plt::Plot{PlotlyBackend}, out::Dict)
out["application/vnd.plotly.v1+json"] = Dict( out["application/vnd.plotly.v1+json"] =
:data => plotly_series(plt), Dict(:data => plotly_series(plt), :layout => plotly_layout(plt))
:layout => plotly_layout(plt)
)
out out
end end
function _ijulia_display_dict(plt::Plot) function _ijulia_display_dict(plt::Plot)
output_type = Symbol(plt.attr[:html_output_format]) output_type = Symbol(plt.attr[:html_output_format])
if output_type == :auto if output_type == :auto

View File

@ -1,8 +1,7 @@
using REPL using REPL
using Scratch using Scratch
const plotly_local_file_path = Ref{Union{Nothing, String}}(nothing) const plotly_local_file_path = Ref{Union{Nothing,String}}(nothing)
function _plots_defaults() function _plots_defaults()
if isdefined(Main, :PLOTS_DEFAULTS) if isdefined(Main, :PLOTS_DEFAULTS)
@ -12,7 +11,6 @@ function _plots_defaults()
end end
end end
function __init__() function __init__()
user_defaults = _plots_defaults() user_defaults = _plots_defaults()
if haskey(user_defaults, :theme) if haskey(user_defaults, :theme)
@ -21,14 +19,27 @@ function __init__()
default(; user_defaults...) default(; user_defaults...)
end end
insert!(Base.Multimedia.displays, findlast(x -> x isa Base.TextDisplay || x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1, PlotsDisplay()) insert!(
Base.Multimedia.displays,
findlast(
x -> x isa Base.TextDisplay || x isa REPL.REPLDisplay,
Base.Multimedia.displays,
) + 1,
PlotsDisplay(),
)
atreplinit(i -> begin atreplinit(
while PlotsDisplay() in Base.Multimedia.displays i -> begin
popdisplay(PlotsDisplay()) while PlotsDisplay() in Base.Multimedia.displays
end popdisplay(PlotsDisplay())
insert!(Base.Multimedia.displays, findlast(x -> x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1, PlotsDisplay()) end
end) insert!(
Base.Multimedia.displays,
findlast(x -> x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1,
PlotsDisplay(),
)
end,
)
@require HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" begin @require HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" begin
fn = joinpath(@__DIR__, "backends", "hdf5.jl") fn = joinpath(@__DIR__, "backends", "hdf5.jl")
@ -84,9 +95,13 @@ function __init__()
end end
if get(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL", "false") == "true" if get(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL", "false") == "true"
global plotly_local_file_path[] = joinpath(@get_scratch!("plotly"), _plotly_min_js_filename) global plotly_local_file_path[] =
joinpath(@get_scratch!("plotly"), _plotly_min_js_filename)
if !isfile(plotly_local_file_path[]) if !isfile(plotly_local_file_path[])
download("https://cdn.plot.ly/$(_plotly_min_js_filename)", plotly_local_file_path[]) download(
"https://cdn.plot.ly/$(_plotly_min_js_filename)",
plotly_local_file_path[],
)
end end
use_local_plotlyjs[] = true use_local_plotlyjs[] = true
@ -94,8 +109,8 @@ function __init__()
use_local_dependencies[] = use_local_plotlyjs[] use_local_dependencies[] = use_local_plotlyjs[]
@require FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" begin @require FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" begin
_show(io::IO, mime::MIME"image/png", plt::Plot{<:PDFBackends}) = _show_pdfbackends(io, mime, plt) _show(io::IO, mime::MIME"image/png", plt::Plot{<:PDFBackends}) =
_show_pdfbackends(io, mime, plt)
end end
end end

View File

@ -18,16 +18,16 @@ ispositive(m::Measure) = m.value > 0
# union together bounding boxes # union together bounding boxes
function Base.:+(bb1::BoundingBox, bb2::BoundingBox) function Base.:+(bb1::BoundingBox, bb2::BoundingBox)
# empty boxes don't change the union # empty boxes don't change the union
ispositive(width(bb1)) || return bb2 ispositive(width(bb1)) || return bb2
ispositive(height(bb1)) || return bb2 ispositive(height(bb1)) || return bb2
ispositive(width(bb2)) || return bb1 ispositive(width(bb2)) || return bb1
ispositive(height(bb2)) || return bb1 ispositive(height(bb2)) || return bb1
l = min(left(bb1), left(bb2)) l = min(left(bb1), left(bb2))
t = min(top(bb1), top(bb2)) t = min(top(bb1), top(bb2))
r = max(right(bb1), right(bb2)) r = max(right(bb1), right(bb2))
b = max(bottom(bb1), bottom(bb2)) b = max(bottom(bb1), bottom(bb2))
BoundingBox(l, t, r-l, b-t) BoundingBox(l, t, r - l, b - t)
end end
# this creates a bounding box in the parent's scope, where the child bounding box # this creates a bounding box in the parent's scope, where the child bounding box
@ -53,7 +53,7 @@ end
# convert a bounding box from absolute coords to percentages... # convert a bounding box from absolute coords to percentages...
# returns an array of percentages of figure size: [left, bottom, width, height] # returns an array of percentages of figure size: [left, bottom, width, height]
function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true) function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true)
mms = Float64[f(bb).value for f in (left,bottom,width,height)] mms = Float64[f(bb).value for f in (left, bottom, width, height)]
if flipy if flipy
mms[2] = figh.value - mms[2] # flip y when origin in bottom-left mms[2] = figh.value - mms[2] # flip y when origin in bottom-left
end end
@ -61,7 +61,10 @@ function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true)
end end
function Base.show(io::IO, bbox::BoundingBox) function Base.show(io::IO, bbox::BoundingBox)
print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}") print(
io,
"BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}",
)
end end
# ----------------------------------------------------------- # -----------------------------------------------------------
@ -83,12 +86,11 @@ function resolve_mixed(mix::MixedMeasures, sp::Subplot, letter::Symbol)
end end
if pct != 0 if pct != 0
amin, amax = axis_limits(sp, letter) amin, amax = axis_limits(sp, letter)
xy += pct * (amax-amin) xy += pct * (amax - amin)
end end
xy xy
end end
# ----------------------------------------------------------- # -----------------------------------------------------------
# AbstractLayout # AbstractLayout
@ -160,7 +162,8 @@ parent_bbox(layout::AbstractLayout) = bbox(parent(layout))
# padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout)) # padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout))
update_position!(layout::AbstractLayout) = nothing update_position!(layout::AbstractLayout) = nothing
update_child_bboxes!(layout::AbstractLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) = nothing update_child_bboxes!(layout::AbstractLayout, minimum_perimeter = [0mm, 0mm, 0mm, 0mm]) =
nothing
left(layout::AbstractLayout) = left(bbox(layout)) left(layout::AbstractLayout) = left(bbox(layout))
top(layout::AbstractLayout) = top(bbox(layout)) top(layout::AbstractLayout) = top(bbox(layout))
@ -206,7 +209,7 @@ mutable struct EmptyLayout <: AbstractLayout
end end
EmptyLayout(parent = RootLayout(); kw...) = EmptyLayout(parent, defaultbox, KW(kw)) EmptyLayout(parent = RootLayout(); kw...) = EmptyLayout(parent, defaultbox, KW(kw))
Base.size(layout::EmptyLayout) = (0,0) Base.size(layout::EmptyLayout) = (0, 0)
Base.length(layout::EmptyLayout) = 0 Base.length(layout::EmptyLayout) = 0
Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing
@ -235,22 +238,25 @@ columns of different width.
""" """
grid(args...; kw...) = GridLayout(args...; kw...) grid(args...; kw...) = GridLayout(args...; kw...)
function GridLayout(dims...; function GridLayout(
parent = RootLayout(), dims...;
widths = zeros(dims[2]), parent = RootLayout(),
heights = zeros(dims[1]), widths = zeros(dims[2]),
kw...) heights = zeros(dims[1]),
kw...,
)
grid = Matrix{AbstractLayout}(undef, dims...) grid = Matrix{AbstractLayout}(undef, dims...)
layout = GridLayout( layout = GridLayout(
parent, parent,
(20mm, 5mm, 2mm, 10mm), (20mm, 5mm, 2mm, 10mm),
defaultbox, defaultbox,
grid, grid,
Measure[w*pct for w in widths], Measure[w * pct for w in widths],
Measure[h*pct for h in heights], Measure[h * pct for h in heights],
# convert(Vector{Float64}, widths), # convert(Vector{Float64}, widths),
# convert(Vector{Float64}, heights), # convert(Vector{Float64}, heights),
KW(kw)) KW(kw),
)
for i in eachindex(grid) for i in eachindex(grid)
grid[i] = EmptyLayout(layout) grid[i] = EmptyLayout(layout)
end end
@ -259,9 +265,9 @@ end
Base.size(layout::GridLayout) = size(layout.grid) Base.size(layout::GridLayout) = size(layout.grid)
Base.length(layout::GridLayout) = length(layout.grid) Base.length(layout::GridLayout) = length(layout.grid)
Base.getindex(layout::GridLayout, r::Int, c::Int) = layout.grid[r,c] Base.getindex(layout::GridLayout, r::Int, c::Int) = layout.grid[r, c]
function Base.setindex!(layout::GridLayout, v, r::Int, c::Int) function Base.setindex!(layout::GridLayout, v, r::Int, c::Int)
layout.grid[r,c] = v layout.grid[r, c] = v
end end
leftpad(layout::GridLayout) = layout.minpad[1] leftpad(layout::GridLayout) = layout.minpad[1]
@ -269,7 +275,6 @@ toppad(layout::GridLayout) = layout.minpad[2]
rightpad(layout::GridLayout) = layout.minpad[3] rightpad(layout::GridLayout) = layout.minpad[3]
bottompad(layout::GridLayout) = layout.minpad[4] bottompad(layout::GridLayout) = layout.minpad[4]
# here's how this works... first we recursively "update the minimum padding" (which # here's how this works... first we recursively "update the minimum padding" (which
# means to calculate the minimum size needed from the edge of the subplot to plot area) # means to calculate the minimum size needed from the edge of the subplot to plot area)
# for the whole layout tree. then we can compute the "padding borders" of this # for the whole layout tree. then we can compute the "padding borders" of this
@ -281,14 +286,13 @@ bottompad(layout::GridLayout) = layout.minpad[4]
function _update_min_padding!(layout::GridLayout) function _update_min_padding!(layout::GridLayout)
map(_update_min_padding!, layout.grid) map(_update_min_padding!, layout.grid)
layout.minpad = ( layout.minpad = (
maximum(map(leftpad, layout.grid[:,1])), maximum(map(leftpad, layout.grid[:, 1])),
maximum(map(toppad, layout.grid[1,:])), maximum(map(toppad, layout.grid[1, :])),
maximum(map(rightpad, layout.grid[:,end])), maximum(map(rightpad, layout.grid[:, end])),
maximum(map(bottompad, layout.grid[end,:])) maximum(map(bottompad, layout.grid[end, :])),
) )
end end
function update_position!(layout::GridLayout) function update_position!(layout::GridLayout)
map(update_position!, layout.grid) map(update_position!, layout.grid)
end end
@ -307,7 +311,9 @@ function recompute_lengths(v)
end end
leftover = 1.0pct - tot leftover = 1.0pct - tot
if cnt > 1 && leftover.value <= 0 if cnt > 1 && leftover.value <= 0
error("Not enough length left over in layout! v = $v, cnt = $cnt, leftover = $leftover") error(
"Not enough length left over in layout! v = $v, cnt = $cnt, leftover = $leftover",
)
end end
# now fill in the blanks # now fill in the blanks
@ -315,21 +321,21 @@ function recompute_lengths(v)
end end
# recursively compute the bounding boxes for the layout and plotarea (relative to canvas!) # recursively compute the bounding boxes for the layout and plotarea (relative to canvas!)
function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm, 0mm, 0mm, 0mm])
nr, nc = size(layout) nr, nc = size(layout)
# # create a matrix for each minimum padding direction # # create a matrix for each minimum padding direction
# _update_min_padding!(layout) # _update_min_padding!(layout)
minpad_left = map(leftpad, layout.grid) minpad_left = map(leftpad, layout.grid)
minpad_top = map(toppad, layout.grid) minpad_top = map(toppad, layout.grid)
minpad_right = map(rightpad, layout.grid) minpad_right = map(rightpad, layout.grid)
minpad_bottom = map(bottompad, layout.grid) minpad_bottom = map(bottompad, layout.grid)
# get the max horizontal (left and right) padding over columns, # get the max horizontal (left and right) padding over columns,
# and max vertical (bottom and top) padding over rows # and max vertical (bottom and top) padding over rows
# TODO: add extra padding here # TODO: add extra padding here
pad_left = maximum(minpad_left, dims = 1) pad_left = maximum(minpad_left, dims = 1)
pad_top = maximum(minpad_top, dims = 2) pad_top = maximum(minpad_top, dims = 2)
pad_right = maximum(minpad_right, dims = 1) pad_right = maximum(minpad_right, dims = 1)
pad_bottom = maximum(minpad_bottom, dims = 2) pad_bottom = maximum(minpad_bottom, dims = 2)
# make sure the perimeter match the parent # make sure the perimeter match the parent
@ -343,7 +349,7 @@ function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0
total_pad_vertical = sum(pad_top + pad_bottom) total_pad_vertical = sum(pad_top + pad_bottom)
# now we can compute the total plot area in each direction # now we can compute the total plot area in each direction
total_plotarea_horizontal = width(layout) - total_pad_horizontal total_plotarea_horizontal = width(layout) - total_pad_horizontal
total_plotarea_vertical = height(layout) - total_pad_vertical total_plotarea_vertical = height(layout) - total_pad_vertical
# recompute widths/heights # recompute widths/heights
@ -355,19 +361,22 @@ function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0
# denom_h = sum(layout.heights) # denom_h = sum(layout.heights)
# we have all the data we need... lets compute the plot areas and set the bounding boxes # we have all the data we need... lets compute the plot areas and set the bounding boxes
for r=1:nr, c=1:nc for r in 1:nr, c in 1:nc
child = layout[r,c] child = layout[r, c]
# get the top-left corner of this child... the first one is top-left of the parent (i.e. layout) # get the top-left corner of this child... the first one is top-left of the parent (i.e. layout)
child_left = (c == 1 ? left(layout.bbox) : right(layout[r, c-1].bbox)) child_left = (c == 1 ? left(layout.bbox) : right(layout[r, c - 1].bbox))
child_top = (r == 1 ? top(layout.bbox) : bottom(layout[r-1, c].bbox)) child_top = (r == 1 ? top(layout.bbox) : bottom(layout[r - 1, c].bbox))
# compute plot area # compute plot area
plotarea_left = child_left + pad_left[c] plotarea_left = child_left + pad_left[c]
plotarea_top = child_top + pad_top[r] plotarea_top = child_top + pad_top[r]
plotarea_width = total_plotarea_horizontal * layout.widths[c] plotarea_width = total_plotarea_horizontal * layout.widths[c]
plotarea_height = total_plotarea_vertical * layout.heights[r] plotarea_height = total_plotarea_vertical * layout.heights[r]
plotarea!(child, BoundingBox(plotarea_left, plotarea_top, plotarea_width, plotarea_height)) plotarea!(
child,
BoundingBox(plotarea_left, plotarea_top, plotarea_width, plotarea_height),
)
# compute child bbox # compute child bbox
child_width = pad_left[c] + plotarea_width + pad_right[c] child_width = pad_left[c] + plotarea_width + pad_right[c]
@ -377,10 +386,10 @@ function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0
# this is the minimum perimeter as decided by this child's parent, so that # this is the minimum perimeter as decided by this child's parent, so that
# all children on this border have the same value # all children on this border have the same value
min_child_perimeter = [ min_child_perimeter = [
c == 1 ? layout.minpad[1] : pad_left[c], c == 1 ? layout.minpad[1] : pad_left[c],
r == 1 ? layout.minpad[2] : pad_top[r], r == 1 ? layout.minpad[2] : pad_top[r],
c == nc ? layout.minpad[3] : pad_right[c], c == nc ? layout.minpad[3] : pad_right[c],
r == nr ? layout.minpad[4] : pad_bottom[r] r == nr ? layout.minpad[4] : pad_bottom[r],
] ]
# recursively update the child's children # recursively update the child's children
@ -395,12 +404,15 @@ function update_inset_bboxes!(plt::Plot)
p_area = Measures.resolve(plotarea(sp.parent), sp[:relative_bbox]) p_area = Measures.resolve(plotarea(sp.parent), sp[:relative_bbox])
plotarea!(sp, p_area) plotarea!(sp, p_area)
bbox!(sp, bbox( bbox!(
left(p_area) - leftpad(sp), sp,
top(p_area) - toppad(sp), bbox(
width(p_area) + leftpad(sp) + rightpad(sp), left(p_area) - leftpad(sp),
height(p_area) + toppad(sp) + bottompad(sp) top(p_area) - toppad(sp),
)) width(p_area) + leftpad(sp) + rightpad(sp),
height(p_area) + toppad(sp) + bottompad(sp),
),
)
end end
end end
@ -441,7 +453,9 @@ end
function layout_args(plotattributes::AKW, n_override::Integer) function layout_args(plotattributes::AKW, n_override::Integer)
layout, n = layout_args(n_override, get(plotattributes, :layout, n_override)) layout, n = layout_args(n_override, get(plotattributes, :layout, n_override))
if n != n_override if n != n_override
error("When doing layout, n ($n) != n_override ($(n_override)). You're probably trying to force existing plots into a layout that doesn't fit them.") error(
"When doing layout, n ($n) != n_override ($(n_override)). You're probably trying to force existing plots into a layout that doesn't fit them.",
)
end end
layout, n layout, n
end end
@ -451,27 +465,27 @@ function layout_args(n::Integer)
GridLayout(nr, nc), n GridLayout(nr, nc), n
end end
function layout_args(sztup::NTuple{2, Integer}) function layout_args(sztup::NTuple{2,Integer})
nr, nc = sztup nr, nc = sztup
GridLayout(nr, nc), nr*nc GridLayout(nr, nc), nr * nc
end end
layout_args(n_override::Integer, n::Integer) = layout_args(n) layout_args(n_override::Integer, n::Integer) = layout_args(n)
layout_args(n, sztup::NTuple{2, Integer}) = layout_args(sztup) layout_args(n, sztup::NTuple{2,Integer}) = layout_args(sztup)
function layout_args(n, sztup::Tuple{Colon, Integer}) function layout_args(n, sztup::Tuple{Colon,Integer})
nc = sztup[2] nc = sztup[2]
nr = ceil(Int, n / nc) nr = ceil(Int, n / nc)
GridLayout(nr, nc), n GridLayout(nr, nc), n
end end
function layout_args(n, sztup::Tuple{Integer, Colon}) function layout_args(n, sztup::Tuple{Integer,Colon})
nr = sztup[1] nr = sztup[1]
nc = ceil(Int, n / nr) nc = ceil(Int, n / nr)
GridLayout(nr, nc), n GridLayout(nr, nc), n
end end
function layout_args(sztup::NTuple{3, Integer}) function layout_args(sztup::NTuple{3,Integer})
n, nr, nc = sztup n, nr, nc = sztup
nr, nc = compute_gridsize(n, nr, nc) nr, nc = compute_gridsize(n, nr, nc)
GridLayout(nr, nc), n GridLayout(nr, nc), n
@ -488,16 +502,13 @@ layout_args(n_override::Integer, layout::GridLayout) = layout_args(layout)
layout_args(huh) = error("unhandled layout type $(typeof(huh)): $huh") layout_args(huh) = error("unhandled layout type $(typeof(huh)): $huh")
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
function build_layout(args...) function build_layout(args...)
layout, n = layout_args(args...) layout, n = layout_args(args...)
build_layout(layout, n, Array{Plot}(undef, 0)) build_layout(layout, n, Array{Plot}(undef, 0))
end end
# n is the number of subplots... # n is the number of subplots...
function build_layout(layout::GridLayout, n::Integer, plts::AVec{Plot}) function build_layout(layout::GridLayout, n::Integer, plts::AVec{Plot})
nr, nc = size(layout) nr, nc = size(layout)
@ -505,15 +516,15 @@ function build_layout(layout::GridLayout, n::Integer, plts::AVec{Plot})
spmap = SubplotMap() spmap = SubplotMap()
empty = isempty(plts) empty = isempty(plts)
i = 0 i = 0
for r = 1:nr, c = 1:nc for r in 1:nr, c in 1:nc
l = layout[r, c] l = layout[r, c]
if isa(l, EmptyLayout) && !get(l.attr, :blank, false) if isa(l, EmptyLayout) && !get(l.attr, :blank, false)
if empty if empty
# initialize the inner subplots recursively # initialize the inner subplots recursively
sp = Subplot(backend(), parent=layout) sp = Subplot(backend(), parent = layout)
layout[r, c] = sp layout[r, c] = sp
push!(subplots, sp) push!(subplots, sp)
spmap[attr(l,:label,gensym())] = sp spmap[attr(l, :label, gensym())] = sp
inc = 1 inc = 1
else else
# build a layout from a list of existing Plot objects # build a layout from a list of existing Plot objects
@ -524,19 +535,19 @@ function build_layout(layout::GridLayout, n::Integer, plts::AVec{Plot})
inc = length(plt.subplots) inc = length(plt.subplots)
end end
if get(l.attr, :width, :auto) != :auto if get(l.attr, :width, :auto) != :auto
layout.widths[c] = attr(l,:width) layout.widths[c] = attr(l, :width)
end end
if get(l.attr, :height, :auto) != :auto if get(l.attr, :height, :auto) != :auto
layout.heights[r] = attr(l,:height) layout.heights[r] = attr(l, :height)
end end
i += inc i += inc
elseif isa(l, GridLayout) elseif isa(l, GridLayout)
# sub-grid # sub-grid
if get(l.attr, :width, :auto) != :auto if get(l.attr, :width, :auto) != :auto
layout.widths[c] = attr(l,:width) layout.widths[c] = attr(l, :width)
end end
if get(l.attr, :height, :auto) != :auto if get(l.attr, :height, :auto) != :auto
layout.heights[r] = attr(l,:height) layout.heights[r] = attr(l, :height)
end end
l, sps, m = build_layout(l, n - i, plts) l, sps, m = build_layout(l, n - i, plts)
append!(subplots, sps) append!(subplots, sps)
@ -551,7 +562,6 @@ function build_layout(layout::GridLayout, n::Integer, plts::AVec{Plot})
layout, subplots, spmap layout, subplots, spmap
end end
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
# @layout macro # @layout macro
@ -563,9 +573,9 @@ function add_layout_pct!(kw::AKW, v::Expr, idx::Integer, nidx::Integer)
if length(v.args) == 3 && isa(num, Number) if length(v.args) == 3 && isa(num, Number)
units = v.args[3] units = v.args[3]
if units == :h if units == :h
return kw[:h] = num*pct return kw[:h] = num * pct
elseif units == :w elseif units == :w
return kw[:w] = num*pct return kw[:w] = num * pct
elseif units in (:pct, :px, :mm, :cm, :inch) elseif units in (:pct, :px, :mm, :cm, :inch)
idx == 1 && (kw[:w] = v) idx == 1 && (kw[:w] = v)
(idx == 2 || nidx == 1) && (kw[:h] = v) (idx == 2 || nidx == 1) && (kw[:h] = v)
@ -578,23 +588,29 @@ end
function add_layout_pct!(kw::AKW, v::Number, idx::Integer) function add_layout_pct!(kw::AKW, v::Number, idx::Integer)
# kw[idx == 1 ? :w : :h] = v*pct # kw[idx == 1 ? :w : :h] = v*pct
idx == 1 && (kw[:w] = v*pct) idx == 1 && (kw[:w] = v * pct)
(idx == 2 || nidx == 1) && (kw[:h] = v*pct) (idx == 2 || nidx == 1) && (kw[:h] = v * pct)
end end
isrow(v) = isa(v, Expr) && v.head in (:hcat,:row) isrow(v) = isa(v, Expr) && v.head in (:hcat, :row)
iscol(v) = isa(v, Expr) && v.head == :vcat iscol(v) = isa(v, Expr) && v.head == :vcat
rowsize(v) = isrow(v) ? length(v.args) : 1 rowsize(v) = isrow(v) ? length(v.args) : 1
function create_grid(expr::Expr) function create_grid(expr::Expr)
if iscol(expr) if iscol(expr)
create_grid_vcat(expr) create_grid_vcat(expr)
elseif isrow(expr) elseif isrow(expr)
:(let cell = GridLayout(1, $(length(expr.args))) :(
$([:(cell[1,$i] = $(create_grid(v))) for (i,v) in enumerate(expr.args)]...) let cell = GridLayout(1, $(length(expr.args)))
cell $(
end) [
:(cell[1, $i] = $(create_grid(v))) for
(i, v) in enumerate(expr.args)
]...
)
cell
end
)
elseif expr.head == :curly elseif expr.head == :curly
create_grid_curly(expr) create_grid_curly(expr)
@ -613,39 +629,58 @@ function create_grid_vcat(expr::Expr)
nr = length(expr.args) nr = length(expr.args)
nc = rmin nc = rmin
body = Expr(:block) body = Expr(:block)
for r=1:nr for r in 1:nr
arg = expr.args[r] arg = expr.args[r]
if isrow(arg) if isrow(arg)
for (c,item) in enumerate(arg.args) for (c, item) in enumerate(arg.args)
push!(body.args, :(cell[$r,$c] = $(create_grid(item)))) push!(body.args, :(cell[$r, $c] = $(create_grid(item))))
end end
else else
push!(body.args, :(cell[$r,1] = $(create_grid(arg)))) push!(body.args, :(cell[$r, 1] = $(create_grid(arg))))
end end
end end
:(let cell = GridLayout($nr, $nc) :(
$body let cell = GridLayout($nr, $nc)
cell $body
end) cell
end
)
else else
# otherwise just build one row at a time # otherwise just build one row at a time
:(let cell = GridLayout($(length(expr.args)), 1) :(
$([:(cell[$i,1] = $(create_grid(v))) for (i,v) in enumerate(expr.args)]...) let cell = GridLayout($(length(expr.args)), 1)
cell $(
end) [
:(cell[$i, 1] = $(create_grid(v))) for
(i, v) in enumerate(expr.args)
]...
)
cell
end
)
end end
end end
function create_grid_curly(expr::Expr) function create_grid_curly(expr::Expr)
kw = KW() kw = KW()
for (i,arg) in enumerate(expr.args[2:end]) for (i, arg) in enumerate(expr.args[2:end])
add_layout_pct!(kw, arg, i, length(expr.args)-1) add_layout_pct!(kw, arg, i, length(expr.args) - 1)
end end
s = expr.args[1] s = expr.args[1]
if isa(s, Expr) && s.head == :call && s.args[1] == :grid if isa(s, Expr) && s.head == :call && s.args[1] == :grid
create_grid(:(grid($(s.args[2:end]...), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto)))))) create_grid(
:(grid(
$(s.args[2:end]...),
width = $(get(kw, :w, QuoteNode(:auto))),
height = $(get(kw, :h, QuoteNode(:auto))),
)),
)
elseif isa(s, Symbol) elseif isa(s, Symbol)
:(EmptyLayout(label = $(QuoteNode(s)), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto))))) :(EmptyLayout(
label = $(QuoteNode(s)),
width = $(get(kw, :w, QuoteNode(:auto))),
height = $(get(kw, :h, QuoteNode(:auto))),
))
else else
error("Unknown use of curly brackets: $expr") error("Unknown use of curly brackets: $expr")
end end
@ -659,14 +694,13 @@ macro layout(mat::Expr)
create_grid(mat) create_grid(mat)
end end
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# make all reference the same axis extrema/values. # make all reference the same axis extrema/values.
# merge subplot lists. # merge subplot lists.
function link_axes!(axes::Axis...) function link_axes!(axes::Axis...)
a1 = axes[1] a1 = axes[1]
for i=2:length(axes) for i in 2:length(axes)
a2 = axes[i] a2 = axes[i]
expand_extrema!(a1, ignorenan_extrema(a2)) expand_extrema!(a1, ignorenan_extrema(a2))
for k in (:extrema, :discrete_values, :continuous_values, :discrete_map) for k in (:extrema, :discrete_values, :continuous_values, :discrete_map)
@ -688,8 +722,8 @@ function link_subplots(a::AbstractArray{AbstractLayout}, axissym::Symbol)
for l in a for l in a
if isa(l, Subplot) if isa(l, Subplot)
push!(subplots, l) push!(subplots, l)
elseif isa(l, GridLayout) && size(l) == (1,1) elseif isa(l, GridLayout) && size(l) == (1, 1)
push!(subplots, l[1,1]) push!(subplots, l[1, 1])
end end
end end
subplots subplots
@ -705,20 +739,19 @@ function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol)
end end
# don't do anything for most layout types # don't do anything for most layout types
function link_axes!(l::AbstractLayout, link::Symbol) function link_axes!(l::AbstractLayout, link::Symbol) end
end
# process a GridLayout, recursively linking axes according to the link symbol # process a GridLayout, recursively linking axes according to the link symbol
function link_axes!(layout::GridLayout, link::Symbol) function link_axes!(layout::GridLayout, link::Symbol)
nr, nc = size(layout) nr, nc = size(layout)
if link in (:x, :both) if link in (:x, :both)
for c=1:nc for c in 1:nc
link_axes!(layout.grid[:,c], :xaxis) link_axes!(layout.grid[:, c], :xaxis)
end end
end end
if link in (:y, :both) if link in (:y, :both)
for r=1:nr for r in 1:nr
link_axes!(layout.grid[r,:], :yaxis) link_axes!(layout.grid[r, :], :yaxis)
end end
end end
if link == :square if link == :square
@ -744,8 +777,9 @@ end
"Adds a new, empty subplot overlayed on top of `sp`, with a mirrored y-axis and linked x-axis." "Adds a new, empty subplot overlayed on top of `sp`, with a mirrored y-axis and linked x-axis."
function twinx(sp::Subplot) function twinx(sp::Subplot)
plot!(sp.plt, plot!(
inset = (sp[:subplot_index], bbox(0,0,1,1)), sp.plt,
inset = (sp[:subplot_index], bbox(0, 0, 1, 1)),
right_margin = sp[:right_margin], right_margin = sp[:right_margin],
left_margin = sp[:left_margin], left_margin = sp[:left_margin],
top_margin = sp[:top_margin], top_margin = sp[:top_margin],
@ -756,7 +790,7 @@ function twinx(sp::Subplot)
twinsp[:yaxis][:grid] = false twinsp[:yaxis][:grid] = false
twinsp[:xaxis][:showaxis] = false twinsp[:xaxis][:showaxis] = false
twinsp[:yaxis][:mirror] = true twinsp[:yaxis][:mirror] = true
twinsp[:background_color_inside] = RGBA{Float64}(0,0,0,0) twinsp[:background_color_inside] = RGBA{Float64}(0, 0, 0, 0)
link_axes!(sp[:xaxis], twinsp[:xaxis]) link_axes!(sp[:xaxis], twinsp[:xaxis])
twinsp twinsp
end end

View File

@ -8,20 +8,19 @@ Return `(x,y)` at an angle `theta` degrees from
`xmax`, `ymin`, `ymax`). `xmax`, `ymin`, `ymax`).
""" """
function legend_pos_from_angle(theta, xmin, xcenter, xmax, ymin, ycenter, ymax) function legend_pos_from_angle(theta, xmin, xcenter, xmax, ymin, ycenter, ymax)
(s,c) = sincosd(theta) (s, c) = sincosd(theta)
x = c < 0 ? (xmin-xcenter)/c : (xmax-xcenter)/c x = c < 0 ? (xmin - xcenter) / c : (xmax - xcenter) / c
y = s < 0 ? (ymin-ycenter)/s : (ymax-ycenter)/s y = s < 0 ? (ymin - ycenter) / s : (ymax - ycenter) / s
A = min(x,y) A = min(x, y)
return (xcenter + A*c, ycenter + A*s) return (xcenter + A * c, ycenter + A * s)
end end
""" """
Split continuous range `[-1,1]` evenly into an integer `[1,2,3]` Split continuous range `[-1,1]` evenly into an integer `[1,2,3]`
""" """
function legend_anchor_index(x) function legend_anchor_index(x)
x<-1//3 && return 1 x < -1 // 3 && return 1
x<1//3 && return 2 x < 1 // 3 && return 2
return 3 return 3
end end
@ -32,28 +31,28 @@ so :topleft exactly corresponds to (45, :inner) etc.
If `leg` is a (::Real,::Real) tuple, keep it as is. If `leg` is a (::Real,::Real) tuple, keep it as is.
""" """
legend_angle(leg::Real) = (leg,:inner) legend_angle(leg::Real) = (leg, :inner)
legend_angle(leg::Tuple{S,T}) where {S<:Real,T<:Real} = leg legend_angle(leg::Tuple{S,T}) where {S<:Real,T<:Real} = leg
legend_angle(leg::Tuple{S,Symbol}) where S<:Real = leg legend_angle(leg::Tuple{S,Symbol}) where {S<:Real} = leg
legend_angle(leg::Symbol) = get( legend_angle(leg::Symbol) = get(
( (
topleft = (135,:inner), topleft = (135, :inner),
top = (90, :inner), top = (90, :inner),
topright = (45, :inner), topright = (45, :inner),
left = (180,:inner), left = (180, :inner),
right = (0, :inner), right = (0, :inner),
bottomleft = (225,:inner), bottomleft = (225, :inner),
bottom = (270,:inner), bottom = (270, :inner),
bottomright = (315,:inner), bottomright = (315, :inner),
outertopleft = (135,:outer), outertopleft = (135, :outer),
outertop = (90, :outer), outertop = (90, :outer),
outertopright = (45, :outer), outertopright = (45, :outer),
outerleft = (180,:outer), outerleft = (180, :outer),
outerright = (0, :outer), outerright = (0, :outer),
outerbottomleft = (225,:outer), outerbottomleft = (225, :outer),
outerbottom = (270,:outer), outerbottom = (270, :outer),
outerbottomright = (315,:outer), outerbottomright = (315, :outer),
), ),
leg, leg,
(45, :inner) (45, :inner),
) )

View File

@ -73,7 +73,6 @@ function txt(plt::Plot, fn::AbstractString)
end end
txt(fn::AbstractString) = txt(current(), fn) txt(fn::AbstractString) = txt(current(), fn)
# ---------------------------------------------------------------- # ----------------------------------------------------------------
const _savemap = Dict( const _savemap = Dict(
@ -128,7 +127,6 @@ function savefig(plt::Plot, fn::AbstractString)
end end
savefig(fn::AbstractString) = savefig(current(), fn) savefig(fn::AbstractString) = savefig(current(), fn)
# --------------------------------------------------------- # ---------------------------------------------------------
""" """
@ -186,8 +184,8 @@ function _show(io::IO, ::MIME"text/html", plt::Plot)
end end
# delegate showable to _show instead # delegate showable to _show instead
function Base.showable(m::M, plt::P) where {M <: MIME, P <: Plot} function Base.showable(m::M, plt::P) where {M<:MIME,P<:Plot}
return hasmethod(_show, Tuple{IO, M, P}) return hasmethod(_show, Tuple{IO,M,P})
end end
function _display(plt::Plot) function _display(plt::Plot)
@ -224,7 +222,6 @@ Base.show(io::IO, m::MIME"application/prs.juno.plotpane+html", plt::Plot) =
"Close all open gui windows of the current backend" "Close all open gui windows of the current backend"
closeall() = closeall(backend()) closeall() = closeall(backend())
# function html_output_format(fmt) # function html_output_format(fmt)
# if fmt == "png" # if fmt == "png"
# @eval function Base.show(io::IO, ::MIME"text/html", plt::Plot) # @eval function Base.show(io::IO, ::MIME"text/html", plt::Plot)
@ -241,7 +238,6 @@ closeall() = closeall(backend())
# #
# html_output_format("svg") # html_output_format("svg")
# --------------------------------------------------------- # ---------------------------------------------------------
# Atom PlotPane # Atom PlotPane
# --------------------------------------------------------- # ---------------------------------------------------------

View File

@ -30,7 +30,6 @@ function RecipesPipeline.warn_on_recipe_aliases!(
end end
end end
## Grouping ## Grouping
RecipesPipeline.splittable_attribute(plt::Plot, key, val::SeriesAnnotations, len) = RecipesPipeline.splittable_attribute(plt::Plot, key, val::SeriesAnnotations, len) =
@ -41,7 +40,6 @@ function RecipesPipeline.split_attribute(plt::Plot, key, val::SeriesAnnotations,
return SeriesAnnotations(split_strs, val.font, val.baseshape, val.scalefactor) return SeriesAnnotations(split_strs, val.font, val.baseshape, val.scalefactor)
end end
## Preprocessing attributes ## Preprocessing attributes
function RecipesPipeline.preprocess_axis_args!(plt::Plot, plotattributes, letter) function RecipesPipeline.preprocess_axis_args!(plt::Plot, plotattributes, letter)
# Fix letter for seriestypes that are x only but data gets passed as y # Fix letter for seriestypes that are x only but data gets passed as y
@ -62,7 +60,6 @@ RecipesPipeline.is_axis_attribute(plt::Plot, attr) = is_axis_attr_noletter(attr)
RecipesPipeline.is_subplot_attribute(plt::Plot, attr) = is_subplot_attr(attr) # in src/args.jl RecipesPipeline.is_subplot_attribute(plt::Plot, attr) = is_subplot_attr(attr) # in src/args.jl
## User recipes ## User recipes
function RecipesPipeline.process_userrecipe!(plt::Plot, kw_list, kw) function RecipesPipeline.process_userrecipe!(plt::Plot, kw_list, kw)
@ -84,13 +81,15 @@ function _preprocess_userrecipe(kw::AKW)
# map marker_z if it's a Function # map marker_z if it's a Function
if isa(get(kw, :marker_z, nothing), Function) if isa(get(kw, :marker_z, nothing), Function)
# TODO: should this take y and/or z as arguments? # TODO: should this take y and/or z as arguments?
kw[:marker_z] = isa(kw[:z], Nothing) ? map(kw[:marker_z], kw[:x], kw[:y]) : kw[:marker_z] =
isa(kw[:z], Nothing) ? map(kw[:marker_z], kw[:x], kw[:y]) :
map(kw[:marker_z], kw[:x], kw[:y], kw[:z]) map(kw[:marker_z], kw[:x], kw[:y], kw[:z])
end end
# map line_z if it's a Function # map line_z if it's a Function
if isa(get(kw, :line_z, nothing), Function) if isa(get(kw, :line_z, nothing), Function)
kw[:line_z] = isa(kw[:z], Nothing) ? map(kw[:line_z], kw[:x], kw[:y]) : kw[:line_z] =
isa(kw[:z], Nothing) ? map(kw[:line_z], kw[:x], kw[:y]) :
map(kw[:line_z], kw[:x], kw[:y], kw[:z]) map(kw[:line_z], kw[:x], kw[:y], kw[:z])
end end
@ -140,15 +139,12 @@ function _add_smooth_kw(kw_list::Vector{KW}, kw::AKW)
end end
end end
RecipesPipeline.get_axis_limits(plt::Plot, letter) = axis_limits(plt[1], letter, false) RecipesPipeline.get_axis_limits(plt::Plot, letter) = axis_limits(plt[1], letter, false)
## Plot recipes ## Plot recipes
RecipesPipeline.type_alias(plt::Plot) = get(_typeAliases, st, st) RecipesPipeline.type_alias(plt::Plot) = get(_typeAliases, st, st)
## Plot setup ## Plot setup
function RecipesPipeline.plot_setup!(plt::Plot, plotattributes, kw_list) function RecipesPipeline.plot_setup!(plt::Plot, plotattributes, kw_list)
@ -159,18 +155,18 @@ end
function RecipesPipeline.process_sliced_series_attributes!(plt::Plots.Plot, kw_list) function RecipesPipeline.process_sliced_series_attributes!(plt::Plots.Plot, kw_list)
# swap errors # swap errors
err_inds = findall(kw -> get(kw, :seriestype, :path) in (:xerror, :yerror, :zerror), kw_list) err_inds =
findall(kw -> get(kw, :seriestype, :path) in (:xerror, :yerror, :zerror), kw_list)
for ind in err_inds for ind in err_inds
if get(kw_list[ind-1],:seriestype,:path) == :scatter if get(kw_list[ind - 1], :seriestype, :path) == :scatter
tmp = copy(kw_list[ind]) tmp = copy(kw_list[ind])
kw_list[ind] = copy(kw_list[ind-1]) kw_list[ind] = copy(kw_list[ind - 1])
kw_list[ind-1] = tmp kw_list[ind - 1] = tmp
end end
end end
return nothing return nothing
end end
# TODO: Should some of this logic be moved to RecipesPipeline? # TODO: Should some of this logic be moved to RecipesPipeline?
function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW}) function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
# merge in anything meant for the Plot # merge in anything meant for the Plot
@ -193,7 +189,6 @@ function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
plt.init = true plt.init = true
end end
# handle inset subplots # handle inset subplots
insets = plt[:inset_subplots] insets = plt[:inset_subplots]
if insets !== nothing if insets !== nothing
@ -226,7 +221,7 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
# Subplot/Axis attributes set by a user/series recipe apply only to the # Subplot/Axis attributes set by a user/series recipe apply only to the
# Subplot object which they belong to. # Subplot object which they belong to.
# TODO: allow matrices to still apply to all subplots # TODO: allow matrices to still apply to all subplots
sp_attrs = Dict{Subplot, Any}() sp_attrs = Dict{Subplot,Any}()
for kw in kw_list for kw in kw_list
# get the Subplot object to which the series belongs. # get the Subplot object to which the series belongs.
sps = get(kw, :subplot, :auto) sps = get(kw, :subplot, :auto)
@ -294,13 +289,13 @@ function _add_plot_title!(plt)
if plot_title != "" if plot_title != ""
the_layout = plt.layout the_layout = plt.layout
vspan = plt[:plot_titlevspan] vspan = plt[:plot_titlevspan]
plt.layout = grid(2, 1, heights=(vspan, 1 - vspan)) plt.layout = grid(2, 1, heights = (vspan, 1 - vspan))
plt.layout.grid[1, 1] = subplot = Subplot(plt.backend, parent=plt.layout[1, 1]) plt.layout.grid[1, 1] = subplot = Subplot(plt.backend, parent = plt.layout[1, 1])
plt.layout.grid[2, 1] = the_layout plt.layout.grid[2, 1] = the_layout
subplot.plt = plt subplot.plt = plt
# propagate arguments plt[:plot_titleXXX] --> subplot[:titleXXX] # propagate arguments plt[:plot_titleXXX] --> subplot[:titleXXX]
for sym filter(x -> startswith(string(x), "plot_title"), keys(_plot_defaults)) for sym in filter(x -> startswith(string(x), "plot_title"), keys(_plot_defaults))
subplot[Symbol(string(sym)[length("plot_") + 1:end])] = plt[sym] subplot[Symbol(string(sym)[(length("plot_") + 1):end])] = plt[sym]
end end
top = plt.backend isa PyPlotBackend ? nothing : 0mm top = plt.backend isa PyPlotBackend ? nothing : 0mm
bot = 0mm bot = 0mm
@ -346,7 +341,10 @@ function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where {T}
st = _override_seriestype_check(plotattributes, st) st = _override_seriestype_check(plotattributes, st)
# change to a 3d projection for this subplot? # change to a 3d projection for this subplot?
if RecipesPipeline.needs_3d_axes(st) || (st == :quiver && plotattributes[:z] !== nothing) if (
RecipesPipeline.needs_3d_axes(st) ||
(st == :quiver && plotattributes[:z] !== nothing)
)
sp.attr[:projection] = "3d" sp.attr[:projection] = "3d"
end end
@ -362,8 +360,10 @@ function _override_seriestype_check(plotattributes::AKW, st::Symbol)
# do we want to override the series type? # do we want to override the series type?
if !RecipesPipeline.is3d(st) && !(st in (:contour, :contour3d, :quiver)) if !RecipesPipeline.is3d(st) && !(st in (:contour, :contour3d, :quiver))
z = plotattributes[:z] z = plotattributes[:z]
if z !== nothing && if (
(size(plotattributes[:x]) == size(plotattributes[:y]) == size(z)) z !== nothing &&
(size(plotattributes[:x]) == size(plotattributes[:y]) == size(z))
)
st = (st == :scatter ? :scatter3d : :path3d) st = (st == :scatter ? :scatter3d : :path3d)
plotattributes[:seriestype] = st plotattributes[:seriestype] = st
end end
@ -374,7 +374,7 @@ end
function needs_any_3d_axes(sp::Subplot) function needs_any_3d_axes(sp::Subplot)
any( any(
RecipesPipeline.needs_3d_axes( RecipesPipeline.needs_3d_axes(
_override_seriestype_check(s.plotattributes, s.plotattributes[:seriestype]) _override_seriestype_check(s.plotattributes, s.plotattributes[:seriestype]),
) for s in series_list(sp) ) for s in series_list(sp)
) )
end end
@ -399,9 +399,9 @@ end
function _add_the_series(plt, sp, plotattributes) function _add_the_series(plt, sp, plotattributes)
extra_kwargs = warn_on_unsupported_args(plt.backend, plotattributes) extra_kwargs = warn_on_unsupported_args(plt.backend, plotattributes)
if (kw = plt[:extra_kwargs]) isa AbstractDict if (kw = plt[:extra_kwargs]) isa AbstractDict
plt[:extra_plot_kwargs] = get(kw,:plot,KW()) plt[:extra_plot_kwargs] = get(kw, :plot, KW())
sp[:extra_kwargs] = get(kw,:subplot, KW()) sp[:extra_kwargs] = get(kw, :subplot, KW())
plotattributes[:extra_kwargs] = get(kw,:series,KW()) plotattributes[:extra_kwargs] = get(kw, :series, KW())
elseif plt[:extra_kwargs] == :plot elseif plt[:extra_kwargs] == :plot
plt[:extra_plot_kwargs] = extra_kwargs plt[:extra_plot_kwargs] = extra_kwargs
elseif plt[:extra_kwargs] == :subplot elseif plt[:extra_kwargs] == :subplot

View File

@ -1,6 +1,6 @@
mutable struct CurrentPlot mutable struct CurrentPlot
nullableplot::Union{AbstractPlot, Nothing} nullableplot::Union{AbstractPlot,Nothing}
end end
const CURRENT_PLOT = CurrentPlot(nothing) const CURRENT_PLOT = CurrentPlot(nothing)
@ -20,17 +20,20 @@ current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = plot)
# --------------------------------------------------------- # ---------------------------------------------------------
Base.string(plt::Plot) = "Plot{$(plt.backend) n=$(plt.n)}" Base.string(plt::Plot) = "Plot{$(plt.backend) n=$(plt.n)}"
Base.print(io::IO, plt::Plot) = print(io, string(plt)) Base.print(io::IO, plt::Plot) = print(io, string(plt))
function Base.show(io::IO, plt::Plot) function Base.show(io::IO, plt::Plot)
print(io, string(plt)) print(io, string(plt))
sp_ekwargs = getindex.(plt.subplots, :extra_kwargs) sp_ekwargs = getindex.(plt.subplots, :extra_kwargs)
s_ekwargs = getindex.(plt.series_list, :extra_kwargs) s_ekwargs = getindex.(plt.series_list, :extra_kwargs)
if isempty(plt[:extra_plot_kwargs]) && all(isempty, sp_ekwargs) && all(isempty, s_ekwargs) if (
isempty(plt[:extra_plot_kwargs]) &&
all(isempty, sp_ekwargs) &&
all(isempty, s_ekwargs)
)
return return
end end
print(io,"\nCaptured extra kwargs:\n") print(io, "\nCaptured extra kwargs:\n")
do_show = true do_show = true
for (key, value) in plt[:extra_plot_kwargs] for (key, value) in plt[:extra_plot_kwargs]
do_show && println(io, " Plot:") do_show && println(io, " Plot:")
@ -62,7 +65,6 @@ convertSeriesIndex(plt::Plot, n::Int) = n
# --------------------------------------------------------- # ---------------------------------------------------------
""" """
The main plot command. Use `plot` to create a new plot object, and `plot!` to add to an existing one: The main plot command. Use `plot` to create a new plot object, and `plot!` to add to an existing one:
@ -91,7 +93,8 @@ end
# build a new plot from existing plots # build a new plot from existing plots
# note: we split into plt1, plt2 and plts_tail so we can dispatch correctly # note: we split into plt1, plt2 and plts_tail so we can dispatch correctly
plot(plt1::Plot, plt2::Plot, plts_tail::Plot...; kw...) = plot!(deepcopy(plt1), deepcopy(plt2), deepcopy.(plts_tail)...; kw...) plot(plt1::Plot, plt2::Plot, plts_tail::Plot...; kw...) =
plot!(deepcopy(plt1), deepcopy(plt2), deepcopy.(plts_tail)...; kw...)
function plot!(plt1::Plot, plt2::Plot, plts_tail::Plot...; kw...) function plot!(plt1::Plot, plt2::Plot, plts_tail::Plot...; kw...)
@nospecialize @nospecialize
plotattributes = KW(kw) plotattributes = KW(kw)
@ -102,8 +105,8 @@ function plot!(plt1::Plot, plt2::Plot, plts_tail::Plot...; kw...)
plts = Array{Plot}(undef, n) plts = Array{Plot}(undef, n)
plts[1] = plt1 plts[1] = plt1
plts[2] = plt2 plts[2] = plt2
for (i,plt) in enumerate(plts_tail) for (i, plt) in enumerate(plts_tail)
plts[i+2] = plt plts[i + 2] = plt
end end
# compute the layout # compute the layout
@ -130,9 +133,9 @@ function plot!(plt1::Plot, plt2::Plot, plts_tail::Plot...; kw...)
plt.init = true plt.init = true
series_attr = KW() series_attr = KW()
for (k,v) in plotattributes for (k, v) in plotattributes
if is_series_attr(k) if is_series_attr(k)
series_attr[k] = pop!(plotattributes,k) series_attr[k] = pop!(plotattributes, k)
end end
end end
@ -163,7 +166,7 @@ function plot!(plt1::Plot, plt2::Plot, plts_tail::Plot...; kw...)
_add_plot_title!(plt) _add_plot_title!(plt)
# first apply any args for the subplots # first apply any args for the subplots
for (idx,sp) in enumerate(plt.subplots) for (idx, sp) in enumerate(plt.subplots)
_update_subplot_args(plt, sp, plotattributes, idx, false) _update_subplot_args(plt, sp, plotattributes, idx, false)
end end
@ -173,8 +176,6 @@ function plot!(plt1::Plot, plt2::Plot, plts_tail::Plot...; kw...)
plt plt
end end
# this adds to the current plot, or creates a new plot if none are current # this adds to the current plot, or creates a new plot if none are current
function plot!(args...; kw...) function plot!(args...; kw...)
@nospecialize @nospecialize
@ -210,14 +211,13 @@ function _plot!(plt::Plot, plotattributes, args)
return plt return plt
end end
# we're getting ready to display/output. prep for layout calcs, then update # we're getting ready to display/output. prep for layout calcs, then update
# the plot object after # the plot object after
function prepare_output(plt::Plot) function prepare_output(plt::Plot)
_before_layout_calcs(plt) _before_layout_calcs(plt)
w, h = plt.attr[:size] w, h = plt.attr[:size]
plt.layout.bbox = BoundingBox(0mm, 0mm, w*px, h*px) plt.layout.bbox = BoundingBox(0mm, 0mm, w * px, h * px)
# One pass down and back up the tree to compute the minimum padding # One pass down and back up the tree to compute the minimum padding
# of the children on the perimeter. This is an backend callback. # of the children on the perimeter. This is an backend callback.
@ -229,9 +229,10 @@ function prepare_output(plt::Plot)
# spedific to :plot_title see _add_plot_title! # spedific to :plot_title see _add_plot_title!
force_minpad = get(plt, :force_minpad, ()) force_minpad = get(plt, :force_minpad, ())
if !isempty(force_minpad) if !isempty(force_minpad)
for i eachindex(plt.layout.grid) for i in eachindex(plt.layout.grid)
plt.layout.grid[i].minpad = Tuple( plt.layout.grid[i].minpad = Tuple(
i === nothing ? j : i for (i, j) zip(force_minpad, plt.layout.grid[i].minpad) i === nothing ? j : i for
(i, j) in zip(force_minpad, plt.layout.grid[i].minpad)
) )
end end
end end

View File

@ -1,8 +1,10 @@
const _attribute_defaults = Dict(:Series => _series_defaults, const _attribute_defaults = Dict(
:Subplot => _subplot_defaults, :Series => _series_defaults,
:Plot => _plot_defaults, :Subplot => _subplot_defaults,
:Axis => _axis_defaults) :Plot => _plot_defaults,
:Axis => _axis_defaults,
)
attrtypes() = join(keys(_attribute_defaults), ", ") attrtypes() = join(keys(_attribute_defaults), ", ")
attributes(attrtype::Symbol) = sort(collect(keys(_attribute_defaults[attrtype]))) attributes(attrtype::Symbol) = sort(collect(keys(_attribute_defaults[attrtype])))
@ -21,7 +23,9 @@ Look up the properties of a Plots attribute, or specify an attribute type. Call
The information is the same as that given on https://docs.juliaplots.org/latest/attributes/. The information is the same as that given on https://docs.juliaplots.org/latest/attributes/.
""" """
function plotattr() function plotattr()
println("Specify an attribute type to get a list of supported attributes. Options are $(attrtypes())") println(
"Specify an attribute type to get a list of supported attributes. Options are $(attrtypes())",
)
end end
function plotattr(attrtype::Symbol) function plotattr(attrtype::Symbol)
@ -44,7 +48,8 @@ printnothing(x) = x
printnothing(x::Nothing) = "nothing" printnothing(x::Nothing) = "nothing"
function plotattr(attrtype::Symbol, attribute::AbstractString) function plotattr(attrtype::Symbol, attribute::AbstractString)
in(attrtype, keys(_attribute_defaults)) || ArgumentError("`attrtype` must match one of $(attrtypes())") in(attrtype, keys(_attribute_defaults)) ||
ArgumentError("`attrtype` must match one of $(attrtypes())")
attribute = Symbol(lookup_aliases(attrtype, attribute)) attribute = Symbol(lookup_aliases(attrtype, attribute))
@ -54,17 +59,21 @@ function plotattr(attrtype::Symbol, attribute::AbstractString)
typedesc = "" typedesc = ""
desc = strip(desc) desc = strip(desc)
else else
typedesc = desc[1:first_period_idx-1] typedesc = desc[1:(first_period_idx - 1)]
desc = strip(desc[first_period_idx+1:end]) desc = strip(desc[(first_period_idx + 1):end])
end end
als = keys(filter(x->x[2]==attribute, _keyAliases)) |> collect |> sort als = keys(filter(x -> x[2] == attribute, _keyAliases)) |> collect |> sort
als = join(map(string,als), ", ") als = join(map(string, als), ", ")
def = _attribute_defaults[attrtype][attribute] def = _attribute_defaults[attrtype][attribute]
# Looks up the different elements and plots them # Looks up the different elements and plots them
println("$(printnothing(attribute)) ", typedesc == "" ? "" : "{$(printnothing(typedesc))}", "\n", println(
"$(printnothing(attribute)) ",
typedesc == "" ? "" : "{$(printnothing(typedesc))}",
"\n",
als == "" ? "" : "$(printnothing(als))\n", als == "" ? "" : "$(printnothing(als))\n",
"\n$(printnothing(desc))\n", "\n$(printnothing(desc))\n",
"$(printnothing(attrtype)) attribute, ", def == "" ? "" : " default: $(printnothing(def))") "$(printnothing(attrtype)) attribute, ",
def == "" ? "" : " default: $(printnothing(def))",
)
end end

View File

@ -1,6 +1,5 @@
should_precompile = true should_precompile = true
# Don't edit the following! Instead change the script for `snoop_bot`. # Don't edit the following! Instead change the script for `snoop_bot`.
ismultios = false ismultios = false
ismultiversion = false ismultiversion = false
@ -8,13 +7,11 @@ ismultiversion = false
@static if !should_precompile @static if !should_precompile
# nothing # nothing
elseif !ismultios && !ismultiversion elseif !ismultios && !ismultiversion
@static if isfile(joinpath( @static if isfile(
@__DIR__, joinpath(@__DIR__, "../deps/SnoopCompile/precompile/precompile_Plots.jl"),
"../deps/SnoopCompile/precompile/precompile_Plots.jl", )
))
include("../deps/SnoopCompile/precompile/precompile_Plots.jl") include("../deps/SnoopCompile/precompile/precompile_Plots.jl")
_precompile_() _precompile_()
end end
else else
end # precompile_enclosure end # precompile_enclosure

View File

@ -41,17 +41,16 @@ function all_seriestypes()
sort(collect(sts)) sort(collect(sts))
end end
# ---------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------
num_series(x::AMat) = size(x, 2) num_series(x::AMat) = size(x, 2)
num_series(x) = 1 num_series(x) = 1
RecipesBase.apply_recipe(plotattributes::AKW, ::Type{T}, plt::AbstractPlot) where {T} = throw(MethodError(T, "Unmatched plot recipe: $T")) RecipesBase.apply_recipe(plotattributes::AKW, ::Type{T}, plt::AbstractPlot) where {T} =
throw(MethodError(T, "Unmatched plot recipe: $T"))
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# for seriestype `line`, need to sort by x values # for seriestype `line`, need to sort by x values
const POTENTIAL_VECTOR_ARGUMENTS = [ const POTENTIAL_VECTOR_ARGUMENTS = [
@ -114,8 +113,8 @@ end
@recipe function f(::Type{Val{:hline}}, x, y, z) @recipe function f(::Type{Val{:hline}}, x, y, z)
n = length(y) n = length(y)
newx = repeat(Float64[1, 2, NaN], n) newx = repeat(Float64[1, 2, NaN], n)
newy = vec(Float64[yi for i = 1:3, yi in y]) newy = vec(Float64[yi for i in 1:3, yi in y])
x := newx x := newx
y := newy y := newy
seriestype := :straightline seriestype := :straightline
@ -125,7 +124,7 @@ end
@recipe function f(::Type{Val{:vline}}, x, y, z) @recipe function f(::Type{Val{:vline}}, x, y, z)
n = length(y) n = length(y)
newx = vec(Float64[yi for i = 1:3, yi in y]) newx = vec(Float64[yi for i in 1:3, yi in y])
x := newx x := newx
y := repeat(Float64[1, 2, NaN], n) y := repeat(Float64[1, 2, NaN], n)
seriestype := :straightline seriestype := :straightline
@ -136,7 +135,7 @@ end
@recipe function f(::Type{Val{:hspan}}, x, y, z) @recipe function f(::Type{Val{:hspan}}, x, y, z)
n = div(length(y), 2) n = div(length(y), 2)
newx = repeat([-Inf, Inf, Inf, -Inf, NaN], outer = n) newx = repeat([-Inf, Inf, Inf, -Inf, NaN], outer = n)
newy = vcat([[y[2i - 1], y[2i - 1], y[2i], y[2i], NaN] for i = 1:n]...) newy = vcat([[y[2i - 1], y[2i - 1], y[2i], y[2i], NaN] for i in 1:n]...)
linewidth --> 0 linewidth --> 0
x := newx x := newx
y := newy y := newy
@ -147,7 +146,7 @@ end
@recipe function f(::Type{Val{:vspan}}, x, y, z) @recipe function f(::Type{Val{:vspan}}, x, y, z)
n = div(length(y), 2) n = div(length(y), 2)
newx = vcat([[y[2i - 1], y[2i - 1], y[2i], y[2i], NaN] for i = 1:n]...) newx = vcat([[y[2i - 1], y[2i - 1], y[2i], y[2i], NaN] for i in 1:n]...)
newy = repeat([-Inf, Inf, Inf, -Inf, NaN], outer = n) newy = repeat([-Inf, Inf, Inf, -Inf, NaN], outer = n)
linewidth --> 0 linewidth --> 0
x := newx x := newx
@ -179,7 +178,6 @@ end
end end
@deps scatterpath path scatter @deps scatterpath path scatter
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# regression line and scatter # regression line and scatter
@ -202,10 +200,8 @@ end
() ()
end end
@specialize @specialize
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# steps # steps
@ -215,10 +211,10 @@ function make_steps(x::AbstractArray, st, even)
n == 0 && return zeros(0) n == 0 && return zeros(0)
newx = zeros(2n - (even ? 0 : 1)) newx = zeros(2n - (even ? 0 : 1))
newx[1] = x[1] newx[1] = x[1]
for i = 2:n for i in 2:n
idx = 2i - 1 idx = 2i - 1
if st == :mid if st == :mid
newx[idx] = newx[idx-1] = (x[i] + x[i-1]) / 2 newx[idx] = newx[idx - 1] = (x[i] + x[i - 1]) / 2
else else
newx[idx] = x[i] newx[idx] = x[i]
newx[idx - 1] = x[st == :pre ? i : i - 1] newx[idx - 1] = x[st == :pre ? i : i - 1]
@ -229,7 +225,6 @@ function make_steps(x::AbstractArray, st, even)
end end
make_steps(t::Tuple, st, even) = Tuple(make_steps(ti, st, even) for ti in t) make_steps(t::Tuple, st, even) = Tuple(make_steps(ti, st, even) for ti in t)
@nospecialize @nospecialize
# create a path from steps # create a path from steps
@ -307,7 +302,6 @@ end
end end
@deps steppost path scatter @deps steppost path scatter
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# sticks # sticks
@ -326,7 +320,7 @@ end
end end
newx, newy = zeros(3n), zeros(3n) newx, newy = zeros(3n), zeros(3n)
newz = z !== nothing ? zeros(3n) : nothing newz = z !== nothing ? zeros(3n) : nothing
for (i, (xi, yi, zi)) = enumerate(zip(x, y, z !== nothing ? z : 1:n)) for (i, (xi, yi, zi)) in enumerate(zip(x, y, z !== nothing ? z : 1:n))
rng = (3i - 2):(3i) rng = (3i - 2):(3i)
newx[rng] = [xi, xi, NaN] newx[rng] = [xi, xi, NaN]
if z !== nothing if z !== nothing
@ -343,7 +337,11 @@ end
end end
fillrange := nothing fillrange := nothing
seriestype := :path seriestype := :path
if plotattributes[:linecolor] == :auto && plotattributes[:marker_z] !== nothing && plotattributes[:line_z] === nothing if (
plotattributes[:linecolor] == :auto &&
plotattributes[:marker_z] !== nothing &&
plotattributes[:line_z] === nothing
)
line_z := plotattributes[:marker_z] line_z := plotattributes[:marker_z]
end end
@ -368,7 +366,6 @@ end
@specialize @specialize
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# bezier curves # bezier curves
@ -438,8 +435,7 @@ end
# create a bar plot as a filled step function # create a bar plot as a filled step function
@recipe function f(::Type{Val{:bar}}, x, y, z) @recipe function f(::Type{Val{:bar}}, x, y, z)
procx, procy, xscale, yscale, baseline = procx, procy, xscale, yscale, baseline = _preprocess_barlike(plotattributes, x, y)
_preprocess_barlike(plotattributes, x, y)
nx, ny = length(procx), length(procy) nx, ny = length(procx), length(procy)
axis = plotattributes[:subplot][isvertical(plotattributes) ? :xaxis : :yaxis] axis = plotattributes[:subplot][isvertical(plotattributes) ? :xaxis : :yaxis]
cv = [discrete_value!(axis, xi)[1] for xi in procx] cv = [discrete_value!(axis, xi)[1] for xi in procx]
@ -448,7 +444,9 @@ end
elseif nx == ny + 1 elseif nx == ny + 1
0.5 * diff(cv) + cv[1:(end - 1)] 0.5 * diff(cv) + cv[1:(end - 1)]
else else
error("bar recipe: x must be same length as y (centers), or one more than y (edges).\n\t\tlength(x)=$(length(x)), length(y)=$(length(y))") error(
"bar recipe: x must be same length as y (centers), or one more than y (edges).\n\t\tlength(x)=$(length(x)), length(y)=$(length(y))",
)
end end
# compute half-width of bars # compute half-width of bars
@ -473,7 +471,7 @@ end
end end
xseg, yseg = Segments(), Segments() xseg, yseg = Segments(), Segments()
for i = 1:ny for i in 1:ny
yi = procy[i] yi = procy[i]
if !isnan(yi) if !isnan(yi)
center = procx[i] center = procx[i]
@ -497,7 +495,7 @@ end
# switch back # switch back
if !isvertical(plotattributes) if !isvertical(plotattributes)
xseg, yseg = yseg, xseg xseg, yseg = yseg, xseg
x, y = y, x x, y = y, x
end end
# reset orientation # reset orientation
@ -532,8 +530,8 @@ end
m, n = size(z.surf) m, n = size(z.surf)
x_pts, y_pts = fill(NaN, 6 * m * n), fill(NaN, 6 * m * n) x_pts, y_pts = fill(NaN, 6 * m * n), fill(NaN, 6 * m * n)
fz = zeros(m * n) fz = zeros(m * n)
for i = 1:m # y for i in 1:m # y
for j = 1:n # x for j in 1:n # x
k = (j - 1) * m + i k = (j - 1) * m + i
inds = (6 * (k - 1) + 1):(6 * k - 1) inds = (6 * (k - 1) + 1):(6 * k - 1)
x_pts[inds] .= [xe[j], xe[j + 1], xe[j + 1], xe[j], xe[j]] x_pts[inds] .= [xe[j], xe[j + 1], xe[j + 1], xe[j], xe[j]]
@ -580,7 +578,6 @@ function _scale_adjusted_values(
end end
end end
function _binbarlike_baseline(min_value::T, scale::Symbol) where {T<:Real} function _binbarlike_baseline(min_value::T, scale::Symbol) where {T<:Real}
if (scale in _logScales) if (scale in _logScales)
!isnan(min_value) ? min_value / T(_logScaleBases[scale]^log10(2)) : T(1E-3) !isnan(min_value) ? min_value / T(_logScaleBases[scale]^log10(2)) : T(1E-3)
@ -589,7 +586,6 @@ function _binbarlike_baseline(min_value::T, scale::Symbol) where {T<:Real}
end end
end end
function _preprocess_binbarlike_weights( function _preprocess_binbarlike_weights(
::Type{T}, ::Type{T},
w, w,
@ -618,12 +614,10 @@ function _preprocess_binlike(plotattributes, x, y)
edge, weights, xscale, yscale, baseline edge, weights, xscale, yscale, baseline
end end
@nospecialize @nospecialize
@recipe function f(::Type{Val{:barbins}}, x, y, z) @recipe function f(::Type{Val{:barbins}}, x, y, z)
edge, weights, xscale, yscale, baseline = edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, x, y)
_preprocess_binlike(plotattributes, x, y)
if (plotattributes[:bar_width] === nothing) if (plotattributes[:bar_width] === nothing)
bar_width := diff(edge) bar_width := diff(edge)
end end
@ -634,10 +628,8 @@ end
end end
@deps barbins bar @deps barbins bar
@recipe function f(::Type{Val{:scatterbins}}, x, y, z) @recipe function f(::Type{Val{:scatterbins}}, x, y, z)
edge, weights, xscale, yscale, baseline = edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, x, y)
_preprocess_binlike(plotattributes, x, y)
@series begin @series begin
x := _bin_centers(edge) x := _bin_centers(edge)
xerror := diff(edge) / 2 xerror := diff(edge) / 2
@ -654,13 +646,7 @@ end
@specialize @specialize
function _stepbins_path( function _stepbins_path(edge, weights, baseline::Real, xscale::Symbol, yscale::Symbol)
edge,
weights,
baseline::Real,
xscale::Symbol,
yscale::Symbol,
)
log_scale_x = xscale in _logScales log_scale_x = xscale in _logScales
log_scale_y = yscale in _logScales log_scale_y = yscale in _logScales
@ -722,11 +708,9 @@ end
@recipe function f(::Type{Val{:stepbins}}, x, y, z) @recipe function f(::Type{Val{:stepbins}}, x, y, z)
@nospecialize @nospecialize
axis = axis = plotattributes[:subplot][Plots.isvertical(plotattributes) ? :xaxis : :yaxis]
plotattributes[:subplot][Plots.isvertical(plotattributes) ? :xaxis : :yaxis]
edge, weights, xscale, yscale, baseline = edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, x, y)
_preprocess_binlike(plotattributes, x, y)
xpts, ypts = _stepbins_path(edge, weights, baseline, xscale, yscale) xpts, ypts = _stepbins_path(edge, weights, baseline, xscale, yscale)
if !isvertical(plotattributes) if !isvertical(plotattributes)
@ -778,12 +762,9 @@ function _auto_binning_nbins(
# The nd estimator is the key to most automatic binning methods, and is modified for twodimensional histograms to include correlation # The nd estimator is the key to most automatic binning methods, and is modified for twodimensional histograms to include correlation
nd = n_samples^(1 / (2 + N)) nd = n_samples^(1 / (2 + N))
nd = N == 2 ? nd =
min( N == 2 ?
n_samples^(1 / (2 + N)), min(n_samples^(1 / (2 + N)), nd / (1 - cor(first(vs), last(vs))^2)^(3 // 8)) : nd # the >2-dimensional case does not have a nice solution to correlations
nd / (1 - cor(first(vs), last(vs))^2)^(3 // 8),
) :
nd # the >2-dimensional case does not have a nice solution to correlations
v = vs[dim] v = vs[dim]
@ -812,11 +793,8 @@ _hist_edge(vs::NTuple{N,AbstractVector}, dim::Integer, binning::Integer) where {
StatsBase.histrange(vs[dim], binning, :left) StatsBase.histrange(vs[dim], binning, :left)
_hist_edge(vs::NTuple{N,AbstractVector}, dim::Integer, binning::Symbol) where {N} = _hist_edge(vs::NTuple{N,AbstractVector}, dim::Integer, binning::Symbol) where {N} =
_hist_edge(vs, dim, _auto_binning_nbins(vs, dim, mode = binning)) _hist_edge(vs, dim, _auto_binning_nbins(vs, dim, mode = binning))
_hist_edge( _hist_edge(vs::NTuple{N,AbstractVector}, dim::Integer, binning::AbstractVector) where {N} =
vs::NTuple{N,AbstractVector}, binning
dim::Integer,
binning::AbstractVector,
) where {N} = binning
_hist_edges(vs::NTuple{N,AbstractVector}, binning::NTuple{N,Any}) where {N} = _hist_edges(vs::NTuple{N,AbstractVector}, binning::NTuple{N,Any}) where {N} =
map(dim -> _hist_edge(vs, dim, binning[dim]), (1:N...,)) map(dim -> _hist_edge(vs, dim, binning[dim]), (1:N...,))
@ -846,8 +824,8 @@ function _make_hist(
edges = _hist_edges(localvs, binning) edges = _hist_edges(localvs, binning)
h = float( h = float(
weights === nothing ? weights === nothing ?
StatsBase.fit(StatsBase.Histogram, localvs, edges, closed = :left) : StatsBase.fit(StatsBase.Histogram, localvs, edges, closed = :left) :
StatsBase.fit( StatsBase.fit(
StatsBase.Histogram, StatsBase.Histogram,
localvs, localvs,
StatsBase.Weights(weights), StatsBase.Weights(weights),
@ -908,7 +886,6 @@ end
end end
@deps scatterhist scatterbins @deps scatterhist scatterbins
@recipe function f(h::StatsBase.Histogram{T,1,E}) where {T,E} @recipe function f(h::StatsBase.Histogram{T,1,E}) where {T,E}
seriestype --> :barbins seriestype --> :barbins
@ -918,8 +895,7 @@ end
:step => :stepbins, :step => :stepbins,
:steppost => :stepbins, # :step can be mapped to :steppost in pre-processing :steppost => :stepbins, # :step can be mapped to :steppost in pre-processing
) )
seriestype := seriestype := get(st_map, plotattributes[:seriestype], plotattributes[:seriestype])
get(st_map, plotattributes[:seriestype], plotattributes[:seriestype])
if plotattributes[:seriestype] == :scatterbins if plotattributes[:seriestype] == :scatterbins
# Workaround, error bars currently not set correctly by scatterbins # Workaround, error bars currently not set correctly by scatterbins
@ -933,7 +909,6 @@ end
end end
end end
@recipe function f(hv::AbstractVector{H}) where {H<:StatsBase.Histogram} @recipe function f(hv::AbstractVector{H}) where {H<:StatsBase.Histogram}
for h in hv for h in hv
@series begin @series begin
@ -942,7 +917,6 @@ end
end end
end end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Histogram 2D # Histogram 2D
@ -969,7 +943,6 @@ end
end end
Plots.@deps bins2d heatmap Plots.@deps bins2d heatmap
@recipe function f(::Type{Val{:histogram2d}}, x, y, z) @recipe function f(::Type{Val{:histogram2d}}, x, y, z)
h = _make_hist( h = _make_hist(
(x, y), (x, y),
@ -985,13 +958,11 @@ Plots.@deps bins2d heatmap
end end
@deps histogram2d bins2d @deps histogram2d bins2d
@recipe function f(h::StatsBase.Histogram{T,2,E}) where {T,E} @recipe function f(h::StatsBase.Histogram{T,2,E}) where {T,E}
seriestype --> :bins2d seriestype --> :bins2d
(h.edges[1], h.edges[2], Surface(h.weights)) (h.edges[1], h.edges[2], Surface(h.weights))
end end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# pie # pie
@recipe function f(::Type{Val{:pie}}, x, y, z) @recipe function f(::Type{Val{:pie}}, x, y, z)
@ -1013,7 +984,6 @@ end
end end
@deps pie shape @deps pie shape
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# mesh 3d replacement for non-plotly backends # mesh 3d replacement for non-plotly backends
@ -1021,12 +991,15 @@ end
# As long as no i,j,k are supplied this should work with PyPlot and GR # As long as no i,j,k are supplied this should work with PyPlot and GR
seriestype := :surface seriestype := :surface
if plotattributes[:connections] !== nothing if plotattributes[:connections] !== nothing
throw(ArgumentError("Giving triangles using the connections argument is only supported on Plotly backend.")) throw(
ArgumentError(
"Giving triangles using the connections argument is only supported on Plotly backend.",
),
)
end end
() ()
end end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# scatter 3d # scatter 3d
@ -1044,7 +1017,7 @@ end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# lens! - magnify a region of a plot # lens! - magnify a region of a plot
lens!(args...;kwargs...) = plot!(args...; seriestype=:lens, kwargs...) lens!(args...; kwargs...) = plot!(args...; seriestype = :lens, kwargs...)
export lens! export lens!
@recipe function f(::Type{Val{:lens}}, plt::AbstractPlot) @recipe function f(::Type{Val{:lens}}, plt::AbstractPlot)
sp_index, inset_bbox = plotattributes[:inset_subplots] sp_index, inset_bbox = plotattributes[:inset_subplots]
@ -1072,11 +1045,12 @@ export lens!
linecolor := :lightgray linecolor := :lightgray
bbx_mag = (x1 + x2) / 2 bbx_mag = (x1 + x2) / 2
bby_mag = (y1 + y2) / 2 bby_mag = (y1 + y2) / 2
xi_lens, yi_lens = intersection_point(bbx_mag, bby_mag, bbx, bby, abs(bby2 - bby1), abs(bbx2 - bbx1)) xi_lens, yi_lens =
xi_mag, yi_mag = intersection_point(bbx, bby, bbx_mag, bby_mag, abs(y2 - y1), abs(x2 - x1)) intersection_point(bbx_mag, bby_mag, bbx, bby, abs(bby2 - bby1), abs(bbx2 - bbx1))
xi_mag, yi_mag =
intersection_point(bbx, bby, bbx_mag, bby_mag, abs(y2 - y1), abs(x2 - x1))
# add lines # add lines
if xl1 < xi_lens < xl2 && if xl1 < xi_lens < xl2 && yl1 < yi_lens < yl2
yl1 < yi_lens < yl2
@series begin @series begin
primary := false primary := false
subplot := sp_index subplot := sp_index
@ -1121,14 +1095,14 @@ function intersection_point(xA, yA, xB, yB, h, w)
else # left else # left
return xB - hw, yB - s * hw return xB - hw, yB - s * hw
end end
# top or bot? # top or bot?
elseif -hw <= hh/s <= hw elseif -hw <= hh / s <= hw
if yA > yB if yA > yB
# top # top
return xB + hh/s, yB + hh return xB + hh / s, yB + hh
else else
# bottom # bottom
return xB - hh/s, yB - hh return xB - hh / s, yB - hh
end end
end end
end end
@ -1268,15 +1242,15 @@ function quiver_using_arrows(plotattributes::AKW)
if !isa(plotattributes[:arrow], Arrow) if !isa(plotattributes[:arrow], Arrow)
plotattributes[:arrow] = arrow() plotattributes[:arrow] = arrow()
end end
is_3d = haskey(plotattributes,:z) && !isnothing(plotattributes[:z]) is_3d = haskey(plotattributes, :z) && !isnothing(plotattributes[:z])
velocity = error_zipit(plotattributes[:quiver]) velocity = error_zipit(plotattributes[:quiver])
xorig, yorig = plotattributes[:x], plotattributes[:y] xorig, yorig = plotattributes[:x], plotattributes[:y]
zorig = is_3d ? plotattributes[:z] : [] zorig = is_3d ? plotattributes[:z] : []
# for each point, we create an arrow of velocity vi, translated to the x/y coordinates # for each point, we create an arrow of velocity vi, translated to the x/y coordinates
x, y = zeros(0), zeros(0) x, y = zeros(0), zeros(0)
is_3d && ( z = zeros(0)) is_3d && (z = zeros(0))
for i = 1:max(length(xorig), length(yorig), is_3d ? 0 : length(zorig)) for i in 1:max(length(xorig), length(yorig), is_3d ? 0 : length(zorig))
# get the starting position # get the starting position
xi = _cycle(xorig, i) xi = _cycle(xorig, i)
yi = _cycle(yorig, i) yi = _cycle(yorig, i)
@ -1326,7 +1300,7 @@ function quiver_using_hack(plotattributes::AKW)
# for each point, we create an arrow of velocity vi, translated to the x/y coordinates # for each point, we create an arrow of velocity vi, translated to the x/y coordinates
pts = P2[] pts = P2[]
for i = 1:max(length(xorig), length(yorig)) for i in 1:max(length(xorig), length(yorig))
# get the starting position # get the starting position
xi = _cycle(xorig, i) xi = _cycle(xorig, i)
@ -1355,10 +1329,7 @@ function quiver_using_hack(plotattributes::AKW)
U2 *= arrow_w U2 *= arrow_w
ppv = p + v ppv = p + v
nanappend!( nanappend!(pts, P2[p, ppv - U1, ppv - U1 + U2, ppv, ppv - U1 - U2, ppv - U1])
pts,
P2[p, ppv - U1, ppv - U1 + U2, ppv, ppv - U1 - U2, ppv - U1],
)
end end
plotattributes[:x], plotattributes[:y] = RecipesPipeline.unzip(pts[2:end]) plotattributes[:x], plotattributes[:y] = RecipesPipeline.unzip(pts[2:end])
@ -1377,7 +1348,6 @@ end
end end
@deps quiver shape path @deps quiver shape path
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# 1 argument # 1 argument
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -1409,7 +1379,7 @@ end
@nospecialize @nospecialize
# images - colors # images - colors
@recipe function f(mat::AMat{T}) where {T <: Colorant} @recipe function f(mat::AMat{T}) where {T<:Colorant}
n, m = axes(mat) n, m = axes(mat)
if is_seriestype_supported(:image) if is_seriestype_supported(:image)
@ -1439,9 +1409,9 @@ end
# interpreted as having one element per shape # interpreted as having one element per shape
for attr in union(_segmenting_array_attributes, _segmenting_vector_attributes) for attr in union(_segmenting_array_attributes, _segmenting_vector_attributes)
v = get(plotattributes, attr, nothing) v = get(plotattributes, attr, nothing)
if v isa AVec || v isa AMat && size(v,2) == 1 if v isa AVec || v isa AMat && size(v, 2) == 1
@warn "Column vector attribute `$attr` reinterpreted as row vector (one value per shape).\n" * @warn "Column vector attribute `$attr` reinterpreted as row vector (one value per shape).\n" *
"Pass a row vector instead (e.g. using `permutedims`) to suppress this warning." "Pass a row vector instead (e.g. using `permutedims`) to suppress this warning."
plotattributes[attr] = permutedims(v) plotattributes[attr] = permutedims(v)
end end
end end
@ -1455,13 +1425,12 @@ end
end end
end end
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# 3 arguments # 3 arguments
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# images - grays # images - grays
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where {T <: Gray} @recipe function f(x::AVec, y::AVec, mat::AMat{T}) where {T<:Gray}
if is_seriestype_supported(:image) if is_seriestype_supported(:image)
seriestype := :image seriestype := :image
yflip --> true yflip --> true
@ -1476,7 +1445,7 @@ end
end end
# images - colors # images - colors
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where {T <: Colorant} @recipe function f(x::AVec, y::AVec, mat::AMat{T}) where {T<:Colorant}
if is_seriestype_supported(:image) if is_seriestype_supported(:image)
seriestype := :image seriestype := :image
yflip --> true yflip --> true
@ -1497,7 +1466,7 @@ end
@recipe f(p::GeometryBasics.Point) = [p] @recipe f(p::GeometryBasics.Point) = [p]
# Special case for 4-tuples in :ohlc series # Special case for 4-tuples in :ohlc series
@recipe f(xyuv::AVec{<:Tuple{R1, R2, R3, R4}}) where {R1, R2, R3, R4} = @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] : get(plotattributes, :seriestype, :path) == :ohlc ? OHLC[OHLC(t...) for t in xyuv] :
RecipesPipeline.unzip(xyuv) RecipesPipeline.unzip(xyuv)
@ -1554,8 +1523,7 @@ end
@recipe f( @recipe f(
x::AVec, x::AVec,
ohlc::AVec{Tuple{R1,R2,R3,R4}}, ohlc::AVec{Tuple{R1,R2,R3,R4}},
) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = ) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = x, OHLC[OHLC(t...) for t in ohlc]
x, OHLC[OHLC(t...) for t in ohlc]
@recipe function f(x::AVec, v::AVec{OHLC}) @recipe function f(x::AVec, v::AVec{OHLC})
seriestype := :path seriestype := :path
@ -1574,7 +1542,6 @@ end
# TODO: everything below here should be either changed to a # TODO: everything below here should be either changed to a
# series recipe or moved to PlotRecipes # series recipe or moved to PlotRecipes
# "Sparsity plot... heatmap of non-zero values of a matrix" # "Sparsity plot... heatmap of non-zero values of a matrix"
# function spy{T<:Real}(z::AMat{T}; kw...) # function spy{T<:Real}(z::AMat{T}; kw...)
# mat = reshape(map(zi->float(zi!=0), z),1,:) # mat = reshape(map(zi->float(zi!=0), z),1,:)
@ -1622,7 +1589,6 @@ end
@specialize @specialize
Plots.findnz(A::AbstractSparseMatrix) = SparseArrays.findnz(A) Plots.findnz(A::AbstractSparseMatrix) = SparseArrays.findnz(A)
# fallback function for finding non-zero elements of non-sparse matrices # fallback function for finding non-zero elements of non-sparse matrices
@ -1662,7 +1628,6 @@ end
x, real.(y), imag.(y) x, real.(y), imag.(y)
end end
# Moved in from PlotRecipes - see: http://stackoverflow.com/a/37732384/5075246 # Moved in from PlotRecipes - see: http://stackoverflow.com/a/37732384/5075246
@userplot PortfolioComposition @userplot PortfolioComposition

View File

@ -241,7 +241,6 @@ julia> ohlc(y)
""" """
@shorthands ohlc @shorthands ohlc
""" """
contour(x,y,z) contour(x,y,z)
contour!(x,y,z) contour!(x,y,z)
@ -263,7 +262,6 @@ julia> contour(x, y, (x, y) -> x^2 + y^2)
"An alias for `contour` with fill = true." "An alias for `contour` with fill = true."
@shorthands contourf @shorthands contourf
@shorthands contour3d @shorthands contour3d
""" """
@ -408,17 +406,17 @@ julia> curves([1,2,3,4],[1,1,2,4])
@shorthands pie @shorthands pie
"Plot with seriestype :path3d" "Plot with seriestype :path3d"
plot3d(args...; kw...) = plot(args...; kw..., seriestype = :path3d) plot3d(args...; kw...) = plot(args...; kw..., seriestype = :path3d)
plot3d!(args...; kw...) = plot!(args...; kw..., seriestype = :path3d) plot3d!(args...; kw...) = plot!(args...; kw..., seriestype = :path3d)
"Add title to an existing plot" "Add title to an existing plot"
title!(s::AbstractString; kw...) = plot!(; title = s, kw...) title!(s::AbstractString; kw...) = plot!(; title = s, kw...)
"Add xlabel to an existing plot" "Add xlabel to an existing plot"
xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...) xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...)
"Add ylabel to an existing plot" "Add ylabel to an existing plot"
ylabel!(s::AbstractString; kw...) = plot!(; ylabel = s, kw...) ylabel!(s::AbstractString; kw...) = plot!(; ylabel = s, kw...)
"Set xlims for an existing plot" "Set xlims for an existing plot"
xlims!(lims::Tuple; kw...) = plot!(; xlims = lims, kw...) xlims!(lims::Tuple; kw...) = plot!(; xlims = lims, kw...)
@ -429,21 +427,20 @@ ylims!(lims::Tuple; kw...) = plot!(; ylims = lims, kw...)
"Set zlims for an existing plot" "Set zlims for an existing plot"
zlims!(lims::Tuple; kw...) = plot!(; zlims = lims, kw...) zlims!(lims::Tuple; kw...) = plot!(; zlims = lims, kw...)
xlims!(xmin::Real, xmax::Real; kw...) = plot!(; xlims = (xmin,xmax), kw...) xlims!(xmin::Real, xmax::Real; kw...) = plot!(; xlims = (xmin, xmax), kw...)
ylims!(ymin::Real, ymax::Real; kw...) = plot!(; ylims = (ymin,ymax), kw...) ylims!(ymin::Real, ymax::Real; kw...) = plot!(; ylims = (ymin, ymax), kw...)
zlims!(zmin::Real, zmax::Real; kw...) = plot!(; zlims = (zmin,zmax), kw...) zlims!(zmin::Real, zmax::Real; kw...) = plot!(; zlims = (zmin, zmax), kw...)
"Set xticks for an existing plot" "Set xticks for an existing plot"
xticks!(v::TicksArgs; kw...) = plot!(; xticks = v, kw...) xticks!(v::TicksArgs; kw...) = plot!(; xticks = v, kw...)
"Set yticks for an existing plot" "Set yticks for an existing plot"
yticks!(v::TicksArgs; kw...) = plot!(; yticks = v, kw...) yticks!(v::TicksArgs; kw...) = plot!(; yticks = v, kw...)
xticks!( xticks!(ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} =
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(; xticks = (ticks,labels), kw...) plot!(; xticks = (ticks, labels), kw...)
yticks!( yticks!(ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} =
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(; yticks = (ticks,labels), kw...) plot!(; yticks = (ticks, labels), kw...)
""" """
annotate!(anns...) annotate!(anns...)
@ -463,22 +460,22 @@ julia> annotate!([(7,3,"(7,3)"),(3,7,text("hey", 14, :left, :top, :green))])
julia> annotate!([(4, 4, ("More text", 8, 45.0, :bottom, :red))]) julia> annotate!([(4, 4, ("More text", 8, 45.0, :bottom, :red))])
``` ```
""" """
annotate!(anns...; kw...) = plot!(; annotation = anns, kw...) annotate!(anns...; kw...) = plot!(; annotation = anns, kw...)
annotate!(anns::Tuple...; kw...) = plot!(; annotation = collect(anns), kw...) annotate!(anns::Tuple...; kw...) = plot!(; annotation = collect(anns), kw...)
annotate!(anns::AVec{<:Tuple}; kw...) = plot!(; annotation = anns, kw...) annotate!(anns::AVec{<:Tuple}; kw...) = plot!(; annotation = anns, kw...)
"Flip the current plots' x axis" "Flip the current plots' x axis"
xflip!(flip::Bool = true; kw...) = plot!(; xflip = flip, kw...) xflip!(flip::Bool = true; kw...) = plot!(; xflip = flip, kw...)
"Flip the current plots' y axis" "Flip the current plots' y axis"
yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...) yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...)
"Specify x axis attributes for an existing plot" "Specify x axis attributes for an existing plot"
xaxis!(args...; kw...) = plot!(; xaxis = args, kw...) xaxis!(args...; kw...) = plot!(; xaxis = args, kw...)
"Specify y axis attributes for an existing plot" "Specify y axis attributes for an existing plot"
yaxis!(args...; kw...) = plot!(; yaxis = args, kw...) yaxis!(args...; kw...) = plot!(; yaxis = args, kw...)
xgrid!(args...; kw...) = plot!(; xgrid = args, kw...) xgrid!(args...; kw...) = plot!(; xgrid = args, kw...)
ygrid!(args...; kw...) = plot!(; ygrid = args, kw...) ygrid!(args...; kw...) = plot!(; ygrid = args, kw...)
@specialize @specialize

View File

@ -1,6 +1,5 @@
function Subplot(::T; parent = RootLayout()) where {T<:AbstractBackend}
function Subplot(::T; parent = RootLayout()) where T<:AbstractBackend
Subplot{T}( Subplot{T}(
parent, parent,
Series[], Series[],
@ -9,7 +8,7 @@ function Subplot(::T; parent = RootLayout()) where T<:AbstractBackend
defaultbox, defaultbox,
DefaultsDict(KW(), _subplot_defaults), DefaultsDict(KW(), _subplot_defaults),
nothing, nothing,
nothing nothing,
) )
end end
@ -21,8 +20,7 @@ Return the bounding box of a subplot
plotarea(sp::Subplot) = sp.plotarea plotarea(sp::Subplot) = sp.plotarea
plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox) plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox)
Base.size(sp::Subplot) = (1, 1)
Base.size(sp::Subplot) = (1,1)
Base.length(sp::Subplot) = 1 Base.length(sp::Subplot) = 1
Base.getindex(sp::Subplot, r::Int, c::Int) = sp Base.getindex(sp::Subplot, r::Int, c::Int) = sp
@ -43,23 +41,23 @@ series_list(sp::Subplot) = sp.series_list # filter(series -> series.plotattribut
function should_add_to_legend(series::Series) function should_add_to_legend(series::Series)
series.plotattributes[:primary] && series.plotattributes[:primary] &&
series.plotattributes[:label] != "" && series.plotattributes[:label] != "" &&
!( !(
series.plotattributes[:seriestype] in ( series.plotattributes[:seriestype] in (
:hexbin, :hexbin,
:bins2d, :bins2d,
:histogram2d, :histogram2d,
:hline, :hline,
:vline, :vline,
:contour, :contour,
:contourf, :contourf,
:contour3d, :contour3d,
:surface, :surface,
:wireframe, :wireframe,
:heatmap, :heatmap,
:image, :image,
)
) )
)
end end
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------

View File

@ -34,11 +34,8 @@ end
@userplot ShowTheme @userplot ShowTheme
_color_functions = KW( _color_functions =
:protanopic => protanopic, KW(:protanopic => protanopic, :deuteranopic => deuteranopic, :tritanopic => tritanopic)
:deuteranopic => deuteranopic,
:tritanopic => tritanopic,
)
_get_showtheme_args(thm::Symbol) = thm, identity _get_showtheme_args(thm::Symbol) = thm, identity
_get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func, identity) _get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func, identity)
@ -110,8 +107,8 @@ _get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func
subplot := 4 subplot := 4
seriestype := :heatmap seriestype := :heatmap
seriescolor := colorgradient seriescolor := colorgradient
xticks := (-2π:2π:2π, string.(-2:2:2, "π")) xticks := ((-2π):(2π):(2π), string.(-2:2:2, "π"))
yticks := (-2π:2π:2π, string.(-2:2:2, "π")) yticks := ((-2π):(2π):(2π), string.(-2:2:2, "π"))
x, y, z x, y, z
end end
@ -119,8 +116,8 @@ _get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func
subplot := 5 subplot := 5
seriestype := :surface seriestype := :surface
seriescolor := colorgradient seriescolor := colorgradient
xticks := (-2π:2π:2π, string.(-2:2:2, "π")) xticks := ((-2π):(2π):(2π), string.(-2:2:2, "π"))
yticks := (-2π:2π:2π, string.(-2:2:2, "π")) yticks := ((-2π):(2π):(2π), string.(-2:2:2, "π"))
x, y, z x, y, z
end end
@ -137,5 +134,4 @@ _get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func
line_z := z line_z := z
x, y, z x, y, z
end end
end end

View File

@ -6,7 +6,8 @@ const AVec = AbstractVector
const AMat = AbstractMatrix const AMat = AbstractMatrix
const KW = Dict{Symbol,Any} const KW = Dict{Symbol,Any}
const AKW = AbstractDict{Symbol,Any} const AKW = AbstractDict{Symbol,Any}
const TicksArgs = Union{AVec{T}, Tuple{AVec{T}, AVec{S}}, Symbol} where {T<:Real, S<:AbstractString} const TicksArgs =
Union{AVec{T},Tuple{AVec{T},AVec{S}},Symbol} where {T<:Real,S<:AbstractString}
struct PlotsDisplay <: AbstractDisplay end struct PlotsDisplay <: AbstractDisplay end
@ -59,11 +60,10 @@ Extrema() = Extrema(Inf, -Inf)
# ----------------------------------------------------------- # -----------------------------------------------------------
const SubplotMap = Dict{Any, Subplot} const SubplotMap = Dict{Any,Subplot}
# ----------------------------------------------------------- # -----------------------------------------------------------
mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T} mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
backend::T # the backend type backend::T # the backend type
n::Int # number of series n::Int # number of series
@ -78,9 +78,18 @@ mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
end end
function Plot() function Plot()
Plot(backend(), 0, DefaultsDict(KW(), _plot_defaults), Series[], nothing, Plot(
Subplot[], SubplotMap(), EmptyLayout(), backend(),
Subplot[], false) 0,
DefaultsDict(KW(), _plot_defaults),
Series[],
nothing,
Subplot[],
SubplotMap(),
EmptyLayout(),
Subplot[],
false,
)
end end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -89,7 +98,7 @@ Base.getindex(plt::Plot, i::Integer) = plt.subplots[i]
Base.length(plt::Plot) = length(plt.subplots) Base.length(plt::Plot) = length(plt.subplots)
Base.lastindex(plt::Plot) = length(plt) Base.lastindex(plt::Plot) = length(plt)
Base.getindex(plt::Plot, r::Integer, c::Integer) = plt.layout[r,c] Base.getindex(plt::Plot, r::Integer, c::Integer) = plt.layout[r, c]
Base.size(plt::Plot) = size(plt.layout) Base.size(plt::Plot) = size(plt.layout)
Base.size(plt::Plot, i::Integer) = size(plt.layout)[i] Base.size(plt::Plot, i::Integer) = size(plt.layout)[i]
Base.ndims(plt::Plot) = 2 Base.ndims(plt::Plot) = 2

View File

@ -1,11 +1,12 @@
# --------------------------------------------------------------- # ---------------------------------------------------------------
treats_y_as_x(seriestype) = seriestype in (:vline, :vspan, :histogram, :barhist, :stephist, :scatterhist) treats_y_as_x(seriestype) =
seriestype in (:vline, :vspan, :histogram, :barhist, :stephist, :scatterhist)
function replace_image_with_heatmap(z::Array{T}) where T<:Colorant function replace_image_with_heatmap(z::Array{T}) where {T<:Colorant}
n, m = size(z) n, m = size(z)
colors = palette(vec(z)) colors = palette(vec(z))
newz = reshape(1:n*m, n, m) newz = reshape(1:(n * m), n, m)
newz, colors newz, colors
end end
@ -20,8 +21,7 @@ end
Segments() = Segments(Float64) Segments() = Segments(Float64)
Segments(::Type{T}) where {T} = Segments(T[]) Segments(::Type{T}) where {T} = Segments(T[])
Segments(p::Int) = Segments(NTuple{p, Float64}[]) Segments(p::Int) = Segments(NTuple{p,Float64}[])
# Segments() = Segments(zeros(0)) # Segments() = Segments(zeros(0))
@ -30,30 +30,32 @@ to_nan(::Type{NTuple{2,Float64}}) = (NaN, NaN)
to_nan(::Type{NTuple{3,Float64}}) = (NaN, NaN, NaN) to_nan(::Type{NTuple{3,Float64}}) = (NaN, NaN, NaN)
coords(segs::Segments{Float64}) = segs.pts coords(segs::Segments{Float64}) = segs.pts
coords(segs::Segments{NTuple{2,Float64}}) = Float64[p[1] for p in segs.pts], Float64[p[2] for p in segs.pts] coords(segs::Segments{NTuple{2,Float64}}) =
coords(segs::Segments{NTuple{3,Float64}}) = Float64[p[1] for p in segs.pts], Float64[p[2] for p in segs.pts], Float64[p[3] for p in segs.pts] Float64[p[1] for p in segs.pts], Float64[p[2] for p in segs.pts]
coords(segs::Segments{NTuple{3,Float64}}) = Float64[p[1] for p in segs.pts],
Float64[p[2] for p in segs.pts],
Float64[p[3] for p in segs.pts]
function Base.push!(segments::Segments{T}, vs...) where T function Base.push!(segments::Segments{T}, vs...) where {T}
if !isempty(segments.pts) if !isempty(segments.pts)
push!(segments.pts, to_nan(T)) push!(segments.pts, to_nan(T))
end end
for v in vs for v in vs
push!(segments.pts, convert(T,v)) push!(segments.pts, convert(T, v))
end end
segments segments
end end
function Base.push!(segments::Segments{T}, vs::AVec) where T function Base.push!(segments::Segments{T}, vs::AVec) where {T}
if !isempty(segments.pts) if !isempty(segments.pts)
push!(segments.pts, to_nan(T)) push!(segments.pts, to_nan(T))
end end
for v in vs for v in vs
push!(segments.pts, convert(T,v)) push!(segments.pts, convert(T, v))
end end
segments segments
end end
struct SeriesSegment struct SeriesSegment
# indexes of this segement in series data vectors # indexes of this segement in series data vectors
range::UnitRange range::UnitRange
@ -76,7 +78,7 @@ function iter_segments(args...)
NaNSegmentsIterator(tup, n1, n2) NaNSegmentsIterator(tup, n1, n2)
end end
function series_segments(series::Series, seriestype::Symbol=:path; check=false) function series_segments(series::Series, seriestype::Symbol = :path; check = false)
x, y, z = series[:x], series[:y], series[:z] x, y, z = series[:x], series[:y], series[:z]
(x === nothing || isempty(x)) && return UnitRange{Int}[] (x === nothing || isempty(x)) && return UnitRange{Int}[]
@ -85,13 +87,13 @@ function series_segments(series::Series, seriestype::Symbol=:path; check=false)
if check if check
scales = :xscale, :yscale, :zscale scales = :xscale, :yscale, :zscale
for (n, s) enumerate(args) for (n, s) in enumerate(args)
scale = get(series, scales[n], :identity) scale = get(series, scales[n], :identity)
if scale _logScales if scale _logScales
for (i, v) enumerate(s) for (i, v) in enumerate(s)
if v <= 0 if v <= 0
@warn "Invalid negative or zero value $v found at series index $i for $(scale) based $(scales[n])" @warn "Invalid negative or zero value $v found at series index $i for $(scale) based $(scales[n])"
@debug "" exception=(DomainError(v), stacktrace()) @debug "" exception = (DomainError(v), stacktrace())
break break
end end
end end
@ -101,12 +103,12 @@ function series_segments(series::Series, seriestype::Symbol=:path; check=false)
segments = if has_attribute_segments(series) segments = if has_attribute_segments(series)
Iterators.flatten(map(nan_segments) do r Iterators.flatten(map(nan_segments) do r
if seriestype in (:scatter, :scatter3d) if seriestype in (:scatter, :scatter3d)
(SeriesSegment(i:i, i) for i in r) (SeriesSegment(i:i, i) for i in r)
else else
(SeriesSegment(i:i+1, i) for i in first(r):last(r)-1) (SeriesSegment(i:(i + 1), i) for i in first(r):(last(r) - 1))
end end
end) end)
else else
(SeriesSegment(r, 1) for r in nan_segments) (SeriesSegment(r, 1) for r in nan_segments)
end end
@ -117,13 +119,15 @@ end
function warn_on_attr_dim_mismatch(series, x, y, z, segments) function warn_on_attr_dim_mismatch(series, x, y, z, segments)
isempty(segments) && return isempty(segments) && return
seg_range = UnitRange(minimum(first(seg.range) for seg in segments), seg_range = UnitRange(
maximum(last(seg.range) for seg in segments)) minimum(first(seg.range) for seg in segments),
maximum(last(seg.range) for seg in segments),
)
for attr in _segmenting_vector_attributes for attr in _segmenting_vector_attributes
v = get(series, attr, nothing) v = get(series, attr, nothing)
if v isa AVec && eachindex(v) != seg_range if v isa AVec && eachindex(v) != seg_range
@warn "Indices $(eachindex(v)) of attribute `$attr` does not match data indices $seg_range." @warn "Indices $(eachindex(v)) of attribute `$attr` does not match data indices $seg_range."
if any(v -> !isnothing(v) && any(isnan, v), (x,y,z)) if any(v -> !isnothing(v) && any(isnan, v), (x, y, z))
@info """Data contains NaNs or missing values, and indices of `$attr` vector do not match data indices. @info """Data contains NaNs or missing values, and indices of `$attr` vector do not match data indices.
If you intend elements of `$attr` to apply to individual NaN-separated segements in the data, If you intend elements of `$attr` to apply to individual NaN-separated segements in the data,
pass each segment in a separate vector instead, and use a row vector for `$attr`. Legend entries pass each segment in a separate vector instead, and use a row vector for `$attr`. Legend entries
@ -137,20 +141,24 @@ function warn_on_attr_dim_mismatch(series, x, y, z, segments)
end end
# helpers to figure out if there are NaN values in a list of array types # helpers to figure out if there are NaN values in a list of array types
anynan(i::Int, args::Tuple) = any(a -> try isnan(_cycle(a,i)) catch MethodError false end, args) anynan(i::Int, args::Tuple) = any(a -> try
anynan(args::Tuple) = i -> anynan(i,args) isnan(_cycle(a, i))
catch MethodError
false
end, args)
anynan(args::Tuple) = i -> anynan(i, args)
anynan(istart::Int, iend::Int, args::Tuple) = any(anynan(args), istart:iend) anynan(istart::Int, iend::Int, args::Tuple) = any(anynan(args), istart:iend)
allnan(istart::Int, iend::Int, args::Tuple) = all(anynan(args), istart:iend) allnan(istart::Int, iend::Int, args::Tuple) = all(anynan(args), istart:iend)
function Base.iterate(itr::NaNSegmentsIterator, nextidx::Int = itr.n1) function Base.iterate(itr::NaNSegmentsIterator, nextidx::Int = itr.n1)
i = findfirst(!anynan(itr.args), nextidx:itr.n2) i = findfirst(!anynan(itr.args), nextidx:(itr.n2))
i === nothing && return i === nothing && return
nextval = nextidx + i - 1 nextval = nextidx + i - 1
j = findfirst(anynan(itr.args), nextval:itr.n2) j = findfirst(anynan(itr.args), nextval:(itr.n2))
nextnan = j === nothing ? itr.n2 + 1 : nextval + j - 1 nextnan = j === nothing ? itr.n2 + 1 : nextval + j - 1
nextval:nextnan-1, nextnan nextval:(nextnan - 1), nextnan
end end
Base.IteratorSize(::NaNSegmentsIterator) = Base.SizeUnknown() Base.IteratorSize(::NaNSegmentsIterator) = Base.SizeUnknown()
@ -162,7 +170,6 @@ float_extended_type(x::AbstractArray{T}) where {T<:Real} = Float64
# ------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------
nop() = nothing nop() = nothing
notimpl() = error("This has not been implemented yet") notimpl() = error("This has not been implemented yet")
@ -172,12 +179,12 @@ isnothing(x) = false
_cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj _cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj
_cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj _cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj
_cycle(v::AVec, idx::Int) = v[mod(idx, axes(v,1))] _cycle(v::AVec, idx::Int) = v[mod(idx, axes(v, 1))]
_cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[end, mod(idx, axes(v,2))] : v[:, mod(idx, axes(v,2))] _cycle(v::AMat, idx::Int) = size(v, 1) == 1 ? v[end, mod(idx, axes(v, 2))] : v[:, mod(idx, axes(v, 2))]
_cycle(v, idx::Int) = v _cycle(v, idx::Int) = v
_cycle(v::AVec, indices::AVec{Int}) = map(i -> _cycle(v,i), indices) _cycle(v::AVec, indices::AVec{Int}) = map(i -> _cycle(v, i), indices)
_cycle(v::AMat, indices::AVec{Int}) = map(i -> _cycle(v,i), indices) _cycle(v::AMat, indices::AVec{Int}) = map(i -> _cycle(v, i), indices)
_cycle(v, indices::AVec{Int}) = fill(v, length(indices)) _cycle(v, indices::AVec{Int}) = fill(v, length(indices))
_cycle(cl::PlotUtils.AbstractColorList, idx::Int) = cl[mod1(idx, end)] _cycle(cl::PlotUtils.AbstractColorList, idx::Int) = cl[mod1(idx, end)]
@ -198,17 +205,16 @@ maketuple(x::Tuple{T,S}) where {T,S} = x
for i in 2:4 for i in 2:4
@eval begin @eval begin
RecipesPipeline.unzip( RecipesPipeline.unzip(
v::Union{AVec{<:Tuple{Vararg{T,$i} where T}}, AVec{<:GeometryBasics.Point{$i}}}, v::Union{AVec{<:Tuple{Vararg{T,$i} where T}},AVec{<:GeometryBasics.Point{$i}}},
) = $(Expr(:tuple, (:([t[$j] for t in v]) for j=1:i)...)) ) = $(Expr(:tuple, (:([t[$j] for t in v]) for j in 1:i)...))
end end
end end
RecipesPipeline.unzip( RecipesPipeline.unzip(
::Union{AVec{<:GeometryBasics.Point{N}}, AVec{<:Tuple{Vararg{T,N} where T}}} ::Union{AVec{<:GeometryBasics.Point{N}},AVec{<:Tuple{Vararg{T,N} where T}}},
) where N = error("$N-dimensional unzip not implemented.") ) where {N} = error("$N-dimensional unzip not implemented.")
RecipesPipeline.unzip(::Union{AVec{<:GeometryBasics.Point}, AVec{<:Tuple}}) = error( RecipesPipeline.unzip(::Union{AVec{<:GeometryBasics.Point},AVec{<:Tuple}}) =
"Can't unzip points of different dimensions." error("Can't unzip points of different dimensions.")
)
# given 2-element lims and a vector of data x, widen lims to account for the extrema of x # given 2-element lims and a vector of data x, widen lims to account for the extrema of x
function _expand_limits(lims, x) function _expand_limits(lims, x)
@ -221,7 +227,7 @@ function _expand_limits(lims, x)
nothing nothing
end end
expand_data(v, n::Integer) = [_cycle(v, i) for i=1:n] expand_data(v, n::Integer) = [_cycle(v, i) for i in 1:n]
# if the type exists in a list, replace the first occurence. otherwise add it to the end # if the type exists in a list, replace the first occurence. otherwise add it to the end
function addOrReplace(v::AbstractVector, t::DataType, args...; kw...) function addOrReplace(v::AbstractVector, t::DataType, args...; kw...)
@ -253,28 +259,42 @@ function replaceAliases!(plotattributes::AKW, aliases::Dict{Symbol,Symbol})
end end
end end
createSegments(z) = collect(repeat(reshape(z,1,:),2,1))[2:end] createSegments(z) = collect(repeat(reshape(z, 1, :), 2, 1))[2:end]
sortedkeys(plotattributes::Dict) = sort(collect(keys(plotattributes))) sortedkeys(plotattributes::Dict) = sort(collect(keys(plotattributes)))
function _heatmap_edges(v::AVec, isedges::Bool = false, ispolar::Bool = false) function _heatmap_edges(v::AVec, isedges::Bool = false, ispolar::Bool = false)
length(v) == 1 && return v[1] .+ [ispolar ? max(-v[1], -0.5) : -0.5, 0.5] length(v) == 1 && return v[1] .+ [ispolar ? max(-v[1], -0.5) : -0.5, 0.5]
if isedges return v end if isedges
return v
end
# `isedges = true` means that v is a vector which already describes edges # `isedges = true` means that v is a vector which already describes edges
# and does not need to be extended. # and does not need to be extended.
vmin, vmax = ignorenan_extrema(v) vmin, vmax = ignorenan_extrema(v)
extra_min = ispolar ? min(v[1], (v[2] - v[1]) / 2) : (v[2] - v[1]) / 2 extra_min = ispolar ? min(v[1], (v[2] - v[1]) / 2) : (v[2] - v[1]) / 2
extra_max = (v[end] - v[end - 1]) / 2 extra_max = (v[end] - v[end - 1]) / 2
vcat(vmin-extra_min, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra_max) vcat(vmin - extra_min, 0.5 * (v[1:(end - 1)] + v[2:end]), vmax + extra_max)
end end
"create an (n+1) list of the outsides of heatmap rectangles" "create an (n+1) list of the outsides of heatmap rectangles"
function heatmap_edges(v::AVec, scale::Symbol = :identity, isedges::Bool = false, ispolar::Bool = false) function heatmap_edges(
v::AVec,
scale::Symbol = :identity,
isedges::Bool = false,
ispolar::Bool = false,
)
f, invf = RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale) f, invf = RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale)
map(invf, _heatmap_edges(map(f,v), isedges, ispolar)) map(invf, _heatmap_edges(map(f, v), isedges, ispolar))
end end
function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size::Tuple{Int, Int}, ispolar::Bool = false) function heatmap_edges(
x::AVec,
xscale::Symbol,
y::AVec,
yscale::Symbol,
z_size::Tuple{Int,Int},
ispolar::Bool = false,
)
nx, ny = length(x), length(y) nx, ny = length(x), length(y)
# ismidpoints = z_size == (ny, nx) # This fails some tests, but would actually be # ismidpoints = z_size == (ny, nx) # This fails some tests, but would actually be
# the correct check, since (4, 3) != (3, 4) and a missleading plot is produced. # the correct check, since (4, 3) != (3, 4) and a missleading plot is produced.
@ -285,12 +305,11 @@ function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size:
Must be either `size(z) == (length(y), length(x))` (x & y define midpoints) Must be either `size(z) == (length(y), length(x))` (x & y define midpoints)
or `size(z) == (length(y)+1, length(x)+1))` (x & y define edges).""") or `size(z) == (length(y)+1, length(x)+1))` (x & y define edges).""")
end end
x, y = heatmap_edges(x, xscale, isedges), x, y = heatmap_edges(x, xscale, isedges), heatmap_edges(y, yscale, isedges, ispolar) # special handle for `r` in polar plots
heatmap_edges(y, yscale, isedges, ispolar) # special handle for `r` in polar plots
return x, y return x, y
end end
function is_uniformly_spaced(v; tol=1e-6) function is_uniformly_spaced(v; tol = 1e-6)
dv = diff(v) dv = diff(v)
maximum(dv) - minimum(dv) < tol * mean(abs.(dv)) maximum(dv) - minimum(dv) < tol * mean(abs.(dv))
end end
@ -298,8 +317,8 @@ end
function convert_to_polar(theta, r, r_extrema = ignorenan_extrema(r)) function convert_to_polar(theta, r, r_extrema = ignorenan_extrema(r))
rmin, rmax = r_extrema rmin, rmax = r_extrema
r = (r .- rmin) ./ (rmax .- rmin) r = (r .- rmin) ./ (rmax .- rmin)
x = r.*cos.(theta) x = r .* cos.(theta)
y = r.*sin.(theta) y = r .* sin.(theta)
x, y x, y
end end
@ -307,8 +326,8 @@ fakedata(sz::Int...) = fakedata(Random.seed!(PLOTS_SEED), sz...)
function fakedata(rng::AbstractRNG, sz...) function fakedata(rng::AbstractRNG, sz...)
y = zeros(sz...) y = zeros(sz...)
for r in 2:size(y,1) for r in 2:size(y, 1)
y[r,:] = 0.95 * vec(y[r-1,:]) + randn(rng, size(y,2)) y[r, :] = 0.95 * vec(y[r - 1, :]) + randn(rng, size(y, 2))
end end
y y
end end
@ -327,13 +346,14 @@ isscalar(::Any) = false
is_2tuple(v) = typeof(v) <: Tuple && length(v) == 2 is_2tuple(v) = typeof(v) <: Tuple && length(v) == 2
isvertical(plotattributes::AKW) = get(plotattributes, :orientation, :vertical) in (:vertical, :v, :vert) isvertical(plotattributes::AKW) =
get(plotattributes, :orientation, :vertical) in (:vertical, :v, :vert)
isvertical(series::Series) = isvertical(series.plotattributes) isvertical(series::Series) = isvertical(series.plotattributes)
ticksType(ticks::AVec{T}) where {T<:Real} = :ticks ticksType(ticks::AVec{T}) where {T<:Real} = :ticks
ticksType(ticks::AVec{T}) where {T<:AbstractString} = :labels ticksType(ticks::AVec{T}) where {T<:AbstractString} = :labels
ticksType(ticks::Tuple{T,S}) where {T<:Union{AVec,Tuple},S<:Union{AVec,Tuple}} = :ticks_and_labels ticksType(ticks::Tuple{T,S}) where {T<:Union{AVec,Tuple},S<:Union{AVec,Tuple}} =
:ticks_and_labels
ticksType(ticks) = :invalid ticksType(ticks) = :invalid
limsType(lims::Tuple{T,S}) where {T<:Real,S<:Real} = :limits limsType(lims::Tuple{T,S}) where {T<:Real,S<:Real} = :limits
@ -357,9 +377,9 @@ function nansplit(v::AVec)
push!(vs, v) push!(vs, v)
break break
elseif idx > 1 elseif idx > 1
push!(vs, v[1:idx-1]) push!(vs, v[1:(idx - 1)])
end end
v = v[idx+1:end] v = v[(idx + 1):end]
end end
vs vs
end end
@ -376,7 +396,7 @@ end
# returns the array of indices (znew) and a vector of unique values (vals) # returns the array of indices (znew) and a vector of unique values (vals)
function indices_and_unique_values(z::AbstractArray) function indices_and_unique_values(z::AbstractArray)
vals = sort(unique(z)) vals = sort(unique(z))
vmap = Dict([(v,i) for (i,v) in enumerate(vals)]) vmap = Dict([(v, i) for (i, v) in enumerate(vals)])
newz = map(zi -> vmap[zi], z) newz = map(zi -> vmap[zi], z)
newz, vals newz, vals
end end
@ -384,14 +404,14 @@ end
handle_surface(z) = z handle_surface(z) = z
handle_surface(z::Surface) = permutedims(z.surf) handle_surface(z::Surface) = permutedims(z.surf)
ok(x::Number, y::Number, z::Number=0) = isfinite(x) && isfinite(y) && isfinite(z) ok(x::Number, y::Number, z::Number = 0) = isfinite(x) && isfinite(y) && isfinite(z)
ok(tup::Tuple) = ok(tup...) ok(tup::Tuple) = ok(tup...)
# compute one side of a fill range from a ribbon # compute one side of a fill range from a ribbon
function make_fillrange_side(y::AVec, rib) function make_fillrange_side(y::AVec, rib)
frs = zeros(axes(y)) frs = zeros(axes(y))
for (i, yi) in pairs(y) for (i, yi) in pairs(y)
frs[i] = yi + _cycle(rib,i) frs[i] = yi + _cycle(rib, i)
end end
frs frs
end end
@ -407,10 +427,10 @@ function make_fillrange_from_ribbon(kw::AKW)
end end
#turn tuple of fillranges to one path #turn tuple of fillranges to one path
function concatenate_fillrange(x,y::Tuple) function concatenate_fillrange(x, y::Tuple)
rib1, rib2 = first(y), last(y) rib1, rib2 = first(y), last(y)
yline = vcat(rib1,(rib2)[end:-1:1]) yline = vcat(rib1, (rib2)[end:-1:1])
xline = vcat(x,x[end:-1:1]) xline = vcat(x, x[end:-1:1])
return xline, yline return xline, yline
end end
@ -452,9 +472,9 @@ function contour_levels(series::Series, clims)
zmin, zmax = clims zmin, zmax = clims
levels = series[:levels] levels = series[:levels]
if levels isa Integer if levels isa Integer
levels = range(zmin, stop=zmax, length=levels+2) levels = range(zmin, stop = zmax, length = levels + 2)
if !isfilledcontour(series) if !isfilledcontour(series)
levels = levels[2:end-1] levels = levels[2:(end - 1)]
end end
end end
levels levels
@ -479,7 +499,8 @@ for comp in (:line, :fill, :marker)
end end
end end
$get_compcolor(series, clims, i::Int=1) = $get_compcolor(series, clims[1], clims[2], i) $get_compcolor(series, clims, i::Int = 1) =
$get_compcolor(series, clims[1], clims[2], i)
function $get_compcolor(series, i::Int = 1) function $get_compcolor(series, i::Int = 1)
if series[$Symbol($comp_z)] === nothing if series[$Symbol($comp_z)] === nothing
@ -489,7 +510,7 @@ for comp in (:line, :fill, :marker)
end end
end end
$get_compalpha(series, i::Int=1) = _cycle(series[$Symbol($compalpha)], i) $get_compalpha(series, i::Int = 1) = _cycle(series[$Symbol($compalpha)], i)
end end
end end
@ -501,30 +522,30 @@ function get_colorgradient(series::Series)
series[:linecolor] series[:linecolor]
elseif series[:marker_z] !== nothing elseif series[:marker_z] !== nothing
series[:markercolor] series[:markercolor]
elseif series[:line_z] !== nothing elseif series[:line_z] !== nothing
series[:linecolor] series[:linecolor]
elseif series[:fill_z] !== nothing elseif series[:fill_z] !== nothing
series[:fillcolor] series[:fillcolor]
end end
end end
single_color(c, v=0.5) = c single_color(c, v = 0.5) = c
single_color(grad::ColorGradient, v=0.5) = grad[v] single_color(grad::ColorGradient, v = 0.5) = grad[v]
get_gradient(c) = cgrad() get_gradient(c) = cgrad()
get_gradient(cg::ColorGradient) = cg get_gradient(cg::ColorGradient) = cg
get_gradient(cp::ColorPalette) = cgrad(cp, categorical=true) get_gradient(cp::ColorPalette) = cgrad(cp, categorical = true)
get_linewidth(series, i::Int=1) = _cycle(series[:linewidth], i) get_linewidth(series, i::Int = 1) = _cycle(series[:linewidth], i)
get_linestyle(series, i::Int=1) = _cycle(series[:linestyle], i) get_linestyle(series, i::Int = 1) = _cycle(series[:linestyle], i)
function get_markerstrokecolor(series, i::Int=1) function get_markerstrokecolor(series, i::Int = 1)
msc = series[:markerstrokecolor] msc = series[:markerstrokecolor]
isa(msc, ColorGradient) ? msc : _cycle(msc, i) isa(msc, ColorGradient) ? msc : _cycle(msc, i)
end end
get_markerstrokealpha(series, i::Int=1) = _cycle(series[:markerstrokealpha], i) get_markerstrokealpha(series, i::Int = 1) = _cycle(series[:markerstrokealpha], i)
get_markerstrokewidth(series, i::Int=1) = _cycle(series[:markerstrokewidth], i) get_markerstrokewidth(series, i::Int = 1) = _cycle(series[:markerstrokewidth], i)
const _segmenting_vector_attributes = ( const _segmenting_vector_attributes = (
:seriescolor, :seriescolor,
@ -551,9 +572,10 @@ function has_attribute_segments(series::Series)
# of its attributes # of its attributes
series[:seriestype] == :shape && return false series[:seriestype] == :shape && return false
# check relevant attributes if they have multiple inputs # check relevant attributes if they have multiple inputs
return any(series[attr] isa AbstractVector && length(series[attr]) > 1 return any(
for attr in _segmenting_vector_attributes series[attr] isa AbstractVector && length(series[attr]) > 1 for
) || any(series[attr] isa AbstractArray for attr in _segmenting_array_attributes) attr in _segmenting_vector_attributes
) || any(series[attr] isa AbstractArray for attr in _segmenting_array_attributes)
end end
function get_aspect_ratio(sp) function get_aspect_ratio(sp)
@ -577,7 +599,8 @@ get_size(sp::Subplot) = get_size(sp.plt)
get_thickness_scaling(kw) = get(kw, :thickness_scaling, default(:thickness_scaling)) get_thickness_scaling(kw) = get(kw, :thickness_scaling, default(:thickness_scaling))
get_thickness_scaling(plt::Plot) = get_thickness_scaling(plt.attr) get_thickness_scaling(plt::Plot) = get_thickness_scaling(plt.attr)
get_thickness_scaling(sp::Subplot) = get_thickness_scaling(sp.plt) get_thickness_scaling(sp::Subplot) = get_thickness_scaling(sp.plt)
get_thickness_scaling(series::Series) = get_thickness_scaling(series.plotattributes[:subplot]) get_thickness_scaling(series::Series) =
get_thickness_scaling(series.plotattributes[:subplot])
# --------------------------------------------------------------- # ---------------------------------------------------------------
makekw(; kw...) = KW(kw) makekw(; kw...) = KW(kw)
@ -595,8 +618,11 @@ allShapes(arg) = (
trueOrAllTrue(a -> isa(a, Shape), arg) trueOrAllTrue(a -> isa(a, Shape), arg)
) )
allAlphas(arg) = trueOrAllTrue( allAlphas(arg) = trueOrAllTrue(
a -> (typeof(a) <: Real && a > 0 && a < 1) || a ->
(typeof(a) <: AbstractFloat && (a == zero(typeof(a)) || a == one(typeof(a)))), arg (typeof(a) <: Real && a > 0 && a < 1) || (
typeof(a) <: AbstractFloat && (a == zero(typeof(a)) || a == one(typeof(a)))
),
arg,
) )
allReals(arg) = trueOrAllTrue(a -> typeof(a) <: Real, arg) allReals(arg) = trueOrAllTrue(a -> typeof(a) <: Real, arg)
allFunctions(arg) = trueOrAllTrue(a -> isa(a, Function), arg) allFunctions(arg) = trueOrAllTrue(a -> isa(a, Function), arg)
@ -695,7 +721,7 @@ mutable struct DebugMode
end end
const _debugMode = DebugMode(false) const _debugMode = DebugMode(false)
debugplots(on=true) = _debugMode.on = on debugplots(on = true) = _debugMode.on = on
debugshow(io, x) = show(io, x) debugshow(io, x) = show(io, x)
debugshow(io, x::AbstractArray) = print(io, summary(x)) debugshow(io, x::AbstractArray) = print(io, summary(x))
@ -747,16 +773,17 @@ function setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z}
_series_updated(plt, series) _series_updated(plt, series)
end end
setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z<:AbstractMatrix} = ( setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z<:AbstractMatrix} =
setxyz!(plt, (xyz[1], xyz[2], Surface(xyz[3])), i) (setxyz!(plt, (xyz[1], xyz[2], Surface(xyz[3])), i))
)
# ------------------------------------------------------- # -------------------------------------------------------
# indexing notation # indexing notation
# Base.getindex(plt::Plot, i::Integer) = getxy(plt, i) # Base.getindex(plt::Plot, i::Integer) = getxy(plt, i)
Base.setindex!(plt::Plot, xy::Tuple{X,Y}, i::Integer) where {X,Y} = (setxy!(plt, xy, i); plt) Base.setindex!(plt::Plot, xy::Tuple{X,Y}, i::Integer) where {X,Y} =
Base.setindex!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z} = (setxyz!(plt, xyz, i); plt) (setxy!(plt, xy, i); 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 # operate on individual series
@ -810,14 +837,15 @@ function extend_to_length!(v::AbstractVector, n)
extend_by_data!(v, vmax .+ (1:(n - length(v)))) extend_by_data!(v, vmax .+ (1:(n - length(v))))
end end
extend_by_data!(v::AbstractVector, x) = isimmutable(v) ? vcat(v, x) : push!(v, x) extend_by_data!(v::AbstractVector, x) = isimmutable(v) ? vcat(v, x) : push!(v, x)
extend_by_data!(v::AbstractVector, x::AbstractVector) = isimmutable(v) ? vcat(v, x) : append!(v, x) extend_by_data!(v::AbstractVector, x::AbstractVector) =
isimmutable(v) ? vcat(v, x) : append!(v, x)
# ------------------------------------------------------- # -------------------------------------------------------
function attr!(series::Series; kw...) function attr!(series::Series; kw...)
plotattributes = KW(kw) plotattributes = KW(kw)
RecipesPipeline.preprocess_attributes!(plotattributes) RecipesPipeline.preprocess_attributes!(plotattributes)
for (k,v) in plotattributes for (k, v) in plotattributes
if haskey(_series_defaults, k) if haskey(_series_defaults, k)
series[k] = v series[k] = v
else else
@ -831,7 +859,7 @@ end
function attr!(sp::Subplot; kw...) function attr!(sp::Subplot; kw...)
plotattributes = KW(kw) plotattributes = KW(kw)
RecipesPipeline.preprocess_attributes!(plotattributes) RecipesPipeline.preprocess_attributes!(plotattributes)
for (k,v) in plotattributes for (k, v) in plotattributes
if haskey(_subplot_defaults, k) if haskey(_subplot_defaults, k)
sp[k] = v sp[k] = v
else else
@ -861,8 +889,8 @@ Base.append!(plt::Plot, i::Integer, t::Tuple) = append!(plt, i, t...)
# push y[i] to the ith series # push y[i] to the ith series
function Base.push!(plt::Plot, y::AVec) function Base.push!(plt::Plot, y::AVec)
ny = length(y) ny = length(y)
for i in 1:plt.n for i in 1:(plt.n)
push!(plt, i, y[mod1(i,ny)]) push!(plt, i, y[mod1(i, ny)])
end end
plt plt
end end
@ -875,8 +903,8 @@ Base.push!(plt::Plot, x::Real, y::AVec) = push!(plt, [x], y)
function Base.push!(plt::Plot, x::AVec, y::AVec) function Base.push!(plt::Plot, x::AVec, y::AVec)
nx = length(x) nx = length(x)
ny = length(y) ny = length(y)
for i in 1:plt.n for i in 1:(plt.n)
push!(plt, i, x[mod1(i,nx)], y[mod1(i,ny)]) push!(plt, i, x[mod1(i, nx)], y[mod1(i, ny)])
end end
plt plt
end end
@ -886,8 +914,8 @@ function Base.push!(plt::Plot, x::AVec, y::AVec, z::AVec)
nx = length(x) nx = length(x)
ny = length(y) ny = length(y)
nz = length(z) nz = length(z)
for i in 1:plt.n for i in 1:(plt.n)
push!(plt, i, x[mod1(i,nx)], y[mod1(i,ny)], z[mod1(i,nz)]) push!(plt, i, x[mod1(i, nx)], y[mod1(i, ny)], z[mod1(i, nz)])
end end
plt plt
end end
@ -909,15 +937,14 @@ mm2inch(mm::Real) = float(mm / MM_PER_INCH)
px2mm(px::Real) = float(px * MM_PER_PX) px2mm(px::Real) = float(px * MM_PER_PX)
mm2px(mm::Real) = float(mm / MM_PER_PX) mm2px(mm::Real) = float(mm / MM_PER_PX)
"Smallest x in plot" "Smallest x in plot"
xmin(plt::Plot) = ignorenan_minimum( xmin(plt::Plot) = ignorenan_minimum([
[ignorenan_minimum(series.plotattributes[:x]) for series in plt.series_list] ignorenan_minimum(series.plotattributes[:x]) for series in plt.series_list
) ])
"Largest x in plot" "Largest x in plot"
xmax(plt::Plot) = ignorenan_maximum( xmax(plt::Plot) = ignorenan_maximum([
[ignorenan_maximum(series.plotattributes[:x]) for series in plt.series_list] ignorenan_maximum(series.plotattributes[:x]) for series in plt.series_list
) ])
"Extrema of x-values in plot" "Extrema of x-values in plot"
ignorenan_extrema(plt::Plot) = (xmin(plt), xmax(plt)) ignorenan_extrema(plt::Plot) = (xmin(plt), xmax(plt))
@ -1040,11 +1067,14 @@ function straightline_data(series, expansion_factor = 1)
xdata, ydata = fill(NaN, n), fill(NaN, n) xdata, ydata = fill(NaN, n), fill(NaN, n)
for i in 1:k for i in 1:k
inds = (3 * i - 2):(3 * i - 1) inds = (3 * i - 2):(3 * i - 1)
xdata[inds], ydata[inds] = straightline_data(xl, yl, x[inds], y[inds], expansion_factor) xdata[inds], ydata[inds] =
straightline_data(xl, yl, x[inds], y[inds], expansion_factor)
end end
xdata, ydata xdata, ydata
else else
error("Misformed data. `straightline_data` either accepts vectors of length 2 or 3k. The provided series has length $n") error(
"Misformed data. `straightline_data` either accepts vectors of length 2 or 3k. The provided series has length $n",
)
end end
end end
@ -1066,7 +1096,10 @@ function straightline_data(xl, yl, x, y, expansion_factor = 1)
b = y[1] - (y[1] - y[2]) * x[1] / (x[1] - x[2]) b = y[1] - (y[1] - y[2]) * x[1] / (x[1] - x[2])
a = (y[1] - y[2]) / (x[1] - x[2]) a = (y[1] - y[2]) / (x[1] - x[2])
# get the data values # get the data values
xdata = [clamp(x[1] + (x[1] - x[2]) * (ylim - y[1]) / (y[1] - y[2]), xl...) for ylim in yl] xdata = [
clamp(x[1] + (x[1] - x[2]) * (ylim - y[1]) / (y[1] - y[2]), xl...) for
ylim in yl
]
xdata, a .* xdata .+ b xdata, a .* xdata .+ b
end end
@ -1107,49 +1140,51 @@ function shape_data(series, expansion_factor = 1)
return x, y return x, y
end end
construct_categorical_data(x::AbstractArray, axis::Axis) = ( construct_categorical_data(x::AbstractArray, axis::Axis) =
map(xi -> axis[:discrete_values][searchsortedfirst(axis[:continuous_values], xi)], x) (map(xi -> axis[:discrete_values][searchsortedfirst(axis[:continuous_values], xi)], x))
)
_fmt_paragraph(paragraph::AbstractString;kwargs...) = _fmt_paragraph( _fmt_paragraph(paragraph::AbstractString; kwargs...) =
IOBuffer(), paragraph, 0; kwargs... _fmt_paragraph(IOBuffer(), paragraph, 0; kwargs...)
)
function _fmt_paragraph( function _fmt_paragraph(
io::IOBuffer, remaining_text::AbstractString, column_count::Integer; io::IOBuffer,
fillwidth=60, leadingspaces=0 remaining_text::AbstractString,
column_count::Integer;
fillwidth = 60,
leadingspaces = 0,
) )
kwargs = (fillwidth=fillwidth, leadingspaces=leadingspaces) kwargs = (fillwidth = fillwidth, leadingspaces = leadingspaces)
m = match(r"(.*?) (.*)",remaining_text) m = match(r"(.*?) (.*)", remaining_text)
if isa(m, Nothing) if isa(m, Nothing)
if column_count + length(remaining_text) fillwidth if column_count + length(remaining_text) fillwidth
print(io, remaining_text) print(io, remaining_text)
String(take!(io)) String(take!(io))
else else
print(io, "\n"*" "^leadingspaces*remaining_text) print(io, "\n" * " "^leadingspaces * remaining_text)
String(take!(io)) String(take!(io))
end end
else else
if column_count + length(m[1]) fillwidth if column_count + length(m[1]) fillwidth
print(io,"$(m[1]) ") print(io, "$(m[1]) ")
_fmt_paragraph(io, m[2], column_count + length(m[1]) + 1; kwargs...) _fmt_paragraph(io, m[2], column_count + length(m[1]) + 1; kwargs...)
else else
print(io,"\n"*" "^leadingspaces*"$(m[1]) ") print(io, "\n" * " "^leadingspaces * "$(m[1]) ")
_fmt_paragraph(io, m[2], leadingspaces; kwargs...) _fmt_paragraph(io, m[2], leadingspaces; kwargs...)
end end
end end
end end
_document_argument(S::AbstractString) = _fmt_paragraph( _document_argument(S::AbstractString) =
"`$S`: "*_arg_desc[Symbol(S)], leadingspaces=6+length(S) _fmt_paragraph("`$S`: " * _arg_desc[Symbol(S)], leadingspaces = 6 + length(S))
)
function mesh3d_triangles(x, y, z, cns) function mesh3d_triangles(x, y, z, cns)
if typeof(cns) <: Tuple{Array, Array, Array} if typeof(cns) <: Tuple{Array,Array,Array}
ci, cj, ck = cns ci, cj, ck = cns
if !(length(ci) == length(cj) == length(ck)) if !(length(ci) == length(cj) == length(ck))
throw(ArgumentError("Argument connections must consist of equally sized arrays.")) throw(
ArgumentError("Argument connections must consist of equally sized arrays."),
)
end end
else else
throw(ArgumentError("Argument connections has to be a tuple of three arrays.")) throw(ArgumentError("Argument connections has to be a tuple of three arrays."))
@ -1157,11 +1192,14 @@ function mesh3d_triangles(x, y, z, cns)
X = zeros(eltype(x), 4length(ci)) X = zeros(eltype(x), 4length(ci))
Y = zeros(eltype(y), 4length(cj)) Y = zeros(eltype(y), 4length(cj))
Z = zeros(eltype(z), 4length(ck)) Z = zeros(eltype(z), 4length(ck))
@inbounds for I 1:length(ci) @inbounds for I in 1:length(ci)
i = ci[I] + 1 # connections are 0-based i = ci[I] + 1 # connections are 0-based
j = cj[I] + 1 j = cj[I] + 1
k = ck[I] + 1 k = ck[I] + 1
m = 4(I - 1) + 1; n = m + 1; o = m + 2; p = m + 3 m = 4(I - 1) + 1
n = m + 1
o = m + 2
p = m + 3
X[m] = X[p] = x[i] X[m] = X[p] = x[i]
Y[m] = Y[p] = y[i] Y[m] = Y[p] = y[i]
Z[m] = Z[p] = z[i] Z[m] = Z[p] = z[i]

View File

@ -3,13 +3,13 @@ import Plots._current_plots_version
# replace `f(args...)` with `f(rng, args...)` for `f ∈ (rand, randn)` # replace `f(args...)` with `f(rng, args...)` for `f ∈ (rand, randn)`
function replace_rand!(ex) end function replace_rand!(ex) end
function replace_rand!(ex::Expr) function replace_rand!(ex::Expr)
for arg in ex.args for arg in ex.args
replace_rand!(arg) replace_rand!(arg)
end end
if ex.head === :call && ex.args[1] (:rand, :randn, :(Plots.fakedata)) if ex.head === :call && ex.args[1] (:rand, :randn, :(Plots.fakedata))
pushfirst!(ex.args, ex.args[1]) pushfirst!(ex.args, ex.args[1])
ex.args[2] = :rng ex.args[2] = :rng
end end
end end
function fix_rand!(ex) function fix_rand!(ex)
replace_rand!(ex) replace_rand!(ex)
@ -59,7 +59,7 @@ function image_comparison_facts(
sigma = [1, 1], # number of pixels to "blur" sigma = [1, 1], # number of pixels to "blur"
tol = 1e-2, tol = 1e-2,
) # acceptable error (percent) ) # acceptable error (percent)
for i = 1:length(Plots._examples) for i in 1:length(Plots._examples)
i in skip && continue i in skip && continue
if only === nothing || i in only if only === nothing || i in only
@test image_comparison_tests(pkg, i, debug = debug, sigma = sigma, tol = tol) |> @test image_comparison_tests(pkg, i, debug = debug, sigma = sigma, tol = tol) |>

View File

@ -1,13 +1,13 @@
using Plots, Test, Dates using Plots, Test, Dates
@testset "Limits" begin @testset "Limits" begin
y=[1.0*i*i for i in 1:10] y = [1.0 * i * i for i in 1:10]
x=[Date(2019,11,i) for i in 1:10] x = [Date(2019, 11, i) for i in 1:10]
rx=[x[3],x[5]] rx = [x[3], x[5]]
p = plot(x,y, widen = false) p = plot(x, y, widen = false)
vspan!(p, rx, label="", alpha=0.2) vspan!(p, rx, label = "", alpha = 0.2)
ref_ylims = (y[1], y[end]) ref_ylims = (y[1], y[end])
ref_xlims = (x[1].instant.periods.value, x[end].instant.periods.value) ref_xlims = (x[1].instant.periods.value, x[end].instant.periods.value)
@ -23,13 +23,13 @@ using Plots, Test, Dates
end # testset end # testset
@testset "Date xlims" begin @testset "Date xlims" begin
y=[1.0*i*i for i in 1:10] y = [1.0 * i * i for i in 1:10]
x=[Date(2019,11,i) for i in 1:10] x = [Date(2019, 11, i) for i in 1:10]
span = (Date(2019,10,31), Date(2019,11,11)) span = (Date(2019, 10, 31), Date(2019, 11, 11))
ref_xlims = map(date->date.instant.periods.value, span) ref_xlims = map(date -> date.instant.periods.value, span)
p = plot(x,y, xlims=span, widen = false) p = plot(x, y, xlims = span, widen = false)
@test Plots.xlims(p) == ref_xlims @test Plots.xlims(p) == ref_xlims
#@static if (haskey(ENV, "APPVEYOR") || haskey(ENV, "CI")) #@static if (haskey(ENV, "APPVEYOR") || haskey(ENV, "CI"))
@ -42,13 +42,13 @@ end # testset
end # testset end # testset
@testset "DateTime xlims" begin @testset "DateTime xlims" begin
y=[1.0*i*i for i in 1:10] y = [1.0 * i * i for i in 1:10]
x=[DateTime(2019,11,i,11) for i in 1:10] x = [DateTime(2019, 11, i, 11) for i in 1:10]
span = (DateTime(2019,10,31,11,59,59), DateTime(2019,11,11,12,01,15)) span = (DateTime(2019, 10, 31, 11, 59, 59), DateTime(2019, 11, 11, 12, 01, 15))
ref_xlims = map(date->date.instant.periods.value, span) ref_xlims = map(date -> date.instant.periods.value, span)
p = plot(x,y, xlims=span, widen = false) p = plot(x, y, xlims = span, widen = false)
@test Plots.xlims(p) == ref_xlims @test Plots.xlims(p) == ref_xlims
#@static if (haskey(ENV, "APPVEYOR") || haskey(ENV, "CI")) #@static if (haskey(ENV, "APPVEYOR") || haskey(ENV, "CI"))
@static if haskey(ENV, "APPVEYOR") @static if haskey(ENV, "APPVEYOR")

View File

@ -18,12 +18,12 @@ using RecipesBase
@test Plots.plotly_local_file_path[] === nothing @test Plots.plotly_local_file_path[] === nothing
temp = Plots.use_local_dependencies[] temp = Plots.use_local_dependencies[]
withenv("PLOTS_HOST_DEPENDENCY_LOCAL" => true) do withenv("PLOTS_HOST_DEPENDENCY_LOCAL" => true) do
Plots.__init__() Plots.__init__()
@test Plots.plotly_local_file_path[] isa String @test Plots.plotly_local_file_path[] isa String
@test isfile(Plots.plotly_local_file_path[]) @test isfile(Plots.plotly_local_file_path[])
@test Plots.use_local_dependencies[] = true @test Plots.use_local_dependencies[] = true
@test_nowarn Plots._init_ijulia_plotting() @test_nowarn Plots._init_ijulia_plotting()
end end
Plots.plotly_local_file_path[] = nothing Plots.plotly_local_file_path[] = nothing
Plots.use_local_dependencies[] = temp Plots.use_local_dependencies[] = temp
end # testset end # testset
@ -39,12 +39,13 @@ include("test_recipes.jl")
include("test_hdf5plots.jl") include("test_hdf5plots.jl")
include("test_pgfplotsx.jl") include("test_pgfplotsx.jl")
reference_dir(args...) = joinpath(homedir(), ".julia", "dev", "PlotReferenceImages", args...) reference_dir(args...) =
joinpath(homedir(), ".julia", "dev", "PlotReferenceImages", args...)
function reference_file(backend, i, version) function reference_file(backend, i, version)
refdir = reference_dir("Plots", string(backend)) refdir = reference_dir("Plots", string(backend))
fn = "ref$i.png" fn = "ref$i.png"
versions = sort(VersionNumber.(readdir(refdir)), rev=true) versions = sort(VersionNumber.(readdir(refdir)), rev = true)
reffn = joinpath(refdir, string(version), fn) reffn = joinpath(refdir, string(version), fn)
for v in versions for v in versions
@ -62,13 +63,16 @@ reference_path(backend, version) = reference_dir("Plots", string(backend), strin
if !isdir(reference_dir()) if !isdir(reference_dir())
mkpath(reference_dir()) mkpath(reference_dir())
LibGit2.clone("https://github.com/JuliaPlots/PlotReferenceImages.jl.git", reference_dir()) LibGit2.clone(
"https://github.com/JuliaPlots/PlotReferenceImages.jl.git",
reference_dir(),
)
end end
include("imgcomp.jl") include("imgcomp.jl")
# don't actually show the plots # don't actually show the plots
Random.seed!(PLOTS_SEED) Random.seed!(PLOTS_SEED)
default(show=false, reuse=true) default(show = false, reuse = true)
is_ci() = get(ENV, "CI", "false") == "true" is_ci() = get(ENV, "CI", "false") == "true"
const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? "1e-4" : "1e-5")) const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? "1e-4" : "1e-5"))
@ -98,7 +102,6 @@ const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? "1e-4"
## ##
@testset "Backends" begin @testset "Backends" begin
@testset "GR" begin @testset "GR" begin
ENV["PLOTS_TEST"] = "true" ENV["PLOTS_TEST"] = "true"
ENV["GKSwstype"] = "100" ENV["GKSwstype"] = "100"
@ -108,7 +111,11 @@ const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? "1e-4"
@static if haskey(ENV, "APPVEYOR") @static if haskey(ENV, "APPVEYOR")
@info "Skipping GR image comparison tests on AppVeyor" @info "Skipping GR image comparison tests on AppVeyor"
else else
image_comparison_facts(:gr, tol=PLOTS_IMG_TOL, skip=Plots._backend_skips[:gr]) image_comparison_facts(
:gr,
tol = PLOTS_IMG_TOL,
skip = Plots._backend_skips[:gr],
)
end end
end end
@ -148,7 +155,6 @@ const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? "1e-4"
@test isa(p, Plots.Plot) == true @test isa(p, Plots.Plot) == true
@test_broken isa(display(p), Nothing) == true @test_broken isa(display(p), Nothing) == true
end end
end end
@testset "Axes" begin @testset "Axes" begin
@ -158,10 +164,10 @@ end
@test Plots.discrete_value!(axis, "HI") == (0.5, 1) @test Plots.discrete_value!(axis, "HI") == (0.5, 1)
@test Plots.discrete_value!(axis, :yo) == (1.5, 2) @test Plots.discrete_value!(axis, :yo) == (1.5, 2)
@test Plots.ignorenan_extrema(axis) == (0.5, 1.5) @test Plots.ignorenan_extrema(axis) == (0.5, 1.5)
@test axis[:discrete_map] == Dict{Any,Any}(:yo => 2, "HI" => 1) @test axis[:discrete_map] == Dict{Any,Any}(:yo => 2, "HI" => 1)
Plots.discrete_value!(axis, ["x$i" for i = 1:5]) Plots.discrete_value!(axis, ["x$i" for i in 1:5])
Plots.discrete_value!(axis, ["x$i" for i = 0:2]) Plots.discrete_value!(axis, ["x$i" for i in 0:2])
@test Plots.ignorenan_extrema(axis) == (0.5, 7.5) @test Plots.ignorenan_extrema(axis) == (0.5, 7.5)
end end
@ -171,14 +177,16 @@ end
@test backend() == Plots.UnicodePlotsBackend() @test backend() == Plots.UnicodePlotsBackend()
@testset "Plot" begin @testset "Plot" begin
plots = [histogram([1, 0, 0, 0, 0, 0]), plots = [
plot([missing]), histogram([1, 0, 0, 0, 0, 0]),
plot([missing, missing]), plot([missing]),
plot(fill(missing, 10)), plot([missing, missing]),
plot([missing; 1:4]), plot(fill(missing, 10)),
plot([fill(missing, 10); 1:4]), plot([missing; 1:4]),
plot([1 1; 1 missing]), plot([fill(missing, 10); 1:4]),
plot(["a" "b"; missing "d"], [1 2; 3 4])] plot([1 1; 1 missing]),
plot(["a" "b"; missing "d"], [1 2; 3 4]),
]
for plt in plots for plt in plots
display(plt) display(plt)
end end
@ -186,13 +194,12 @@ end
end end
@testset "Bar" begin @testset "Bar" begin
p = bar([3,2,1], [1,2,3]); p = bar([3, 2, 1], [1, 2, 3])
@test isa(p, Plots.Plot) @test isa(p, Plots.Plot)
@test isa(display(p), Nothing) == true @test isa(display(p), Nothing) == true
end end
end end
@testset "EmptyAnim" begin @testset "EmptyAnim" begin
anim = @animate for i in [] anim = @animate for i in []
end end
@ -208,17 +215,25 @@ end
@test segments([NaN]) == [] @test segments([NaN]) == []
@test segments(nan10) == [] @test segments(nan10) == []
@test segments([nan10; 1:5]) == [11:15] @test segments([nan10; 1:5]) == [11:15]
@test segments([1:5;nan10]) == [1:5] @test segments([1:5; nan10]) == [1:5]
@test segments([nan10; 1:5; nan10; 1:5; nan10]) == [11:15, 26:30] @test segments([nan10; 1:5; nan10; 1:5; nan10]) == [11:15, 26:30]
@test segments([NaN; 1], 1:10) == [2:2, 4:4, 6:6, 8:8, 10:10] @test segments([NaN; 1], 1:10) == [2:2, 4:4, 6:6, 8:8, 10:10]
@test segments([nan10; 1:15], [1:15; nan10]) == [11:15] @test segments([nan10; 1:15], [1:15; nan10]) == [11:15]
end end
@testset "Utils" begin @testset "Utils" begin
zipped = ([(1, 2)], [("a", "b")], [(1, "a"),(2, "b")], zipped = (
[(1, 2),(3, 4)], [(1, 2, 3),(3, 4, 5)], [(1, 2, 3, 4),(3, 4, 5, 6)], [(1, 2)],
[(1, 2.0),(missing, missing)], [(1, missing),(missing, "a")], [("a", "b")],
[(missing, missing)], [(missing, missing, missing),("a", "b", "c")]) [(1, "a"), (2, "b")],
[(1, 2), (3, 4)],
[(1, 2, 3), (3, 4, 5)],
[(1, 2, 3, 4), (3, 4, 5, 6)],
[(1, 2.0), (missing, missing)],
[(1, missing), (missing, "a")],
[(missing, missing)],
[(missing, missing, missing), ("a", "b", "c")],
)
for z in zipped for z in zipped
@test isequal(collect(zip(Plots.unzip(z)...)), z) @test isequal(collect(zip(Plots.unzip(z)...)), z)
@test isequal(collect(zip(Plots.unzip(GeometryBasics.Point.(z))...)), z) @test isequal(collect(zip(Plots.unzip(GeometryBasics.Point.(z))...)), z)
@ -227,5 +242,7 @@ end
op2 = Plots.process_clims((1, 2.0)) op2 = Plots.process_clims((1, 2.0))
data = randn(100, 100) data = randn(100, 100)
@test op1(data) == op2(data) @test op1(data) == op2(data)
@test Plots.process_clims(nothing) == Plots.process_clims(missing) == Plots.process_clims(:auto) @test Plots.process_clims(nothing) ==
Plots.process_clims(missing) ==
Plots.process_clims(:auto)
end end

View File

@ -9,8 +9,8 @@ using Plots, Test
end end
@testset "Magic axis" begin @testset "Magic axis" begin
@test plot(1, axis=nothing)[1][:xaxis][:ticks] == [] @test plot(1, axis = nothing)[1][:xaxis][:ticks] == []
@test plot(1, axis=nothing)[1][:yaxis][:ticks] == [] @test plot(1, axis = nothing)[1][:yaxis][:ticks] == []
end # testset end # testset
@testset "Categorical ticks" begin @testset "Categorical ticks" begin
@ -23,34 +23,34 @@ end # testset
end end
@testset "Ticks getter functions" begin @testset "Ticks getter functions" begin
ticks1 = ([1,2,3], ("a","b","c")) ticks1 = ([1, 2, 3], ("a", "b", "c"))
ticks2 = ([4,5], ("e","f")) ticks2 = ([4, 5], ("e", "f"))
p1 = plot(1:5, 1:5, 1:5, xticks=ticks1, yticks=ticks1, zticks=ticks1) p1 = plot(1:5, 1:5, 1:5, xticks = ticks1, yticks = ticks1, zticks = ticks1)
p2 = plot(1:5, 1:5, 1:5, xticks=ticks2, yticks=ticks2, zticks=ticks2) p2 = plot(1:5, 1:5, 1:5, xticks = ticks2, yticks = ticks2, zticks = ticks2)
p = plot(p1, p2) p = plot(p1, p2)
@test xticks(p) == yticks(p) == zticks(p) == [ticks1, ticks2] @test xticks(p) == yticks(p) == zticks(p) == [ticks1, ticks2]
@test xticks(p[1]) == yticks(p[1]) == zticks(p[1]) == ticks1 @test xticks(p[1]) == yticks(p[1]) == zticks(p[1]) == ticks1
end end
@testset "Axis limits" begin @testset "Axis limits" begin
pl = plot(1:5, xlims=:symmetric, widen = false) pl = plot(1:5, xlims = :symmetric, widen = false)
@test Plots.xlims(pl) == (-5, 5) @test Plots.xlims(pl) == (-5, 5)
pl = plot(1:3) pl = plot(1:3)
@test Plots.xlims(pl) == Plots.widen(1,3) @test Plots.xlims(pl) == Plots.widen(1, 3)
pl = plot([1.05,2.0,2.95], ylims=:round) pl = plot([1.05, 2.0, 2.95], ylims = :round)
@test Plots.ylims(pl) == (1, 3) @test Plots.ylims(pl) == (1, 3)
pl = plot(1:3, xlims=(1,5)) pl = plot(1:3, xlims = (1, 5))
@test Plots.xlims(pl) == (1, 5) @test Plots.xlims(pl) == (1, 5)
pl = plot(1:3, xlims=(1,5), widen=true) pl = plot(1:3, xlims = (1, 5), widen = true)
@test Plots.xlims(pl) == Plots.widen(1, 5) @test Plots.xlims(pl) == Plots.widen(1, 5)
end end
@testset "3D Axis" begin @testset "3D Axis" begin
ql = quiver([1, 2], [2, 1], [3, 4], quiver = ([1, -1], [0, 0], [1, -0.5]), arrow=true) ql = quiver([1, 2], [2, 1], [3, 4], quiver = ([1, -1], [0, 0], [1, -0.5]), arrow = true)
@test ql[1][:projection] == "3d" @test ql[1][:projection] == "3d"
end end

View File

@ -9,12 +9,15 @@ using Plots, Test
value(m::MyType) = m.val value(m::MyType) = m.val
data = MyType.(sort(randn(20))) data = MyType.(sort(randn(20)))
# A recipe that puts the axis letter in the title # A recipe that puts the axis letter in the title
@recipe function f(::Type{T}, m::T) where T <: AbstractArray{<:MyType} @recipe function f(::Type{T}, m::T) where {T<:AbstractArray{<:MyType}}
title --> string(plotattributes[:letter]) title --> string(plotattributes[:letter])
value.(m) value.(m)
end end
@testset "$f (orientation = $o)" for f in [histogram, barhist, stephist, scatterhist], o in [:vertical, :horizontal] @testset "$f (orientation = $o)" for f in [histogram, barhist, stephist, scatterhist],
@test f(data, orientation=o).subplots[1].attr[:title] == (o == :vertical ? "x" : "y") o in [:vertical, :horizontal]
@test f(data, orientation = o).subplots[1].attr[:title] ==
(o == :vertical ? "x" : "y")
end end
@testset "$f" for f in [hline, hspan] @testset "$f" for f in [hline, hspan]
@test f(data).subplots[1].attr[:title] == "y" @test f(data).subplots[1].attr[:title] == "y"

View File

@ -52,7 +52,7 @@ using Plots, Test
@testset "Plot" begin @testset "Plot" begin
ang = range(0, 2π, length = 60) ang = range(0, 2π, length = 60)
ellipse(x, y, w, h) = Shape(w * sin.(ang) .+ x, h * cos.(ang) .+ y) ellipse(x, y, w, h) = Shape(w * sin.(ang) .+ x, h * cos.(ang) .+ y)
myshapes = [ellipse(x, rand(), rand(), rand()) for x = 1:4] myshapes = [ellipse(x, rand(), rand(), rand()) for x in 1:4]
@test coords(myshapes) isa Tuple{Vector{Vector{S}},Vector{Vector{T}}} where {T,S} @test coords(myshapes) isa Tuple{Vector{Vector{S}},Vector{Vector{T}}} where {T,S}
local p local p
@test_nowarn p = plot(myshapes) @test_nowarn p = plot(myshapes)
@ -116,7 +116,7 @@ end
end end
@testset "Series Annotations" begin @testset "Series Annotations" begin
square = Shape([(0., 0.), (1., 0.), (1., 1.), (0., 1.)]) square = Shape([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)])
@test_logs (:warn, "Unused SeriesAnnotations arg: triangle (Symbol)") begin @test_logs (:warn, "Unused SeriesAnnotations arg: triangle (Symbol)") begin
p = plot( p = plot(
[1, 2, 3], [1, 2, 3],
@ -136,24 +136,25 @@ end
@test sa.scalefactor == (1, 4) @test sa.scalefactor == (1, 4)
end end
spl = scatter( spl = scatter(
4.53 .* [1/1 1/2 1/3 1/4 1/5], 4.53 .* [1 / 1 1 / 2 1 / 3 1 / 4 1 / 5],
[0 0 0 0 0], [0 0 0 0 0],
layout = (5, 1), layout = (5, 1),
ylims = (-1.1, 1.1), ylims = (-1.1, 1.1),
xlims = (0, 5), xlims = (0, 5),
series_annotations = permutedims([["1/1"], ["1/2"], ["1/3"], ["1/4"], ["1/5"]]), series_annotations = permutedims([["1/1"], ["1/2"], ["1/3"], ["1/4"], ["1/5"]]),
) )
for i 1:5 for i in 1:5
@test only(spl.series_list[i].plotattributes[:series_annotations].strs).str == "1/$i" @test only(spl.series_list[i].plotattributes[:series_annotations].strs).str ==
"1/$i"
end end
p = plot([1, 2], annotations=(1.5, 2, text("foo", :left))) p = plot([1, 2], annotations = (1.5, 2, text("foo", :left)))
x, y, txt = only(p.subplots[end][:annotations]) x, y, txt = only(p.subplots[end][:annotations])
@test (x, y) == (1.5, 2) @test (x, y) == (1.5, 2)
@test txt.str == "foo" @test txt.str == "foo"
p = plot([1, 2], annotations=((.1, .5), :auto)) p = plot([1, 2], annotations = ((0.1, 0.5), :auto))
pos, txt = only(p.subplots[end][:annotations]) pos, txt = only(p.subplots[end][:annotations])
@test pos == (.1, .5) @test pos == (0.1, 0.5)
@test txt.str == "(a)" @test txt.str == "(a)"
end end

View File

@ -1,21 +1,20 @@
using Plots, HDF5 using Plots, HDF5
@testset "HDF5_Plots" begin @testset "HDF5_Plots" begin
fname = "tmpplotsave.hdf5" fname = "tmpplotsave.hdf5"
hdf5() hdf5()
x = 1:10 x = 1:10
psrc=plot(x, x.*x); #Create some plot psrc = plot(x, x .* x) #Create some plot
Plots.hdf5plot_write(psrc, fname) Plots.hdf5plot_write(psrc, fname)
#Read back file: #Read back file:
gr() #Choose some fast backend likely to work in test environment. gr() #Choose some fast backend likely to work in test environment.
pread = Plots.hdf5plot_read(fname) pread = Plots.hdf5plot_read(fname)
#Make sure data made it through: #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][:x] == pread.subplots[1].series_list[1][:x]
@test psrc.subplots[1].series_list[1][:y] == pread.subplots[1].series_list[1][:y] @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 #display(pread) #Don't display. Regression env might not support
end #testset end #testset

View File

@ -2,420 +2,407 @@ using Plots, Test
pgfplotsx() pgfplotsx()
function create_plot(args...; kwargs...) function create_plot(args...; kwargs...)
pgfx_plot = plot(args...; kwargs...) pgfx_plot = plot(args...; kwargs...)
return pgfx_plot, repr("application/x-tex", pgfx_plot) return pgfx_plot, repr("application/x-tex", pgfx_plot)
end end
function create_plot!(args...; kwargs...) function create_plot!(args...; kwargs...)
pgfx_plot = plot!(args...; kwargs...) pgfx_plot = plot!(args...; kwargs...)
return pgfx_plot, repr("application/x-tex", pgfx_plot) return pgfx_plot, repr("application/x-tex", pgfx_plot)
end end
@testset "PGFPlotsX" begin @testset "PGFPlotsX" begin
pgfx_plot = plot(1:5) pgfx_plot = plot(1:5)
Plots._update_plot_object(pgfx_plot) Plots._update_plot_object(pgfx_plot)
@test pgfx_plot.o.the_plot isa PGFPlotsX.TikzDocument @test pgfx_plot.o.the_plot isa PGFPlotsX.TikzDocument
@test pgfx_plot.series_list[1].plotattributes[:quiver] === nothing @test pgfx_plot.series_list[1].plotattributes[:quiver] === nothing
axis = Plots.pgfx_axes(pgfx_plot.o)[1] axis = Plots.pgfx_axes(pgfx_plot.o)[1]
@test count(x -> x isa PGFPlotsX.Plot, axis.contents) == 1 @test count(x -> x isa PGFPlotsX.Plot, axis.contents) == 1
@test !haskey(axis.contents[1].options.dict, "fill") @test !haskey(axis.contents[1].options.dict, "fill")
@testset "Legends" begin @testset "Legends" begin
legends_plot = plot(rand(5, 2), lab = ["1" ""]) legends_plot = plot(rand(5, 2), lab = ["1" ""])
scatter!(legends_plot, rand(5)) scatter!(legends_plot, rand(5))
Plots._update_plot_object(legends_plot) Plots._update_plot_object(legends_plot)
axis_contents = Plots.pgfx_axes(legends_plot.o)[1].contents axis_contents = Plots.pgfx_axes(legends_plot.o)[1].contents
leg_entries = filter(x -> x isa PGFPlotsX.LegendEntry, axis_contents) leg_entries = filter(x -> x isa PGFPlotsX.LegendEntry, axis_contents)
series = filter(x -> x isa PGFPlotsX.Plot, axis_contents) series = filter(x -> x isa PGFPlotsX.Plot, axis_contents)
@test length(leg_entries) == 2 @test length(leg_entries) == 2
@test !haskey(series[1].options.dict, "forget plot") @test !haskey(series[1].options.dict, "forget plot")
@test haskey(series[2].options.dict, "forget plot") @test haskey(series[2].options.dict, "forget plot")
@test !haskey(series[3].options.dict, "forget plot") @test !haskey(series[3].options.dict, "forget plot")
end # testset end # testset
@testset "3D docs example" begin @testset "3D docs example" begin
n = 100 n = 100
ts = range(0, stop = 8π, length = n) ts = range(0, stop = 8π, length = n)
x = ts .* map(cos, ts) x = ts .* map(cos, ts)
y = (0.1ts) .* map(sin, ts) y = (0.1ts) .* map(sin, ts)
z = 1:n z = 1:n
pl = plot( pl = plot(
x, x,
y, y,
z, z,
zcolor = reverse(z), zcolor = reverse(z),
m = (10, 0.8, :blues, Plots.stroke(0)), m = (10, 0.8, :blues, Plots.stroke(0)),
leg = false, leg = false,
cbar = true, cbar = true,
w = 5, w = 5,
) )
pgfx_plot = plot!(pl, zeros(n), zeros(n), 1:n, w = 10) pgfx_plot = plot!(pl, zeros(n), zeros(n), 1:n, w = 10)
Plots._update_plot_object(pgfx_plot) Plots._update_plot_object(pgfx_plot)
if @test_nowarn( if @test_nowarn(
haskey(Plots.pgfx_axes(pgfx_plot.o)[1].options.dict, "colorbar") == true haskey(Plots.pgfx_axes(pgfx_plot.o)[1].options.dict, "colorbar") == true
) )
@test Plots.pgfx_axes(pgfx_plot.o)[1]["colorbar"] === nothing @test Plots.pgfx_axes(pgfx_plot.o)[1]["colorbar"] === nothing
end end
end # testset end # testset
@testset "Color docs example" begin @testset "Color docs example" begin
y = rand(100) y = rand(100)
plot( plot(
0:10:100, 0:10:100,
rand(11, 4), rand(11, 4),
lab = "lines", lab = "lines",
w = 3, w = 3,
palette = :grays, palette = :grays,
fill = 0, fill = 0,
α = 0.6, α = 0.6,
) )
pl = scatter!( pl = scatter!(
y, y,
zcolor = abs.(y .- 0.5), zcolor = abs.(y .- 0.5),
m = (:hot, 0.8, Plots.stroke(1, :green)), m = (:hot, 0.8, Plots.stroke(1, :green)),
ms = 10 * abs.(y .- 0.5) .+ 4, ms = 10 * abs.(y .- 0.5) .+ 4,
lab = ["grad", "", "ient"], lab = ["grad", "", "ient"],
) )
Plots._update_plot_object(pl) Plots._update_plot_object(pl)
axis = Plots.pgfx_axes(pl.o)[1] axis = Plots.pgfx_axes(pl.o)[1]
@test count(x -> x isa PGFPlotsX.LegendEntry, axis.contents) == 6 @test count(x -> x isa PGFPlotsX.LegendEntry, axis.contents) == 6
@test count(x -> x isa PGFPlotsX.Plot, axis.contents) == 108 # each marker is its own plot, fillranges create 2 plot-objects @test count(x -> x isa PGFPlotsX.Plot, axis.contents) == 108 # each marker is its own plot, fillranges create 2 plot-objects
marker = axis.contents[15] marker = axis.contents[15]
@test marker isa PGFPlotsX.Plot @test marker isa PGFPlotsX.Plot
@test marker.options["mark"] == "*" @test marker.options["mark"] == "*"
@test marker.options["mark options"]["color"] == @test marker.options["mark options"]["color"] == RGBA{Float64}(colorant"green", 0.8)
RGBA{Float64}(colorant"green", 0.8) @test marker.options["mark options"]["line width"] == 0.75 # 1px is 0.75pt
@test marker.options["mark options"]["line width"] == 0.75 # 1px is 0.75pt end # testset
end # testset @testset "Plot in pieces" begin
@testset "Plot in pieces" begin pic = plot(rand(100) / 3, reg = true, fill = (0, :green))
pic = plot(rand(100) / 3, reg = true, fill = (0, :green)) scatter!(pic, rand(100), markersize = 6, c = :orange)
scatter!(pic, rand(100), markersize = 6, c = :orange) Plots._update_plot_object(pic)
Plots._update_plot_object(pic) axis_contents = Plots.pgfx_axes(pic.o)[1].contents
axis_contents = Plots.pgfx_axes(pic.o)[1].contents leg_entries = filter(x -> x isa PGFPlotsX.LegendEntry, axis_contents)
leg_entries = filter(x -> x isa PGFPlotsX.LegendEntry, axis_contents) series = filter(x -> x isa PGFPlotsX.Plot, axis_contents)
series = filter(x -> x isa PGFPlotsX.Plot, axis_contents) @test length(leg_entries) == 2
@test length(leg_entries) == 2 @test length(series) == 4
@test length(series) == 4 @test haskey(series[1].options.dict, "forget plot")
@test haskey(series[1].options.dict, "forget plot") @test !haskey(series[2].options.dict, "forget plot")
@test !haskey(series[2].options.dict, "forget plot") @test haskey(series[3].options.dict, "forget plot")
@test haskey(series[3].options.dict, "forget plot") @test !haskey(series[4].options.dict, "forget plot")
@test !haskey(series[4].options.dict, "forget plot") end # testset
end # testset @testset "Marker types" begin
@testset "Marker types" begin markers = filter((m -> begin
markers = filter((m -> begin m in Plots.supported_markers()
m in Plots.supported_markers() end), Plots._shape_keys)
end), Plots._shape_keys) markers = reshape(markers, 1, length(markers))
markers = reshape(markers, 1, length(markers)) n = length(markers)
n = length(markers) x = (range(0, stop = 10, length = n + 2))[2:(end - 1)]
x = (range(0, stop = 10, length = n + 2))[2:(end-1)] y = repeat(reshape(reverse(x), 1, :), n, 1)
y = repeat(reshape(reverse(x), 1, :), n, 1) scatter(
scatter( x,
x, y,
y, m = (8, :auto),
m = (8, :auto), lab = map(string, markers),
lab = map(string, markers), bg = :linen,
bg = :linen, xlim = (0, 10),
xlim = (0, 10), ylim = (0, 10),
ylim = (0, 10), )
) end # testset
end # testset @testset "Layout" begin
@testset "Layout" begin plot(
plot( Plots.fakedata(100, 10),
Plots.fakedata(100, 10), layout = 4,
layout = 4, palette = [:grays :blues :hot :rainbow],
palette = [:grays :blues :hot :rainbow], bg_inside = [:orange :pink :darkblue :black],
bg_inside = [:orange :pink :darkblue :black], )
) end # testset
end # testset @testset "Polar plots" begin
@testset "Polar plots" begin Θ = range(0, stop = 1.5π, length = 100)
Θ = range(0, stop = 1.5π, length = 100) r = abs.(0.1 * randn(100) + sin.(3Θ))
r = abs.(0.1 * randn(100) + sin.(3Θ)) plot(Θ, r, proj = :polar, m = 2)
plot(Θ, r, proj = :polar, m = 2) end # testset
end # testset @testset "Drawing shapes" begin
@testset "Drawing shapes" begin verts = [
verts = [ (-1.0, 1.0),
(-1.0, 1.0), (-1.28, 0.6),
(-1.28, 0.6), (-0.2, -1.4),
(-0.2, -1.4), (0.2, -1.4),
(0.2, -1.4), (1.28, 0.6),
(1.28, 0.6), (1.0, 1.0),
(1.0, 1.0), (-1.0, 1.0),
(-1.0, 1.0), (-0.2, -0.6),
(-0.2, -0.6), (0.0, -0.2),
(0.0, -0.2), (-0.4, 0.6),
(-0.4, 0.6), (1.28, 0.6),
(1.28, 0.6), (0.2, -1.4),
(0.2, -1.4), (-0.2, -1.4),
(-0.2, -1.4), (0.6, 0.2),
(0.6, 0.2), (-0.2, 0.2),
(-0.2, 0.2), (0.0, -0.2),
(0.0, -0.2), (0.2, 0.2),
(0.2, 0.2), (-0.2, -0.6),
(-0.2, -0.6), ]
] x = 0.1:0.2:0.9
x = 0.1:0.2:0.9 y = 0.7 * rand(5) .+ 0.15
y = 0.7 * rand(5) .+ 0.15 plot(
plot( x,
x, y,
y, line = (3, :dash, :lightblue),
line = (3, :dash, :lightblue), marker = (Shape(verts), 30, RGBA(0, 0, 0, 0.2)),
marker = (Shape(verts), 30, RGBA(0, 0, 0, 0.2)), bg = :pink,
bg = :pink, fg = :darkblue,
fg = :darkblue, xlim = (0, 1),
xlim = (0, 1), ylim = (0, 1),
ylim = (0, 1), leg = false,
leg = false, )
) end # testset
end # testset @testset "Histogram 2D" begin
@testset "Histogram 2D" begin histogram2d(randn(10000), randn(10000), nbins = 20)
histogram2d(randn(10000), randn(10000), nbins = 20) end # testset
end # testset @testset "Heatmap-like" begin
@testset "Heatmap-like" begin xs = [string("x", i) for i in 1:10]
xs = [string("x", i) for i = 1:10] ys = [string("y", i) for i in 1:4]
ys = [string("y", i) for i = 1:4] z = float((1:4) * reshape(1:10, 1, :))
z = float((1:4) * reshape(1:10, 1, :)) pgfx_plot = heatmap(xs, ys, z, aspect_ratio = 1)
pgfx_plot = heatmap(xs, ys, z, aspect_ratio = 1) Plots._update_plot_object(pgfx_plot)
Plots._update_plot_object(pgfx_plot) if @test_nowarn(
if @test_nowarn( haskey(Plots.pgfx_axes(pgfx_plot.o)[1].options.dict, "colorbar") == true
haskey(Plots.pgfx_axes(pgfx_plot.o)[1].options.dict, "colorbar") == true )
) @test Plots.pgfx_axes(pgfx_plot.o)[1]["colorbar"] === nothing
@test Plots.pgfx_axes(pgfx_plot.o)[1]["colorbar"] === nothing @test Plots.pgfx_axes(pgfx_plot.o)[1]["colormap name"] == "plots1"
@test Plots.pgfx_axes(pgfx_plot.o)[1]["colormap name"] == "plots1" end
end
pgfx_plot = wireframe(xs, ys, z, aspect_ratio = 1) pgfx_plot = wireframe(xs, ys, z, aspect_ratio = 1)
# TODO: clims are wrong # TODO: clims are wrong
end # testset end # testset
@testset "Contours" begin @testset "Contours" begin
x = 1:0.5:20 x = 1:0.5:20
y = 1:0.5:10 y = 1:0.5:10
f(x, y) = begin f(x, y) = begin
(3x + y^2) * abs(sin(x) + cos(y)) (3x + y^2) * abs(sin(x) + cos(y))
end end
X = repeat(reshape(x, 1, :), length(y), 1) X = repeat(reshape(x, 1, :), length(y), 1)
Y = repeat(y, 1, length(x)) Y = repeat(y, 1, length(x))
Z = map(f, X, Y) Z = map(f, X, Y)
p2 = contour(x, y, Z) p2 = contour(x, y, Z)
p1 = contour(x, y, f, fill = true) p1 = contour(x, y, f, fill = true)
plot(p1, p2) plot(p1, p2)
# TODO: colorbar for filled contours # TODO: colorbar for filled contours
end # testset end # testset
@testset "Varying colors" begin @testset "Varying colors" begin
t = range(0, stop = 1, length = 100) t = range(0, stop = 1, length = 100)
θ = (6π) .* t θ = (6π) .* t
x = t .* cos.(θ) x = t .* cos.(θ)
y = t .* sin.(θ) y = t .* sin.(θ)
p1 = plot(x, y, line_z = t, linewidth = 3, legend = false) p1 = plot(x, y, line_z = t, linewidth = 3, legend = false)
p2 = scatter( p2 = scatter(x, y, marker_z = ((x, y) -> begin
x,
y,
marker_z = ((x, y) -> begin
x + y x + y
end), end), color = :bwr, legend = false)
color = :bwr, plot(p1, p2)
legend = false, end # testset
) @testset "Framestyles" begin
plot(p1, p2) scatter(
end # testset fill(randn(10), 6),
@testset "Framestyles" begin fill(randn(10), 6),
scatter( framestyle = [:box :semi :origin :zerolines :grid :none],
fill(randn(10), 6), title = [":box" ":semi" ":origin" ":zerolines" ":grid" ":none"],
fill(randn(10), 6), color = permutedims(1:6),
framestyle = [:box :semi :origin :zerolines :grid :none], layout = 6,
title = [":box" ":semi" ":origin" ":zerolines" ":grid" ":none"], label = "",
color = permutedims(1:6), markerstrokewidth = 0,
layout = 6, ticks = -2:2,
label = "", )
markerstrokewidth = 0, # TODO: support :semi
ticks = -2:2, end # testset
) @testset "Quiver" begin
# TODO: support :semi x = (-2pi):0.2:(2 * pi)
end # testset y = sin.(x)
@testset "Quiver" begin
x = (-2pi):0.2:(2*pi)
y = sin.(x)
u = ones(length(x)) u = ones(length(x))
v = cos.(x) v = cos.(x)
arrow_plot = plot(x, y, quiver = (u, v), arrow = true) arrow_plot = plot(x, y, quiver = (u, v), arrow = true)
# TODO: could adjust limits to fit arrows if too long, but how? # TODO: could adjust limits to fit arrows if too long, but how?
# TODO: get latex available on CI # TODO: get latex available on CI
# mktempdir() do path # mktempdir() do path
# @test_nowarn savefig(arrow_plot, path*"arrow.pdf") # @test_nowarn savefig(arrow_plot, path*"arrow.pdf")
# end # end
end # testset end # testset
@testset "Annotations" begin @testset "Annotations" begin
y = rand(10) y = rand(10)
pgfx_plot = plot( pgfx_plot =
y, plot(y, annotations = (3, y[3], Plots.text("this is \\#3", :left)), leg = false)
annotations = (3, y[3], Plots.text("this is \\#3", :left)), Plots._update_plot_object(pgfx_plot)
leg = false, axis_content = Plots.pgfx_axes(pgfx_plot.o)[1].contents
) nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content)
Plots._update_plot_object(pgfx_plot) @test length(nodes) == 1
axis_content = Plots.pgfx_axes(pgfx_plot.o)[1].contents mktempdir() do path
nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content) file_path = joinpath(path, "annotations.tex")
@test length(nodes) == 1 @test_nowarn savefig(pgfx_plot, file_path)
mktempdir() do path open(file_path) do io
file_path = joinpath(path, "annotations.tex") lines = readlines(io)
@test_nowarn savefig(pgfx_plot, file_path) @test count(s -> occursin("node", s), lines) == 1
open(file_path) do io end
lines = readlines(io) end
@test count(s -> occursin("node", s), lines) == 1 annotate!([
end (5, y[5], Plots.text("this is \\#5", 16, :red, :center)),
end (10, y[10], Plots.text("this is \\#10", :right, 20, "courier")),
annotate!([ ])
(5, y[5], Plots.text("this is \\#5", 16, :red, :center)), Plots._update_plot_object(pgfx_plot)
(10, y[10], Plots.text("this is \\#10", :right, 20, "courier")), axis_content = Plots.pgfx_axes(pgfx_plot.o)[1].contents
]) nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content)
Plots._update_plot_object(pgfx_plot) @test length(nodes) == 3
axis_content = Plots.pgfx_axes(pgfx_plot.o)[1].contents mktempdir() do path
nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content) file_path = joinpath(path, "annotations.tex")
@test length(nodes) == 3 @test_nowarn savefig(pgfx_plot, file_path)
mktempdir() do path open(file_path) do io
file_path = joinpath(path, "annotations.tex") lines = readlines(io)
@test_nowarn savefig(pgfx_plot, file_path) @test count(s -> occursin("node", s), lines) == 3
open(file_path) do io end
lines = readlines(io) end
@test count(s -> occursin("node", s), lines) == 3 annotation_plot = scatter!(
end range(2, stop = 8, length = 6),
end rand(6),
annotation_plot = scatter!( marker = (50, 0.2, :orange),
range(2, stop = 8, length = 6), series_annotations = [
rand(6), "series",
marker = (50, 0.2, :orange), "annotations",
series_annotations = [ "map",
"series", "to",
"annotations", "series",
"map", Plots.text("data", :green),
"to", ],
"series", )
Plots.text("data", :green), Plots._update_plot_object(annotation_plot)
], axis_content = Plots.pgfx_axes(annotation_plot.o)[1].contents
) nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content)
Plots._update_plot_object(annotation_plot) @test length(nodes) == 9
axis_content = Plots.pgfx_axes(annotation_plot.o)[1].contents mktempdir() do path
nodes = filter(x -> !isa(x, PGFPlotsX.Plot), axis_content) file_path = joinpath(path, "annotations.tex")
@test length(nodes) == 9 @test_nowarn savefig(annotation_plot, file_path)
mktempdir() do path open(file_path) do io
file_path = joinpath(path, "annotations.tex") lines = readlines(io)
@test_nowarn savefig(annotation_plot, file_path) @test count(s -> occursin("node", s), lines) == 9
open(file_path) do io end
lines = readlines(io) # test .tikz extension
@test count(s -> occursin("node", s), lines) == 9 file_path = joinpath(path, "annotations.tikz")
end @test_nowarn savefig(annotation_plot, file_path)
# test .tikz extension @test_nowarn open(file_path) do io
file_path = joinpath(path, "annotations.tikz") end
@test_nowarn savefig(annotation_plot, file_path) end
@test_nowarn open(file_path) do io end # testset
end @testset "Ribbon" begin
end aa = rand(10)
end # testset bb = rand(10)
@testset "Ribbon" begin cc = rand(10)
aa = rand(10) conf = [aa - cc bb - cc]
bb = rand(10) ribbon_plot = plot(collect(1:10), fill(1, 10), ribbon = (conf[:, 1], conf[:, 2]))
cc = rand(10) Plots._update_plot_object(ribbon_plot)
conf = [aa - cc bb - cc] axis = Plots.pgfx_axes(ribbon_plot.o)[1]
ribbon_plot = plots = filter(x -> x isa PGFPlotsX.Plot, axis.contents)
plot(collect(1:10), fill(1, 10), ribbon = (conf[:, 1], conf[:, 2])) @test length(plots) == 3
Plots._update_plot_object(ribbon_plot) @test haskey(plots[1].options.dict, "fill")
axis = Plots.pgfx_axes(ribbon_plot.o)[1] @test haskey(plots[2].options.dict, "fill")
plots = filter(x -> x isa PGFPlotsX.Plot, axis.contents) @test !haskey(plots[3].options.dict, "fill")
@test length(plots) == 3 @test ribbon_plot.o !== nothing
@test haskey(plots[1].options.dict, "fill") @test ribbon_plot.o.the_plot !== nothing
@test haskey(plots[2].options.dict, "fill") end # testset
@test !haskey(plots[3].options.dict, "fill") @testset "Markers and Paths" begin
@test ribbon_plot.o !== nothing pl = plot(
@test ribbon_plot.o.the_plot !== nothing 5 .- ones(9),
end # testset markershape = [:utriangle, :rect],
@testset "Markers and Paths" begin markersize = 8,
pl = plot( color = [:red, :black],
5 .- ones(9), )
markershape = [:utriangle, :rect], Plots._update_plot_object(pl)
markersize = 8, axis = Plots.pgfx_axes(pl.o)[1]
color = [:red, :black], plots = filter(x -> x isa PGFPlotsX.Plot, axis.contents)
) @test length(plots) == 9
Plots._update_plot_object(pl) end # testset
axis = Plots.pgfx_axes(pl.o)[1] @testset "Groups and Subplots" begin
plots = filter(x -> x isa PGFPlotsX.Plot, axis.contents) group = rand(map((i -> begin
@test length(plots) == 9 "group $(i)"
end # testset end), 1:4), 100)
@testset "Groups and Subplots" begin pl = plot(
group = rand(map((i->begin rand(100),
"group $(i)" layout = @layout([a b; c]),
end), 1:4), 100) group = group,
pl = plot(rand(100), layout = @layout([a b; c]), group = group, linetype = [:bar :scatter :steppre], linecolor = :match) linetype = [:bar :scatter :steppre],
Plots._update_plot_object(pl) linecolor = :match,
axis = Plots.pgfx_axes(pl.o)[1] )
legend_entries = filter(x -> x isa PGFPlotsX.LegendEntry, axis.contents) Plots._update_plot_object(pl)
@test length(legend_entries) == 2 axis = Plots.pgfx_axes(pl.o)[1]
end legend_entries = filter(x -> x isa PGFPlotsX.LegendEntry, axis.contents)
@test length(legend_entries) == 2
end
end # testset end # testset
@testset "Extra kwargs" begin @testset "Extra kwargs" begin
pl = plot(1:5, test = "me") pl = plot(1:5, test = "me")
@test pl[1][1].plotattributes[:extra_kwargs][:test] == "me" @test pl[1][1].plotattributes[:extra_kwargs][:test] == "me"
pl = plot(1:5, test = "me", extra_kwargs = :subplot) pl = plot(1:5, test = "me", extra_kwargs = :subplot)
@test pl[1].attr[:extra_kwargs][:test] == "me" @test pl[1].attr[:extra_kwargs][:test] == "me"
pl = plot(1:5, test = "me", extra_kwargs = :plot) pl = plot(1:5, test = "me", extra_kwargs = :plot)
@test pl.attr[:extra_plot_kwargs][:test] == "me" @test pl.attr[:extra_plot_kwargs][:test] == "me"
pl = plot( pl = plot(
1:5, 1:5,
extra_kwargs = Dict( extra_kwargs = Dict(
:plot => Dict(:test => "me"), :plot => Dict(:test => "me"),
:series => Dict(:and => "me too"), :series => Dict(:and => "me too"),
), ),
) )
@test pl.attr[:extra_plot_kwargs][:test] == "me" @test pl.attr[:extra_plot_kwargs][:test] == "me"
@test pl[1][1].plotattributes[:extra_kwargs][:and] == "me too" @test pl[1][1].plotattributes[:extra_kwargs][:and] == "me too"
pl = plot( pl = plot(
plot(1:5, title = "Line"), plot(1:5, title = "Line"),
scatter( scatter(
1:5, 1:5,
title = "Scatter", title = "Scatter",
extra_kwargs = Dict(:subplot => Dict("axis line shift" => "10pt")), extra_kwargs = Dict(:subplot => Dict("axis line shift" => "10pt")),
), ),
) )
Plots._update_plot_object(pl) Plots._update_plot_object(pl)
axes = Plots.pgfx_axes(pl.o) axes = Plots.pgfx_axes(pl.o)
@test !haskey(axes[1].options.dict, "axis line shift") @test !haskey(axes[1].options.dict, "axis line shift")
@test haskey(axes[2].options.dict, "axis line shift") @test haskey(axes[2].options.dict, "axis line shift")
pl = plot( pl =
x -> x, plot(x -> x, -1:1; add = raw"\node at (0,0.5) {\huge hi};", extra_kwargs = :subplot)
-1:1; @test pl[1][:extra_kwargs] == Dict(:add => raw"\node at (0,0.5) {\huge hi};")
add = raw"\node at (0,0.5) {\huge hi};", Plots._update_plot_object(pl)
extra_kwargs = :subplot, axes = Plots.pgfx_axes(pl.o)
) @test filter(x -> x isa String, axes[1].contents)[1] ==
@test pl[1][:extra_kwargs] == Dict(:add => raw"\node at (0,0.5) {\huge hi};") raw"\node at (0,0.5) {\huge hi};"
Plots._update_plot_object(pl) plot!(pl)
axes = Plots.pgfx_axes(pl.o) @test pl[1][:extra_kwargs] == Dict(:add => raw"\node at (0,0.5) {\huge hi};")
@test filter(x -> x isa String, axes[1].contents)[1] == Plots._update_plot_object(pl)
raw"\node at (0,0.5) {\huge hi};" axes = Plots.pgfx_axes(pl.o)
plot!(pl) @test filter(x -> x isa String, axes[1].contents)[1] ==
@test pl[1][:extra_kwargs] == Dict(:add => raw"\node at (0,0.5) {\huge hi};") raw"\node at (0,0.5) {\huge hi};"
Plots._update_plot_object(pl)
axes = Plots.pgfx_axes(pl.o)
@test filter(x -> x isa String, axes[1].contents)[1] ==
raw"\node at (0,0.5) {\huge hi};"
end # testset end # testset
@testset "Titlefonts" begin @testset "Titlefonts" begin
pl = plot(1:5, title = "Test me", titlefont = (2, :left)) pl = plot(1:5, title = "Test me", titlefont = (2, :left))
@test pl[1][:title] == "Test me" @test pl[1][:title] == "Test me"
@test pl[1][:titlefontsize] == 2 @test pl[1][:titlefontsize] == 2
@test pl[1][:titlefonthalign] == :left @test pl[1][:titlefonthalign] == :left
Plots._update_plot_object(pl) Plots._update_plot_object(pl)
ax_opt = Plots.pgfx_axes(pl.o)[1].options ax_opt = Plots.pgfx_axes(pl.o)[1].options
@test ax_opt["title"] == "Test me" @test ax_opt["title"] == "Test me"
@test(haskey(ax_opt.dict, "title style")) isa Test.Pass @test(haskey(ax_opt.dict, "title style")) isa Test.Pass
pl = plot(1:5, plot_title = "Test me", plot_titlefont = (2, :left)) pl = plot(1:5, plot_title = "Test me", plot_titlefont = (2, :left))
@test pl[:plot_title] == "Test me" @test pl[:plot_title] == "Test me"
@test pl[:plot_titlefontsize] == 2 @test pl[:plot_titlefontsize] == 2
@test pl[:plot_titlefonthalign] == :left @test pl[:plot_titlefonthalign] == :left
pl = heatmap( pl = heatmap(rand(3, 3), colorbar_title = "Test me", colorbar_titlefont = (12, :right))
rand(3, 3), @test pl[1][:colorbar_title] == "Test me"
colorbar_title = "Test me", @test pl[1][:colorbar_titlefontsize] == 12
colorbar_titlefont = (12, :right), @test pl[1][:colorbar_titlefonthalign] == :right
)
@test pl[1][:colorbar_title] == "Test me"
@test pl[1][:colorbar_titlefontsize] == 12
@test pl[1][:colorbar_titlefonthalign] == :right
end # testset end # testset

View File

@ -11,8 +11,8 @@ using RecipesPipeline
end end
@testset "get_axis_limits" begin @testset "get_axis_limits" begin
x = [.1, 5] x = [0.1, 5]
p1 = plot(x, [5, .1], yscale=:log10) p1 = plot(x, [5, 0.1], yscale = :log10)
p2 = plot!(identity) p2 = plot!(identity)
@test all(RecipesPipeline.get_axis_limits(p1, :x) .== x) @test all(RecipesPipeline.get_axis_limits(p1, :x) .== x)
@test all(RecipesPipeline.get_axis_limits(p2, :x) .== x) @test all(RecipesPipeline.get_axis_limits(p2, :x) .== x)

View File

@ -3,27 +3,27 @@ using OffsetArrays
@testset "lens!" begin @testset "lens!" begin
pl = plot(1:5) pl = plot(1:5)
lens!(pl, [1,2], [1,2], inset = (1, bbox(0.0,0.0,0.2,0.2)), colorbar = false) lens!(pl, [1, 2], [1, 2], inset = (1, bbox(0.0, 0.0, 0.2, 0.2)), colorbar = false)
@test length(pl.series_list) == 4 @test length(pl.series_list) == 4
@test pl[2][:colorbar] == :none @test pl[2][:colorbar] == :none
end # testset end # testset
@testset "vline, vspan" begin @testset "vline, vspan" begin
vl = vline([1], widen = false) vl = vline([1], widen = false)
@test Plots.xlims(vl) == (1,2) @test Plots.xlims(vl) == (1, 2)
@test Plots.ylims(vl) == (1,2) @test Plots.ylims(vl) == (1, 2)
vl = vline([1], xlims=(0,2), widen = false) vl = vline([1], xlims = (0, 2), widen = false)
@test Plots.xlims(vl) == (0,2) @test Plots.xlims(vl) == (0, 2)
vl = vline([1], ylims=(-3,5), widen = false) vl = vline([1], ylims = (-3, 5), widen = false)
@test Plots.ylims(vl) == (-3,5) @test Plots.ylims(vl) == (-3, 5)
vsp = vspan([1,3], widen = false) vsp = vspan([1, 3], widen = false)
@test Plots.xlims(vsp) == (1,3) @test Plots.xlims(vsp) == (1, 3)
@test Plots.ylims(vsp) == (0,1) # TODO: might be problematic on log-scales @test Plots.ylims(vsp) == (0, 1) # TODO: might be problematic on log-scales
vsp = vspan([1,3], xlims=(-2,5), widen = false) vsp = vspan([1, 3], xlims = (-2, 5), widen = false)
@test Plots.xlims(vsp) == (-2,5) @test Plots.xlims(vsp) == (-2, 5)
vsp = vspan([1,3], ylims=(-2,5), widen = false) vsp = vspan([1, 3], ylims = (-2, 5), widen = false)
@test Plots.ylims(vsp) == (-2,5) @test Plots.ylims(vsp) == (-2, 5)
end # testset end # testset
@testset "offset axes" begin @testset "offset axes" begin

View File

@ -4,45 +4,45 @@ using Plots, Test
@testset "Set Lims" begin @testset "Set Lims" begin
p = plot(rand(10)) p = plot(rand(10))
xlims!((1,20)) xlims!((1, 20))
@test xlims(p) == (1,20) @test xlims(p) == (1, 20)
ylims!((-1,1)) ylims!((-1, 1))
@test ylims(p) == (-1,1) @test ylims(p) == (-1, 1)
zlims!((-1,1)) zlims!((-1, 1))
@test zlims(p) == (-1,1) @test zlims(p) == (-1, 1)
xlims!(-1,11) xlims!(-1, 11)
@test xlims(p) == (-1,11) @test xlims(p) == (-1, 11)
ylims!((-10,10)) ylims!((-10, 10))
@test ylims(p) == (-10,10) @test ylims(p) == (-10, 10)
zlims!((-10,10)) zlims!((-10, 10))
@test zlims(p) == (-10,10) @test zlims(p) == (-10, 10)
end end
@testset "Set Ticks" begin @testset "Set Ticks" begin
p = plot([0,2,3,4,5,6,7,8,9,10]) p = plot([0, 2, 3, 4, 5, 6, 7, 8, 9, 10])
xticks = 2:6 xticks = 2:6
xticks!(xticks) xticks!(xticks)
@test Plots.get_subplot(current(),1).attr[:xaxis][:ticks] == xticks @test Plots.get_subplot(current(), 1).attr[:xaxis][:ticks] == xticks
yticks = 0.2:0.1:0.7 yticks = 0.2:0.1:0.7
yticks!(yticks) yticks!(yticks)
@test Plots.get_subplot(current(),1).attr[:yaxis][:ticks] == yticks @test Plots.get_subplot(current(), 1).attr[:yaxis][:ticks] == yticks
xticks = [5,6,7.5] xticks = [5, 6, 7.5]
xlabels = ["a","b","c"] xlabels = ["a", "b", "c"]
xticks!(xticks, xlabels) xticks!(xticks, xlabels)
@test Plots.get_subplot(current(),1).attr[:xaxis][:ticks] == (xticks, xlabels) @test Plots.get_subplot(current(), 1).attr[:xaxis][:ticks] == (xticks, xlabels)
yticks = [.5,.6,.75] yticks = [0.5, 0.6, 0.75]
ylabels = ["z","y","x"] ylabels = ["z", "y", "x"]
yticks!(yticks, ylabels) yticks!(yticks, ylabels)
@test Plots.get_subplot(current(),1).attr[:yaxis][:ticks] == (yticks, ylabels) @test Plots.get_subplot(current(), 1).attr[:yaxis][:ticks] == (yticks, ylabels)
end end
end end