Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8eac99a389 | |||
| a65d41ca39 | |||
| dd25893a70 | |||
| 52c081758d | |||
| 7ed9fbd7fc | |||
| 6bb53b5181 | |||
| 9d57817bdf | |||
| 4b94a8c2f6 | |||
| aafc1e1989 | |||
| 4e6c64ebcc | |||
| cb103598d7 | |||
| 5728e9fb46 | |||
| 74a6520960 | |||
| f557ca3b74 | |||
| bb08d61709 | |||
| 857d37c380 | |||
| 261f47fb6c | |||
| 476769fab6 | |||
| f7282dd49a | |||
| 5f09d97ca0 | |||
| e4754f3aeb | |||
| 43fd0e76be | |||
| 3ed781ac2d |
@@ -8,5 +8,3 @@ deps/plotly-latest.min.js
|
||||
deps/build.log
|
||||
deps/deps.jl
|
||||
Manifest.toml
|
||||
dev/
|
||||
test/tmpplotsave.hdf5
|
||||
|
||||
+2
-8
@@ -4,8 +4,8 @@ os:
|
||||
- linux
|
||||
# - osx
|
||||
julia:
|
||||
- 1.0
|
||||
- 1
|
||||
- 1.3
|
||||
- nightly
|
||||
|
||||
matrix:
|
||||
@@ -20,9 +20,6 @@ addons:
|
||||
- xauth
|
||||
- xvfb
|
||||
|
||||
env:
|
||||
- GKS_ENCODING="utf8"
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.julia/artifacts
|
||||
@@ -35,10 +32,7 @@ before_install:
|
||||
notifications:
|
||||
email: true
|
||||
|
||||
after_success:
|
||||
- julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'
|
||||
|
||||
script:
|
||||
- if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
|
||||
- if [[ `uname` = "Linux" ]]; then TESTCMD="xvfb-run julia"; else TESTCMD="julia"; fi
|
||||
- $TESTCMD -e 'using Pkg; Pkg.build(); Pkg.test(coverage=true)'
|
||||
- $TESTCMD -e 'using Pkg; pkg"add https://github.com/mkborregaard/RecipeUtils.jl"; Pkg.build(); Pkg.test(coverage=true)'
|
||||
|
||||
+7
-8
@@ -1,7 +1,7 @@
|
||||
name = "Plots"
|
||||
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
|
||||
author = ["Tom Breloff (@tbreloff)"]
|
||||
version = "1.0.2"
|
||||
version = "0.29.7"
|
||||
|
||||
[deps]
|
||||
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
|
||||
@@ -21,6 +21,7 @@ PlotUtils = "995b91a9-d308-5afd-9ec6-746e21dbc043"
|
||||
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
|
||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||
RecipePipeline = "01d81517-befc-4cb6-b9ec-a95719d0359c"
|
||||
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
|
||||
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
|
||||
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
|
||||
@@ -33,7 +34,7 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
||||
[compat]
|
||||
Contour = "0.5"
|
||||
FFMPEG = "0.2, 0.3"
|
||||
FixedPointNumbers = "0.6, 0.7, 0.8"
|
||||
FixedPointNumbers = "0.6, 0.7"
|
||||
GR = "0.46, 0.47, 0.48"
|
||||
GeometryTypes = "0.7, 0.8"
|
||||
JSON = "0.21"
|
||||
@@ -41,8 +42,8 @@ Measures = "0.3"
|
||||
NaNMath = "0.3"
|
||||
PGFPlotsX = "1.2.0"
|
||||
PlotThemes = "1"
|
||||
PlotUtils = "0.6.5"
|
||||
RecipesBase = "1"
|
||||
PlotUtils = "0.6.1"
|
||||
RecipesBase = "0.8"
|
||||
Reexport = "0.2"
|
||||
Requires = "0.5, 1.0"
|
||||
Showoff = "0.3.1"
|
||||
@@ -53,19 +54,17 @@ julia = "1"
|
||||
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
|
||||
GeometryTypes = "4d00f742-c7ba-57c2-abde-4428a4b178cb"
|
||||
Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44"
|
||||
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
|
||||
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
|
||||
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
|
||||
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
|
||||
LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
|
||||
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
|
||||
PGFPlotsX = "8314cec4-20b6-5062-9cdb-752b83310925"
|
||||
RDatasets = "ce6b1742-4840-55fa-b093-852dadbb1d8b"
|
||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
|
||||
StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd"
|
||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
|
||||
VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92"
|
||||
|
||||
[targets]
|
||||
test = ["FileIO", "GeometryTypes", "Gtk", "ImageMagick", "Images", "LibGit2", "OffsetArrays", "PGFPlotsX", "HDF5", "Random", "RDatasets", "StaticArrays", "StatsPlots", "Test", "UnicodePlots", "VisualRegressionTests"]
|
||||
test = ["FileIO", "GeometryTypes", "Gtk", "ImageMagick", "Images", "LaTeXStrings", "LibGit2", "PGFPlotsX", "Random", "RDatasets", "StatsPlots", "Test", "UnicodePlots", "VisualRegressionTests"]
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
[![][pkgeval-img]][pkgeval-url]
|
||||
[![][gitter-img]][gitter-url]
|
||||
[![][docs-img]][docs-url]
|
||||
[](https://codecov.io/gh/JuliaPlots/Plots.jl)
|
||||
|
||||
#### Created by Tom Breloff (@tbreloff)
|
||||
|
||||
|
||||
+3
-2
@@ -1,7 +1,8 @@
|
||||
environment:
|
||||
matrix:
|
||||
- julia_version: 1.0
|
||||
# - julia_version: 0.7
|
||||
- julia_version: 1
|
||||
- julia_version: 1.3
|
||||
- julia_version: nightly
|
||||
|
||||
platform:
|
||||
@@ -32,7 +33,7 @@ install:
|
||||
- ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1"))
|
||||
|
||||
build_script:
|
||||
- echo "%JL_TEST_SCRIPT%"
|
||||
- echo "%JL_BUILD_SCRIPT%"
|
||||
- C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%"
|
||||
|
||||
test_script:
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
ignore:
|
||||
- "src/backends/inspectdr.jl"
|
||||
- "src/backends/orca.jl"
|
||||
- "src/backends/pgfplots.jl"
|
||||
- "src/backends/plotly.jl"
|
||||
- "src/backends/plotlyjs.jl"
|
||||
- "src/backends/pyplot.jl"
|
||||
- "src/backends/web.jl"
|
||||
- "src/fileio.jl"
|
||||
- "src/ijulia.jl"
|
||||
Vendored
+2
-4
@@ -26,15 +26,13 @@
|
||||
|
||||
using SnoopCompile
|
||||
|
||||
project_flag = string("--project=", joinpath(homedir(), ".julia", "dev", "Plots"))
|
||||
log_path = joinpath(tempdir(), "compiles.log")
|
||||
precompiles_path = joinpath(tempdir(), "precompile")
|
||||
|
||||
# run examples with GR backend, logging what needs to be compiled
|
||||
SnoopCompile.@snoopc project_flag log_path begin
|
||||
SnoopCompile.@snoopc log_path begin
|
||||
using Plots
|
||||
Plots.test_examples(:gr)
|
||||
Plots.test_examples(:plotly, skip = Plots._backend_skips[:plotly])
|
||||
Plots.test_examples(:gr, disp=true)
|
||||
end
|
||||
|
||||
# precompile calls containing the following strings are dropped
|
||||
|
||||
+27
-2
@@ -18,6 +18,31 @@ using Base.Meta
|
||||
import Showoff
|
||||
import StatsBase
|
||||
import JSON
|
||||
import RecipePipeline:
|
||||
_process_userrecipe,
|
||||
_process_plotrecipe,
|
||||
_process_seriesrecipe,
|
||||
_preprocess_args,
|
||||
preprocessArgs!,
|
||||
is_st_supported,
|
||||
finalize_subplot!,
|
||||
recipe_pipeline!,
|
||||
_recipe_init!,
|
||||
_recipe_after_user!,
|
||||
_recipe_after_plot!,
|
||||
_recipe_before_series!,
|
||||
_recipe_finish!,
|
||||
is_st_supported,
|
||||
Formatted,
|
||||
SliceIt,
|
||||
FuncOrFuncs,
|
||||
MaybeNumber,
|
||||
MaybeString,
|
||||
DataPoint,
|
||||
trueOrAllTrue,
|
||||
Surface,
|
||||
AbstractSurface,
|
||||
Volume
|
||||
|
||||
using Requires
|
||||
|
||||
@@ -222,7 +247,7 @@ end
|
||||
|
||||
const CURRENT_BACKEND = CurrentBackend(:none)
|
||||
|
||||
include("precompile.jl")
|
||||
_precompile_()
|
||||
# include("precompile.jl")
|
||||
# _precompile_()
|
||||
|
||||
end # module
|
||||
|
||||
+4
-8
@@ -71,7 +71,6 @@ function buildanimation(anim::Animation, fn::AbstractString,
|
||||
is_animated_gif::Bool=true;
|
||||
fps::Real = 20, loop::Integer = 0,
|
||||
variable_palette::Bool=false,
|
||||
verbose=false,
|
||||
show_msg::Bool=true)
|
||||
if length(anim.frames) == 0
|
||||
throw(ArgumentError("Cannot build empty animations"))
|
||||
@@ -80,23 +79,20 @@ function buildanimation(anim::Animation, fn::AbstractString,
|
||||
fn = abspath(expanduser(fn))
|
||||
animdir = anim.dir
|
||||
framerate = ffmpeg_framerate(fps)
|
||||
verbose_level = (verbose isa Int ? verbose :
|
||||
verbose ? 32 # "info"
|
||||
: 16) # "error"
|
||||
|
||||
if is_animated_gif
|
||||
if variable_palette
|
||||
# generate a colorpalette for each frame for highest quality, but larger filesize
|
||||
palette="palettegen=stats_mode=single[pal],[0:v][pal]paletteuse=new=1"
|
||||
ffmpeg_exe(`-v $verbose_level -framerate $framerate -loop $loop -i $(animdir)/%06d.png -lavfi "$palette" -y $fn`)
|
||||
ffmpeg_exe(`-v 0 -framerate $framerate -loop $loop -i $(animdir)/%06d.png -lavfi "$palette" -y $fn`)
|
||||
else
|
||||
# generate a colorpalette first so ffmpeg does not have to guess it
|
||||
ffmpeg_exe(`-v $verbose_level -i $(animdir)/%06d.png -vf "palettegen=stats_mode=diff" -y "$(animdir)/palette.bmp"`)
|
||||
ffmpeg_exe(`-v 0 -i $(animdir)/%06d.png -vf "palettegen=stats_mode=diff" -y "$(animdir)/palette.bmp"`)
|
||||
# then apply the palette to get better results
|
||||
ffmpeg_exe(`-v $verbose_level -framerate $framerate -loop $loop -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -y $fn`)
|
||||
ffmpeg_exe(` -v 0 -framerate $framerate -loop $loop -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -y $fn`)
|
||||
end
|
||||
else
|
||||
ffmpeg_exe(`-v $verbose_level -framerate $framerate -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`)
|
||||
ffmpeg_exe(`-v 0 -framerate $framerate -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`)
|
||||
end
|
||||
|
||||
show_msg && @info("Saved animation to ", fn)
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ const _arg_desc = KW(
|
||||
:foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.",
|
||||
:foreground_color_title => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of subplot title.",
|
||||
:color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.",
|
||||
:legend => "Bool (show the legend?) or Symbol (legend position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` , `:inline` (note: only some may be supported in each backend)",
|
||||
:legend => "Bool (show the legend?) or Symbol (legend position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` (note: only some may be supported in each backend)",
|
||||
:legendfontfamily => "String or Symbol. Font family of legend entries.",
|
||||
:legendfontsize => "Integer. Font pointsize of legend entries.",
|
||||
:legendfonthalign => "Symbol. Font horizontal alignment of legend entries: :hcenter, :left, :right or :center",
|
||||
|
||||
+35
-48
@@ -450,33 +450,15 @@ const _initial_fontsizes = Dict(:titlefontsize => _subplot_defaults[:titlefonts
|
||||
:tickfontsize => _axis_defaults[:tickfontsize],
|
||||
:guidefontsize => _axis_defaults[:guidefontsize])
|
||||
|
||||
const _internal_args =
|
||||
[:plot_object, :series_plotindex, :markershape_to_add, :letter, :idxfilter]
|
||||
const _all_args = sort(collect(union(map(keys, _all_defaults)...)))
|
||||
|
||||
const _axis_args = sort(union(collect(keys(_axis_defaults))))
|
||||
const _series_args = sort(union(collect(keys(_series_defaults))))
|
||||
const _subplot_args = sort(union(collect(keys(_subplot_defaults))))
|
||||
const _plot_args = sort(union(collect(keys(_plot_defaults))))
|
||||
|
||||
const _magic_axis_args = [:axis, :tickfont, :guidefont, :grid, :minorgrid]
|
||||
const _magic_subplot_args = [:titlefont, :legendfont, :legendtitlefont, ]
|
||||
const _magic_series_args = [:line, :marker, :fill]
|
||||
|
||||
const _all_axis_args = sort(union([_axis_args; _magic_axis_args]))
|
||||
const _all_subplot_args = sort(union([_subplot_args; _magic_subplot_args]))
|
||||
const _all_series_args = sort(union([_series_args; _magic_series_args]))
|
||||
const _all_plot_args = _plot_args
|
||||
|
||||
const _all_args =
|
||||
sort([_all_axis_args; _all_subplot_args; _all_series_args; _all_plot_args])
|
||||
|
||||
is_subplot_attr(k) = k in _all_subplot_args
|
||||
is_series_attr(k) = k in _all_series_args
|
||||
is_axis_attr(k) = Symbol(chop(string(k); head=1, tail=0)) in _all_axis_args
|
||||
is_axis_attr_noletter(k) = k in _all_axis_args
|
||||
is_subplot_attr(k) = haskey(_subplot_defaults, k)
|
||||
is_series_attr(k) = haskey(_series_defaults, k)
|
||||
#is_axis_attr(k) = haskey(_axis_defaults_byletter, k)
|
||||
is_axis_attr(k) = haskey(_axis_defaults, Symbol(chop(string(k); head=1, tail=0)))
|
||||
is_axis_attr_noletter(k) = haskey(_axis_defaults, k)
|
||||
|
||||
RecipesBase.is_key_supported(k::Symbol) = is_attr_supported(k)
|
||||
is_default_attribute(k) = k in _internal_args || k in _all_args || is_axis_attr_noletter(k)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -656,7 +638,6 @@ function default(k::Symbol)
|
||||
letter, key = axis_k
|
||||
return _axis_defaults_byletter[letter][key]
|
||||
end
|
||||
k == :letter && return k # for type recipe processing
|
||||
k in _suppress_warnings || error("Unknown key: ", k)
|
||||
end
|
||||
|
||||
@@ -938,6 +919,22 @@ end
|
||||
function preprocessArgs!(plotattributes::AKW)
|
||||
replaceAliases!(plotattributes, _keyAliases)
|
||||
|
||||
# clear all axis stuff
|
||||
# if haskey(plotattributes, :axis) && plotattributes[:axis] in (:none, nothing, false)
|
||||
# plotattributes[:ticks] = nothing
|
||||
# plotattributes[:foreground_color_border] = RGBA(0,0,0,0)
|
||||
# plotattributes[:foreground_color_axis] = RGBA(0,0,0,0)
|
||||
# plotattributes[:grid] = false
|
||||
# delete!(plotattributes, :axis)
|
||||
# end
|
||||
# for letter in (:x, :y, :z)
|
||||
# asym = Symbol(letter, :axis)
|
||||
# if haskey(plotattributes, asym) || plotattributes[asym] in (:none, nothing, false)
|
||||
# plotattributes[Symbol(letter, :ticks)] = nothing
|
||||
# plotattributes[Symbol(letter, :foreground_color_border)] = RGBA(0,0,0,0)
|
||||
# end
|
||||
# end
|
||||
|
||||
# handle axis args common to all axis
|
||||
args = pop_kw!(plotattributes, :axis, ())
|
||||
for arg in wraptuple(args)
|
||||
@@ -995,6 +992,13 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
processMinorGridArg!(plotattributes, arg, letter)
|
||||
end
|
||||
end
|
||||
# fonts
|
||||
for fontname in (:titlefont, :legendfont, :legendtitlefont)
|
||||
args = pop_kw!(plotattributes, fontname, ())
|
||||
for arg in wraptuple(args)
|
||||
processFontArg!(plotattributes, fontname, arg)
|
||||
end
|
||||
end
|
||||
# handle font args common to all axes
|
||||
for fontname in (:tickfont, :guidefont)
|
||||
args = pop_kw!(plotattributes, fontname, ())
|
||||
@@ -1013,26 +1017,6 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
end
|
||||
end
|
||||
end
|
||||
# handle axes args
|
||||
for k in _axis_args
|
||||
if haskey(plotattributes, k) && k !== :link
|
||||
v = plotattributes[k]
|
||||
for letter in (:x, :y, :z)
|
||||
lk = Symbol(letter, k)
|
||||
if !is_explicit(plotattributes, lk)
|
||||
plotattributes[lk] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# fonts
|
||||
for fontname in (:titlefont, :legendfont, :legendtitlefont)
|
||||
args = pop_kw!(plotattributes, fontname, ())
|
||||
for arg in wraptuple(args)
|
||||
processFontArg!(plotattributes, fontname, arg)
|
||||
end
|
||||
end
|
||||
|
||||
# handle line args
|
||||
for arg in wraptuple(pop_kw!(plotattributes, :line, ()))
|
||||
@@ -1235,7 +1219,7 @@ function convertLegendValue(val::Symbol)
|
||||
:best
|
||||
elseif val in (:no, :none)
|
||||
:none
|
||||
elseif val in (:right, :left, :top, :bottom, :inside, :best, :legend, :topright, :topleft, :bottomleft, :bottomright, :outertopright, :outertopleft, :outertop, :outerright, :outerleft, :outerbottomright, :outerbottomleft, :outerbottom, :inline)
|
||||
elseif val in (:right, :left, :top, :bottom, :inside, :best, :legend, :topright, :topleft, :bottomleft, :bottomright, :outertopright, :outertopleft, :outertop, :outerright, :outerleft, :outerbottomright, :outerbottomleft, :outerbottom)
|
||||
val
|
||||
else
|
||||
error("Invalid symbol for legend: $val")
|
||||
@@ -1487,9 +1471,12 @@ function _update_axis(plt::Plot, sp::Subplot, plotattributes_in::AKW, letter::Sy
|
||||
end
|
||||
|
||||
function _update_axis(axis::Axis, plotattributes_in::AKW, letter::Symbol, subplot_index::Int)
|
||||
# grab magic args (for example `xaxis = (:flip, :log)`)
|
||||
args = wraptuple(get(plotattributes_in, Symbol(letter, :axis), ()))
|
||||
|
||||
# build the KW of arguments from the letter version (i.e. xticks --> ticks)
|
||||
kw = KW()
|
||||
for k in _all_axis_args
|
||||
for k in keys(_axis_defaults)
|
||||
# first get the args without the letter: `tickfont = font(10)`
|
||||
# note: we don't pop because we want this to apply to all axes! (delete after all have finished)
|
||||
if haskey(plotattributes_in, k)
|
||||
@@ -1504,7 +1491,7 @@ function _update_axis(axis::Axis, plotattributes_in::AKW, letter::Symbol, subplo
|
||||
end
|
||||
|
||||
# update the axis
|
||||
attr!(axis; kw...)
|
||||
attr!(axis, args...; kw...)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@@ -84,9 +84,6 @@ function attr!(axis::Axis, args...; kw...)
|
||||
process_axis_arg!(plotattributes, arg)
|
||||
end
|
||||
|
||||
# then preprocess keyword arguments
|
||||
preprocessArgs!(KW(kw))
|
||||
|
||||
# then override for any keywords... only those keywords that already exists in plotattributes
|
||||
for (k,v) in kw
|
||||
if haskey(plotattributes, k)
|
||||
|
||||
+1
-23
@@ -1043,13 +1043,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
viewport_plotarea[3] += legendh + 0.04
|
||||
end
|
||||
end
|
||||
if sp[:legend] == :inline
|
||||
if sp[:yaxis][:mirror]
|
||||
viewport_plotarea[1] += legendw
|
||||
else
|
||||
viewport_plotarea[2] -= legendw
|
||||
end
|
||||
end
|
||||
|
||||
# fill in the plot area background
|
||||
bg = plot_color(sp[:background_color_inside])
|
||||
@@ -1805,21 +1798,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
gr_text(GR.wctondc(xi, yi)..., str)
|
||||
end
|
||||
|
||||
if sp[:legend] == :inline && should_add_to_legend(series)
|
||||
gr_set_font(legendfont(sp))
|
||||
gr_set_textcolor(plot_color(sp[:legendfontcolor]))
|
||||
if sp[:yaxis][:mirror]
|
||||
(_,i) = sp[:xaxis][:flip] ? findmax(x) : findmin(x)
|
||||
GR.settextalign(GR.TEXT_HALIGN_RIGHT, GR.TEXT_VALIGN_HALF)
|
||||
offset = -0.01
|
||||
else
|
||||
(_,i) = sp[:xaxis][:flip] ? findmin(x) : findmax(x)
|
||||
GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF)
|
||||
offset = 0.01
|
||||
end
|
||||
(x_l,y_l) = GR.wctondc(x[i],y[i])
|
||||
gr_text(x_l+offset,y_l,series[:label])
|
||||
end
|
||||
GR.restorestate()
|
||||
end
|
||||
|
||||
@@ -1827,7 +1805,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
hascolorbar(sp) && gr_draw_colorbar(cbar, sp, get_clims(sp))
|
||||
|
||||
# add the legend
|
||||
if !(sp[:legend] in(:none, :inline))
|
||||
if sp[:legend] != :none
|
||||
GR.savestate()
|
||||
GR.selntran(0)
|
||||
GR.setscale(0)
|
||||
|
||||
+35
-65
@@ -83,7 +83,6 @@ if length(HDF5PLOT_MAP_TELEM2STR) < 1
|
||||
"ARRAY" => Array, #Dict won't allow Array to be key in HDF5PLOT_MAP_TELEM2STR
|
||||
|
||||
#Sub-structure types:
|
||||
"ATTR" => Attr,
|
||||
"FONT" => Font,
|
||||
"BOUNDINGBOX" => BoundingBox,
|
||||
"GRIDLAYOUT" => GridLayout,
|
||||
@@ -110,7 +109,7 @@ _hdf5_datapath(subpath::String) = "$_hdf5_dataroot/$subpath"
|
||||
_hdf5_map_str2telem(k::String) = HDF5PLOT_MAP_STR2TELEM[k]
|
||||
_hdf5_map_str2telem(v::Vector) = HDF5PLOT_MAP_STR2TELEM[v[1]]
|
||||
|
||||
function _hdf5_merge!(dest::AKW, src::AKW)
|
||||
function _hdf5_merge!(dest, src)
|
||||
for (k, v) in src
|
||||
if isa(v, Axis)
|
||||
_hdf5_merge!(dest[k].plotattributes, v.plotattributes)
|
||||
@@ -264,15 +263,6 @@ function _hdf5plot_gwrite(grp, k::String, v::Array{T}) where T<:Number #Default
|
||||
grp[k] = v
|
||||
_hdf5plot_writetype(grp, k, HDF5PlotNative)
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Dict)
|
||||
#=
|
||||
tstr = string(Dict)
|
||||
path = HDF5.name(grp) * "/" * k
|
||||
@info("Type not supported: $tstr\npath: $path")
|
||||
=#
|
||||
#No support for structures with Dicts different than KW (plotattributes)
|
||||
end
|
||||
|
||||
#=
|
||||
function _hdf5plot_gwrite(grp, k::String, v::Array{Any})
|
||||
# @show grp, k
|
||||
@@ -305,8 +295,7 @@ function _hdf5plot_gwrite(grp, k::String, v::Tuple)
|
||||
end
|
||||
#NOTE: _hdf5plot_overwritetype overwrites "Array" type with "Tuple".
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, plotattributes::KW)
|
||||
#NOTE: Can only write directly to group, not a subi-item
|
||||
function _hdf5plot_gwrite(grp, k::String, plotattributes::AKW)
|
||||
# @warn("Cannot write dict: $k=$plotattributes")
|
||||
end
|
||||
function _hdf5plot_gwrite(grp, k::String, v::AbstractRange)
|
||||
@@ -387,22 +376,13 @@ function _hdf5plot_gwrite(grp, k::String, v::Subplot)
|
||||
_hdf5plot_writetype(grp, Subplot)
|
||||
return
|
||||
end
|
||||
|
||||
function _hdf5plot_write(grp, plotattributes::KW)
|
||||
function _hdf5plot_write(grp, plotattributes::AKW)
|
||||
for (k, v) in plotattributes
|
||||
kstr = string(k)
|
||||
_hdf5plot_gwrite(grp, kstr, v)
|
||||
end
|
||||
return
|
||||
end
|
||||
function _hdf5plot_write(grp, plotattributes::Attr)
|
||||
for (k, v) in plotattributes
|
||||
kstr = string(k)
|
||||
_hdf5plot_gwrite(grp, kstr, v)
|
||||
end
|
||||
_hdf5plot_writetype(grp, Attr)
|
||||
end
|
||||
|
||||
|
||||
# Write main plot structures:
|
||||
# ----------------------------------------------------------------
|
||||
@@ -465,14 +445,20 @@ _hdf5plot_convert(T::Type{Extrema}, v) = Extrema(v[1], v[2])
|
||||
# Read data structures:
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _hdf5plot_read(grp, k::String, T::Type)
|
||||
function _hdf5plot_read(grp, k::String, T::Type, dtid)
|
||||
v = HDF5.d_read(grp, k)
|
||||
return _hdf5plot_convert(T, v)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Length}, dtid::Vector)
|
||||
v = HDF5.d_read(grp, k)
|
||||
TU = Symbol(dtid[2])
|
||||
T = typeof(v)
|
||||
return Length{TU,T}(v)
|
||||
end
|
||||
|
||||
# Read more complex data structures:
|
||||
# ----------------------------------------------------------------
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Font})
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Font}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
family = _hdf5plot_read(grp, "family")
|
||||
@@ -483,7 +469,7 @@ function _hdf5plot_read(grp, k::String, T::Type{Font})
|
||||
color = _hdf5plot_read(grp, "color")
|
||||
return Font(family, pointsize, halign, valign, rotation, color)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Array}) #ANY
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Array}, dtid) #ANY
|
||||
grp = HDF5.g_open(grp, k)
|
||||
sz = _hdf5plot_read(grp, "dim")
|
||||
if [0] == sz; return []; end
|
||||
@@ -502,18 +488,18 @@ function _hdf5plot_read(grp, k::String, T::Type{Array}) #ANY
|
||||
result = [result[iter] for iter in eachindex(result)] #Potentially make more specific
|
||||
return reshape(result, sz)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{HDF5CTuple})
|
||||
v = _hdf5plot_read(grp, k, Array)
|
||||
function _hdf5plot_read(grp, k::String, T::Type{HDF5CTuple}, dtid)
|
||||
v = _hdf5plot_read(grp, k, Array, dtid)
|
||||
return tuple(v...)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{PlotText})
|
||||
function _hdf5plot_read(grp, k::String, T::Type{PlotText}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
str = _hdf5plot_read(grp, "str")
|
||||
font = _hdf5plot_read(grp, "font")
|
||||
return PlotText(str, font)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{SeriesAnnotations})
|
||||
function _hdf5plot_read(grp, k::String, T::Type{SeriesAnnotations}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
strs = _hdf5plot_read(grp, "strs")
|
||||
@@ -522,29 +508,29 @@ function _hdf5plot_read(grp, k::String, T::Type{SeriesAnnotations})
|
||||
scalefactor = _hdf5plot_read(grp, "scalefactor")
|
||||
return SeriesAnnotations(strs, font, baseshape, scalefactor)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Shape})
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Shape}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
x = _hdf5plot_read(grp, "x")
|
||||
y = _hdf5plot_read(grp, "y")
|
||||
return Shape(x, y)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{ColorGradient})
|
||||
function _hdf5plot_read(grp, k::String, T::Type{ColorGradient}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
colors = _hdf5plot_read(grp, "colors")
|
||||
values = _hdf5plot_read(grp, "values")
|
||||
return ColorGradient(colors, values)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{BoundingBox})
|
||||
function _hdf5plot_read(grp, k::String, T::Type{BoundingBox}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
x0 = _hdf5plot_read(grp, "x0")
|
||||
a = _hdf5plot_read(grp, "a")
|
||||
return BoundingBox(x0, a)
|
||||
end
|
||||
_hdf5plot_read(grp, k::String, T::Type{RootLayout}) = RootLayout()
|
||||
function _hdf5plot_read(grp, k::String, T::Type{GridLayout})
|
||||
_hdf5plot_read(grp, k::String, T::Type{RootLayout}, dtid) = RootLayout()
|
||||
function _hdf5plot_read(grp, k::String, T::Type{GridLayout}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
|
||||
# parent = _hdf5plot_read(grp, "parent")
|
||||
@@ -558,36 +544,22 @@ parent = RootLayout()
|
||||
|
||||
return GridLayout(parent, minpad, bbox, grid, widths, heights, attr)
|
||||
end
|
||||
function _hdf5plot_read(grp, T::Type{Attr})
|
||||
attr = Attr(KW(), _plot_defaults)
|
||||
v = _hdf5plot_read(grp, attr)
|
||||
return attr
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Axis})
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Axis}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
plotattributes = Attr(KW(), _plot_defaults)
|
||||
_hdf5plot_read(grp, plotattributes)
|
||||
return Axis([], plotattributes)
|
||||
kwlist = KW()
|
||||
_hdf5plot_read(grp, kwlist)
|
||||
return Axis([], kwlist)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Surface})
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Surface}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
data2d = _hdf5plot_read(grp, "data2d")
|
||||
return Surface(data2d)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Subplot})
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Subplot}, dtid)
|
||||
grp = HDF5.g_open(grp, k)
|
||||
idx = _hdf5plot_read(grp, "index")
|
||||
return HDF5PLOT_PLOTREF.ref.subplots[idx]
|
||||
end
|
||||
|
||||
#Most types don't need dtid for read!!:
|
||||
_hdf5plot_read(grp, k::String, T::Type, dtid) = _hdf5plot_read(grp, k, T)
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Length}, dtid::Vector)
|
||||
v = HDF5.d_read(grp, k)
|
||||
TU = Symbol(dtid[2])
|
||||
T = typeof(v)
|
||||
return Length{TU,T}(v)
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String)
|
||||
dtid = HDF5.a_read(grp[k], _hdf5plot_datatypeid)
|
||||
T = _hdf5_map_str2telem(dtid) #expect exception
|
||||
@@ -595,7 +567,7 @@ function _hdf5plot_read(grp, k::String)
|
||||
end
|
||||
|
||||
#Read in values in group to populate plotattributes:
|
||||
function _hdf5plot_readattr(grp, plotattributes::AbstractDict)
|
||||
function _hdf5plot_read(grp, plotattributes::AKW)
|
||||
gnames = names(grp)
|
||||
for k in gnames
|
||||
try
|
||||
@@ -609,8 +581,6 @@ function _hdf5plot_readattr(grp, plotattributes::AbstractDict)
|
||||
end
|
||||
return
|
||||
end
|
||||
_hdf5plot_read(grp, plotattributes::KW) = _hdf5plot_readattr(grp, plotattributes)
|
||||
_hdf5plot_read(grp, plotattributes::Attr) = _hdf5plot_readattr(grp, plotattributes)
|
||||
|
||||
# Read main plot structures:
|
||||
# ----------------------------------------------------------------
|
||||
@@ -623,17 +593,17 @@ function _hdf5plot_read(sp::Subplot, subpath::String, f)
|
||||
|
||||
for i in 1:nseries
|
||||
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list/series$i"))
|
||||
seriesinfo = Attr(KW(), _plot_defaults)
|
||||
_hdf5plot_read(grp, seriesinfo)
|
||||
plot!(sp, seriesinfo[:x], seriesinfo[:y]) #Add data & create data structures
|
||||
_hdf5_merge!(sp.series_list[end].plotattributes, seriesinfo)
|
||||
kwlist = KW()
|
||||
_hdf5plot_read(grp, kwlist)
|
||||
plot!(sp, kwlist[:x], kwlist[:y]) #Add data & create data structures
|
||||
_hdf5_merge!(sp.series_list[end].plotattributes, kwlist)
|
||||
end
|
||||
|
||||
#Perform after adding series... otherwise values get overwritten:
|
||||
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/attr"))
|
||||
attr = Attr(KW(), _plot_defaults)
|
||||
_hdf5plot_read(grp, attr)
|
||||
_hdf5_merge!(sp.attr, attr)
|
||||
kwlist = KW()
|
||||
_hdf5plot_read(grp, kwlist)
|
||||
_hdf5_merge!(sp.attr, kwlist)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
+23
-109
@@ -29,20 +29,11 @@ Base.@kwdef mutable struct PGFPlotsXPlot
|
||||
PGFPlotsX.push_preamble!(
|
||||
pgfx_plot.the_plot,
|
||||
raw"""
|
||||
\pgfplotsset{%
|
||||
layers/standard/.define layer set={%
|
||||
background,axis background,axis grid,axis ticks,axis lines,axis tick labels,pre main,main,axis descriptions,axis foreground%
|
||||
}{grid style= {/pgfplots/on layer=axis grid},%
|
||||
tick style= {/pgfplots/on layer=axis ticks},%
|
||||
axis line style= {/pgfplots/on layer=axis lines},%
|
||||
label style= {/pgfplots/on layer=axis descriptions},%
|
||||
legend style= {/pgfplots/on layer=axis descriptions},%
|
||||
title style= {/pgfplots/on layer=axis descriptions},%
|
||||
colorbar style= {/pgfplots/on layer=axis descriptions},%
|
||||
ticklabel style= {/pgfplots/on layer=axis tick labels},%
|
||||
axis background@ style={/pgfplots/on layer=axis background},%
|
||||
3d box foreground style={/pgfplots/on layer=axis foreground},%
|
||||
},
|
||||
\pgfplotsset{
|
||||
/pgfplots/layers/axis on top/.define layer set={
|
||||
background, axis background,pre main,main,axis grid,axis ticks,axis lines,axis tick labels,
|
||||
axis descriptions,axis foreground
|
||||
}{/pgfplots/layers/standard},
|
||||
}
|
||||
""",
|
||||
)
|
||||
@@ -85,7 +76,6 @@ end
|
||||
|
||||
function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
if !pgfx_plot.is_created || pgfx_plot.was_shown
|
||||
pgfx_sanitize_plot!(plt)
|
||||
the_plot = PGFPlotsX.TikzPicture(PGFPlotsX.Options())
|
||||
bgc = plt.attr[:background_color_outside] == :match ?
|
||||
plt.attr[:background_color] : plt.attr[:background_color_outside]
|
||||
@@ -117,24 +107,14 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
fg_alpha = alpha(plot_color(sp[:foreground_color_legend]))
|
||||
title_cstr = plot_color(sp[:titlefontcolor])
|
||||
title_a = alpha(title_cstr)
|
||||
title_loc = sp[:title_location]
|
||||
bgc_inside = plot_color(sp[:background_color_inside])
|
||||
bgc_inside_a = alpha(bgc_inside)
|
||||
axis_opt = PGFPlotsX.Options(
|
||||
"title" => sp[:title],
|
||||
"title style" => PGFPlotsX.Options(
|
||||
"at" => if title_loc == :left
|
||||
"{(0,1)}"
|
||||
elseif title_loc == :right
|
||||
"{(1,1)}"
|
||||
elseif title_loc isa Tuple
|
||||
"{$(string(title_loc))}"
|
||||
else
|
||||
"{(0.5,1)}"
|
||||
end,
|
||||
"font" => pgfx_font(
|
||||
sp[:titlefontsize],
|
||||
pgfx_thickness_scaling(sp),
|
||||
"font" => pgfx_font(
|
||||
sp[:titlefontsize],
|
||||
pgfx_thickness_scaling(sp),
|
||||
),
|
||||
"color" => title_cstr,
|
||||
"draw opacity" => title_a,
|
||||
@@ -159,6 +139,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
"fill" => bgc_inside,
|
||||
"opacity" => bgc_inside_a,
|
||||
),
|
||||
"axis on top" => nothing,
|
||||
# "framed" => nothing,
|
||||
# These are for layouting
|
||||
"anchor" => "north west",
|
||||
"xshift" => string(dx),
|
||||
@@ -277,7 +259,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
if marker isa Shape
|
||||
x = marker.x
|
||||
y = marker.y
|
||||
scale_factor = 0.00125
|
||||
scale_factor = 0.025
|
||||
mark_size = opt[:markersize] * scale_factor
|
||||
path = join(
|
||||
[
|
||||
@@ -364,28 +346,28 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
)
|
||||
end
|
||||
# add to legend?
|
||||
if sp[:legend] != :none
|
||||
if sp[:legend] != :none && pgfx_should_add_to_legend(series)
|
||||
leg_entry = if opt[:label] isa AVec
|
||||
get(opt[:label], i, "")
|
||||
elseif opt[:label] isa AbstractString
|
||||
if i == 1
|
||||
get(opt, :label, "")
|
||||
opt[:label]
|
||||
else
|
||||
""
|
||||
end
|
||||
else
|
||||
throw(ArgumentError("Malformed label. label = $(opt[:label])"))
|
||||
end
|
||||
if leg_entry == "" || !pgfx_should_add_to_legend(series)
|
||||
push!(axis.contents[end].options, "forget plot" => nothing)
|
||||
else
|
||||
leg_opt = PGFPlotsX.Options()
|
||||
if ribbon !== nothing
|
||||
pgfx_filllegend!(axis.contents[end - 3].options, opt)
|
||||
end
|
||||
legend = PGFPlotsX.LegendEntry(leg_opt, leg_entry, false)
|
||||
push!(axis, legend)
|
||||
if leg_entry == ""
|
||||
push!(segment_plot.options, "forget plot" => nothing)
|
||||
continue
|
||||
end
|
||||
leg_opt = PGFPlotsX.Options()
|
||||
if ribbon !== nothing
|
||||
pgfx_filllegend!(axis.contents[end - 3].options, opt)
|
||||
end
|
||||
legend = PGFPlotsX.LegendEntry(leg_opt, leg_entry, false)
|
||||
push!(axis, legend)
|
||||
end
|
||||
end # for segments
|
||||
# add subplot annotations
|
||||
@@ -604,22 +586,11 @@ const _pgfplotsx_markers = KW(
|
||||
)
|
||||
|
||||
const _pgfplotsx_legend_pos = KW(
|
||||
:top => "north",
|
||||
:bottom => "south",
|
||||
:left => "west",
|
||||
:right => "east",
|
||||
:bottomleft => "south west",
|
||||
:bottomright => "south east",
|
||||
:topright => "north east",
|
||||
:topleft => "north west",
|
||||
:outertop => "north",
|
||||
:outerbottom => "outer south",
|
||||
:outerleft => "outer west",
|
||||
:outerright => "outer east",
|
||||
:outerbottomleft => "outer south west",
|
||||
:outerbottomright => "outer south east",
|
||||
:outertopright => "outer north east",
|
||||
:outertopleft => "outer north west",
|
||||
)
|
||||
|
||||
const _pgfx_framestyles = [:box, :axes, :origin, :zerolines, :grid, :none]
|
||||
@@ -720,7 +691,7 @@ function pgfx_font(fontsize, thickness_scaling = 1, font = "\\selectfont")
|
||||
end
|
||||
|
||||
function pgfx_should_add_to_legend(series::Series)
|
||||
series.plotattributes[:primary] &&
|
||||
series.plotattributes[:primary] && series.plotattributes[:label] != "" &&
|
||||
!(
|
||||
series.plotattributes[:seriestype] in (
|
||||
:hexbin,
|
||||
@@ -891,48 +862,6 @@ function pgfx_fillrange_args(fillrange, x, y, z)
|
||||
z_fill = [z; _cycle(fillrange, n:-1:1); z[1]]
|
||||
return PGFPlotsX.Coordiantes(x_fill, y_fill, z_fill)
|
||||
end
|
||||
|
||||
function pgfx_sanitize_string(p::PlotText)
|
||||
PlotText(pgfx_sanitize_string(p.str), p.font)
|
||||
end
|
||||
function pgfx_sanitize_string(s::AbstractString)
|
||||
s = replace(s, r"\\?\#" => "\\#")
|
||||
s = replace(s, r"\\?\%" => "\\%")
|
||||
s = replace(s, r"\\?\_" => "\\_")
|
||||
s = replace(s, r"\\?\&" => "\\&")
|
||||
end
|
||||
function pgfx_sanitize_plot!(plt)
|
||||
for (key, value) in plt.attr
|
||||
if value isa Union{AbstractString, AbstractVector{<:AbstractString}}
|
||||
plt.attr[key] = pgfx_sanitize_string.(value)
|
||||
end
|
||||
end
|
||||
for subplot in plt.subplots
|
||||
for (key, value) in subplot.attr
|
||||
if key == :annotations && subplot.attr[:annotations] !== nothing
|
||||
old_ann = subplot.attr[key]
|
||||
for i in eachindex(old_ann)
|
||||
subplot.attr[key][i] = (old_ann[i][1], old_ann[i][2], pgfx_sanitize_string(old_ann[i][3]))
|
||||
end
|
||||
elseif value isa Union{AbstractString, AbstractVector{<:AbstractString}}
|
||||
subplot.attr[key] = pgfx_sanitize_string.(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
for series in plt.series_list
|
||||
for (key, value) in series.plotattributes
|
||||
if key == :series_annotations && series.plotattributes[:series_annotations] !== nothing
|
||||
old_ann = series.plotattributes[key].strs
|
||||
for i in eachindex(old_ann)
|
||||
series.plotattributes[key].strs[i] = pgfx_sanitize_string(old_ann[i])
|
||||
end
|
||||
elseif value isa Union{AbstractString, AbstractVector{<:AbstractString}}
|
||||
series.plotattributes[key] = pgfx_sanitize_string.(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
##
|
||||
end
|
||||
# --------------------------------------------------------------------------------------
|
||||
function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
|
||||
axis = sp[Symbol(letter, :axis)]
|
||||
@@ -943,21 +872,6 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
|
||||
"scaled $(letter) ticks" => "false",
|
||||
string(letter, :label) => axis[:guide],
|
||||
)
|
||||
tick_color = plot_color(axis[:foreground_color_axis])
|
||||
push!(opt,
|
||||
"$(letter) tick style" => PGFPlotsX.Options(
|
||||
"color" => color(tick_color),
|
||||
"opacity" => alpha(tick_color),
|
||||
),
|
||||
)
|
||||
tick_label_color = plot_color(axis[:tickfontcolor])
|
||||
push!(opt,
|
||||
"$(letter) tick label style" => PGFPlotsX.Options(
|
||||
"color" => color(tick_color),
|
||||
"opacity" => alpha(tick_color),
|
||||
"rotate" => axis[:rotation]
|
||||
),
|
||||
)
|
||||
|
||||
# set to supported framestyle
|
||||
framestyle = pgfx_framestyle(sp[:framestyle] == false ? :none : sp[:framestyle])
|
||||
|
||||
@@ -42,8 +42,8 @@ _display(plt::Plot{PlotlyJSBackend}) = display(plotlyjs_syncplot(plt))
|
||||
|
||||
@require WebIO = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29" begin
|
||||
function WebIO.render(plt::Plot{PlotlyJSBackend})
|
||||
plt_html = sprint(show, MIME("text/html"), plt)
|
||||
return WebIO.render(dom"div"(innerHTML=plt_html))
|
||||
prepare_output(plt)
|
||||
WebIO.render(plt.o)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+1
-51
@@ -594,9 +594,7 @@ function process_annotation(sp::Subplot, xs, ys, labs, font = font())
|
||||
xlength = length(methods(length, (typeof(xs),))) == 0 ? 1 : length(xs)
|
||||
ylength = length(methods(length, (typeof(ys),))) == 0 ? 1 : length(ys)
|
||||
for i in 1:max(xlength, ylength, length(labs))
|
||||
x, y, lab = _cycle(xs, i), _cycle(ys, i), _cycle(labs, i)
|
||||
x = typeof(x) <: TimeType ? Dates.value(x) : x
|
||||
y = typeof(y) <: TimeType ? Dates.value(y) : y
|
||||
x, y, lab = _cycle(xs, i), _cycle(ys, i), _cycle(labs, i)
|
||||
if lab == :auto
|
||||
alphabet = "abcdefghijklmnopqrstuvwxyz"
|
||||
push!(anns, (x, y, text(string("(", alphabet[sp[:subplot_index]], ")"), font)))
|
||||
@@ -652,23 +650,6 @@ end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
abstract type AbstractSurface end
|
||||
|
||||
"represents a contour or surface mesh"
|
||||
struct Surface{M<:AMat} <: AbstractSurface
|
||||
surf::M
|
||||
end
|
||||
|
||||
Surface(f::Function, x, y) = Surface(Float64[f(xi,yi) for yi in y, xi in x])
|
||||
|
||||
Base.Array(surf::Surface) = surf.surf
|
||||
|
||||
for f in (:length, :size, :axes)
|
||||
@eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...)
|
||||
end
|
||||
Base.copy(surf::Surface) = Surface(copy(surf.surf))
|
||||
Base.eltype(surf::Surface{T}) where {T} = eltype(T)
|
||||
|
||||
function expand_extrema!(a::Axis, surf::Surface)
|
||||
ex = a[:extrema]
|
||||
for vi in surf.surf
|
||||
@@ -688,30 +669,6 @@ end
|
||||
# # I don't want to clash with ValidatedNumerics, but this would be nice:
|
||||
# ..(a::T, b::T) = (a,b)
|
||||
|
||||
struct Volume{T}
|
||||
v::Array{T,3}
|
||||
x_extents::Tuple{T,T}
|
||||
y_extents::Tuple{T,T}
|
||||
z_extents::Tuple{T,T}
|
||||
end
|
||||
|
||||
default_extents(::Type{T}) where {T} = (zero(T), one(T))
|
||||
|
||||
function Volume(v::Array{T,3},
|
||||
x_extents = default_extents(T),
|
||||
y_extents = default_extents(T),
|
||||
z_extents = default_extents(T)) where T
|
||||
Volume(v, x_extents, y_extents, z_extents)
|
||||
end
|
||||
|
||||
Base.Array(vol::Volume) = vol.v
|
||||
for f in (:length, :size)
|
||||
@eval Base.$f(vol::Volume, args...) = $f(vol.v, args...)
|
||||
end
|
||||
Base.copy(vol::Volume{T}) where {T} = Volume{T}(copy(vol.v), vol.x_extents, vol.y_extents, vol.z_extents)
|
||||
Base.eltype(vol::Volume{T}) where {T} = T
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
# style is :open or :closed (for now)
|
||||
struct Arrow
|
||||
@@ -773,13 +730,6 @@ function add_arrows(func::Function, x::AVec, y::AVec)
|
||||
end
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"Represents data values with formatting that should apply to the tick labels."
|
||||
struct Formatted{T}
|
||||
data::T
|
||||
formatter::Function
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
"create a BezierCurve for plotting"
|
||||
|
||||
+1
-71
@@ -875,77 +875,6 @@ const _examples = PlotExample[
|
||||
end,
|
||||
],
|
||||
),
|
||||
PlotExample(
|
||||
"Array Types",
|
||||
"Plots supports different `Array` types that follow the `AbstractArray` interface, like `StaticArrays` and `OffsetArrays.`",
|
||||
[
|
||||
quote
|
||||
begin
|
||||
using StaticArrays, OffsetArrays
|
||||
sv = SVector{10}(rand(10))
|
||||
ov = OffsetVector(rand(10), -2)
|
||||
plot([sv, ov], label = ["StaticArray" "OffsetArray"])
|
||||
end
|
||||
end,
|
||||
],
|
||||
),
|
||||
PlotExample(
|
||||
"Setting defaults and font arguments",
|
||||
"",
|
||||
[
|
||||
quote
|
||||
begin
|
||||
using Plots
|
||||
default(
|
||||
titlefont = (20, "times"),
|
||||
legendfontsize = 18,
|
||||
guidefont = (18, :darkgreen),
|
||||
tickfont = (12, :orange),
|
||||
guide = "x",
|
||||
framestyle = :zerolines,
|
||||
yminorgrid = true
|
||||
)
|
||||
plot(
|
||||
[sin, cos],
|
||||
-2π,
|
||||
2π,
|
||||
label = ["sin(θ)" "cos(θ)"],
|
||||
title = "Trigonometric Functions",
|
||||
xlabel = "θ",
|
||||
linewidth = 2,
|
||||
legend = :outertopleft,
|
||||
)
|
||||
end
|
||||
end,
|
||||
],
|
||||
),
|
||||
PlotExample(
|
||||
"Heatmap with DateTime axis",
|
||||
"",
|
||||
[
|
||||
quote
|
||||
begin
|
||||
using Dates
|
||||
z = rand(5, 5)
|
||||
x = DateTime.(2016:2020)
|
||||
y = 1:5
|
||||
heatmap(x, y, z)
|
||||
end
|
||||
end,
|
||||
],
|
||||
),
|
||||
PlotExample(
|
||||
"Linked axes",
|
||||
"",
|
||||
[
|
||||
quote
|
||||
begin
|
||||
x = -5:0.1:5
|
||||
plot(plot(x, x->x^2), plot(x, x->sin(x)), layout = 2, link = :y)
|
||||
end
|
||||
end,
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
# Some constants for PlotDocs and PlotReferenceImages
|
||||
@@ -958,6 +887,7 @@ _backend_skips = Dict(
|
||||
:pgfplots => [2, 5, 6, 10, 16, 20, 22, 23, 25, 28, 30, 31, 34, 37, 38, 39],
|
||||
:pgfplotsx => [ 6, # images
|
||||
10, # histogram2d
|
||||
20, # annotations due to missing sanitation
|
||||
22, # contourf
|
||||
23, # pie
|
||||
32, # spy
|
||||
|
||||
@@ -52,14 +52,6 @@ function tex(plt::Plot, fn::AbstractString)
|
||||
end
|
||||
tex(fn::AbstractString) = tex(current(), fn)
|
||||
|
||||
function json(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "json")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("application/vnd.plotly.v1+json"), plt)
|
||||
close(io)
|
||||
end
|
||||
json(fn::AbstractString) = json(current(), fn)
|
||||
|
||||
function html(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "html")
|
||||
io = open(fn, "w")
|
||||
@@ -86,7 +78,6 @@ const _savemap = Dict(
|
||||
"ps" => ps,
|
||||
"eps" => eps,
|
||||
"tex" => tex,
|
||||
"json" => json,
|
||||
"html" => html,
|
||||
"tikz" => tex,
|
||||
"txt" => txt,
|
||||
|
||||
+16
-143
@@ -1,29 +1,13 @@
|
||||
# Error for aliases used in recipes
|
||||
function warn_on_recipe_aliases!(plotattributes, recipe_type, args...)
|
||||
for k in keys(plotattributes)
|
||||
if !is_default_attribute(k)
|
||||
dk = get(_keyAliases, k, k)
|
||||
if k !== dk
|
||||
@warn "Attribute alias `$k` detected in the $recipe_type recipe defined for the signature $(signature_string(Val{recipe_type}, args...)). To ensure expected behavior it is recommended to use the default attribute `$dk`."
|
||||
end
|
||||
plotattributes[dk] = pop_kw!(plotattributes, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
function warn_on_recipe_aliases!(v::AbstractVector, recipe_type, args...)
|
||||
foreach(x -> warn_on_recipe_aliases!(x, recipe_type, args...), v)
|
||||
end
|
||||
function warn_on_recipe_aliases!(rd::RecipeData, recipe_type, args...)
|
||||
warn_on_recipe_aliases!(rd.plotattributes, recipe_type, args...)
|
||||
function finalize_subplot!(plt::Plot, st, plotattributes::AKW)
|
||||
sp = _prepare_subplot(plt, plotattributes)
|
||||
_prepare_annotations(sp, plotattributes)
|
||||
_expand_subplot_extrema(sp, plotattributes, st)
|
||||
_update_series_attributes!(plotattributes, plt, sp)
|
||||
_add_the_series(plt, sp, plotattributes)
|
||||
end
|
||||
|
||||
function signature_string(::Type{Val{:user}}, args...)
|
||||
return string("(::", join(string.(typeof.(args)), ", ::"), ")")
|
||||
end
|
||||
signature_string(::Type{Val{:type}}, T) = "(::Type{$T}, ::$T)"
|
||||
signature_string(::Type{Val{:plot}}, st) = "(::Type{Val{:$st}}, ::AbstractPlot)"
|
||||
signature_string(::Type{Val{:series}}, st) = "(::Type{Val{:$st}}, x, y, z)"
|
||||
|
||||
# Override the RecipesPipeline `is_st_supported` for Plots.
|
||||
is_st_supported(::Plot, st::Symbol) = is_seriestype_supported(st)
|
||||
# ------------------------------------------------------------------
|
||||
# preprocessing
|
||||
|
||||
@@ -64,8 +48,14 @@ function _preprocess_args(plotattributes::AKW, args, still_to_process::Vector{Re
|
||||
# remove subplot and axis args from plotattributes... they will be passed through in the kw_list
|
||||
if !isempty(args)
|
||||
for (k,v) in plotattributes
|
||||
if k in _all_subplot_args || k in _all_axis_args
|
||||
reset_kw!(plotattributes, k)
|
||||
for defdict in (_subplot_defaults,
|
||||
_axis_defaults,
|
||||
_axis_defaults_byletter[:x],
|
||||
_axis_defaults_byletter[:y],
|
||||
_axis_defaults_byletter[:z])
|
||||
if haskey(defdict, k)
|
||||
reset_kw!(plotattributes, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -77,43 +67,6 @@ end
|
||||
# user recipes
|
||||
|
||||
|
||||
function _process_userrecipes(plt::Plot, plotattributes::AKW, args)
|
||||
still_to_process = RecipeData[]
|
||||
args = _preprocess_args(plotattributes, args, still_to_process)
|
||||
|
||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||||
# we are keeping a stack of series that still need to be processed.
|
||||
# each pass through the loop, we pop one off and apply the recipe.
|
||||
# the recipe will return a list a Series objects... the ones that are
|
||||
# finished (no more args) get added to the kw_list, the ones that are not
|
||||
# are placed on top of the stack and are then processed further.
|
||||
kw_list = KW[]
|
||||
while !isempty(still_to_process)
|
||||
# grab the first in line to be processed and either add it to the kw_list or
|
||||
# pass it through apply_recipe to generate a list of RecipeData objects (data + attributes)
|
||||
# for further processing.
|
||||
next_series = popfirst!(still_to_process)
|
||||
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
|
||||
if !(typeof(next_series) <: RecipeData)
|
||||
error("Inputs couldn't be processed... expected RecipeData but got: $next_series")
|
||||
end
|
||||
if isempty(next_series.args)
|
||||
_process_userrecipe(plt, kw_list, next_series)
|
||||
else
|
||||
rd_list = RecipesBase.apply_recipe(
|
||||
next_series.plotattributes,
|
||||
next_series.args...
|
||||
)
|
||||
warn_on_recipe_aliases!(rd_list, :user, next_series.args...)
|
||||
prepend!(still_to_process,rd_list)
|
||||
end
|
||||
end
|
||||
|
||||
# don't allow something else to handle it
|
||||
plotattributes[:smooth] = false
|
||||
kw_list
|
||||
end
|
||||
|
||||
function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeData)
|
||||
# when the arg tuple is empty, that means there's nothing left to recursively
|
||||
# process... finish up and add to the kw_list
|
||||
@@ -190,40 +143,6 @@ function _add_smooth_kw(kw_list::Vector{KW}, kw::AKW)
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# plot recipes
|
||||
|
||||
# Grab the first in line to be processed and pass it through apply_recipe
|
||||
# to generate a list of RecipeData objects (data + attributes).
|
||||
# If we applied a "plot recipe" without error, then add the returned datalist's KWs,
|
||||
# otherwise we just add the original KW.
|
||||
function _process_plotrecipe(plt::Plot, kw::AKW, kw_list::Vector{KW}, still_to_process::Vector{KW})
|
||||
if !isa(get(kw, :seriestype, nothing), Symbol)
|
||||
# seriestype was never set, or it's not a Symbol, so it can't be a plot recipe
|
||||
push!(kw_list, kw)
|
||||
return
|
||||
end
|
||||
try
|
||||
st = kw[:seriestype]
|
||||
st = kw[:seriestype] = get(_typeAliases, st, st)
|
||||
datalist = RecipesBase.apply_recipe(kw, Val{st}, plt)
|
||||
warn_on_recipe_aliases!(datalist, :plot, st)
|
||||
for data in datalist
|
||||
preprocessArgs!(data.plotattributes)
|
||||
if data.plotattributes[:seriestype] == st
|
||||
error("Plot recipe $st returned the same seriestype: $(data.plotattributes)")
|
||||
end
|
||||
push!(still_to_process, data.plotattributes)
|
||||
end
|
||||
catch err
|
||||
if isa(err, MethodError)
|
||||
push!(kw_list, kw)
|
||||
else
|
||||
rethrow()
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# setup plot and subplot
|
||||
@@ -405,49 +324,3 @@ function _add_the_series(plt, sp, plotattributes)
|
||||
push!(sp.series_list, series)
|
||||
_series_added(plt, series)
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
# this method recursively applies series recipes when the seriestype is not supported
|
||||
# natively by the backend
|
||||
function _process_seriesrecipe(plt::Plot, plotattributes::AKW)
|
||||
#println("process $(typeof(plotattributes))")
|
||||
# replace seriestype aliases
|
||||
st = Symbol(plotattributes[:seriestype])
|
||||
st = plotattributes[:seriestype] = get(_typeAliases, st, st)
|
||||
|
||||
# shapes shouldn't have fillrange set
|
||||
if plotattributes[:seriestype] == :shape
|
||||
plotattributes[:fillrange] = nothing
|
||||
end
|
||||
|
||||
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
|
||||
if is_seriestype_supported(st)
|
||||
sp = _prepare_subplot(plt, plotattributes)
|
||||
_prepare_annotations(sp, plotattributes)
|
||||
_expand_subplot_extrema(sp, plotattributes, st)
|
||||
_update_series_attributes!(plotattributes, plt, sp)
|
||||
_add_the_series(plt, sp, plotattributes)
|
||||
|
||||
else
|
||||
# get a sub list of series for this seriestype
|
||||
x, y, z = plotattributes[:x], plotattributes[:y], plotattributes[:z]
|
||||
datalist = RecipesBase.apply_recipe(plotattributes, Val{st}, x, y, z)
|
||||
warn_on_recipe_aliases!(datalist, :series, st)
|
||||
|
||||
# assuming there was no error, recursively apply the series recipes
|
||||
for data in datalist
|
||||
if isa(data, RecipeData)
|
||||
preprocessArgs!(data.plotattributes)
|
||||
if data.plotattributes[:seriestype] == st
|
||||
error("The seriestype didn't change in series recipe $st. This will cause a StackOverflow.")
|
||||
end
|
||||
_process_seriesrecipe(plt, data.plotattributes)
|
||||
else
|
||||
@warn("Unhandled recipe: $(data)")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
nothing
|
||||
end
|
||||
|
||||
+20
-58
@@ -54,7 +54,7 @@ function plot(args...; kw...)
|
||||
# create an empty Plot then process
|
||||
plt = Plot()
|
||||
# plt.user_attr = plotattributes
|
||||
_plot!(plt, plotattributes, args)
|
||||
recipe_pipeline!(plt, plotattributes, args, type_aliases=_typeAliases)
|
||||
end
|
||||
|
||||
# build a new plot from existing plots
|
||||
@@ -155,7 +155,7 @@ function plot!(plt::Plot, args...; kw...)
|
||||
plotattributes = KW(kw)
|
||||
preprocessArgs!(plotattributes)
|
||||
# merge!(plt.user_attr, plotattributes)
|
||||
_plot!(plt, plotattributes, args)
|
||||
recipe_pipeline!(plt, plotattributes, args, type_aliases=_typeAliases)
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
@@ -163,78 +163,41 @@ end
|
||||
# this is the core plotting function. recursively apply recipes to build
|
||||
# a list of series KW dicts.
|
||||
# note: at entry, we only have those preprocessed args which were passed in... no default values yet
|
||||
function _plot!(plt::Plot, plotattributes::AKW, args::Tuple)
|
||||
|
||||
## here we implement the recipe interface
|
||||
function _recipe_init!(plt::Plot, plotattributes::AKW, args::Tuple)
|
||||
plotattributes[:plot_object] = plt
|
||||
|
||||
if !isempty(args) && !isdefined(Main, :StatsPlots) &&
|
||||
first(split(string(typeof(args[1])), ".")) == "DataFrames"
|
||||
@warn("You're trying to plot a DataFrame, but this functionality is provided by StatsPlots")
|
||||
end
|
||||
end
|
||||
|
||||
# --------------------------------
|
||||
# "USER RECIPES"
|
||||
# --------------------------------
|
||||
|
||||
kw_list = _process_userrecipes(plt, plotattributes, args)
|
||||
|
||||
# @info(1)
|
||||
# map(DD, kw_list)
|
||||
|
||||
|
||||
# --------------------------------
|
||||
# "PLOT RECIPES"
|
||||
# --------------------------------
|
||||
|
||||
# "plot recipe", which acts like a series type, and is processed before
|
||||
# the plot layout is created, which allows for setting layouts and other plot-wide attributes.
|
||||
# we get inputs which have been fully processed by "user recipes" and "type recipes",
|
||||
# so we can expect standard vectors, surfaces, etc. No defaults have been set yet.
|
||||
still_to_process = kw_list
|
||||
kw_list = KW[]
|
||||
while !isempty(still_to_process)
|
||||
next_kw = popfirst!(still_to_process)
|
||||
_process_plotrecipe(plt, next_kw, kw_list, still_to_process)
|
||||
end
|
||||
|
||||
# @info(2)
|
||||
# map(DD, kw_list)
|
||||
|
||||
function _recipe_after_plot!(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
|
||||
# --------------------------------
|
||||
# Plot/Subplot/Layout setup
|
||||
# --------------------------------
|
||||
_plot_setup(plt, plotattributes, kw_list)
|
||||
_subplot_setup(plt, plotattributes, kw_list)
|
||||
end
|
||||
|
||||
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!!
|
||||
# !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
|
||||
function _recipe_after_user!(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
|
||||
# don't allow something else to handle it
|
||||
plotattributes[:smooth] = false
|
||||
end
|
||||
|
||||
# --------------------------------
|
||||
# "SERIES RECIPES"
|
||||
# --------------------------------
|
||||
function _recipe_before_series!(plt::Plot, kw, kw_list)
|
||||
sp::Subplot = kw[:subplot]
|
||||
|
||||
# @info(3)
|
||||
# map(DD, kw_list)
|
||||
# in series attributes given as vector with one element per series,
|
||||
# select the value for current series
|
||||
_slice_series_args!(kw, plt, sp, series_idx(kw_list,kw))
|
||||
|
||||
for kw in kw_list
|
||||
sp::Subplot = kw[:subplot]
|
||||
|
||||
# in series attributes given as vector with one element per series,
|
||||
# select the value for current series
|
||||
_slice_series_args!(kw, plt, sp, series_idx(kw_list,kw))
|
||||
|
||||
|
||||
series_attr = Attr(kw, _series_defaults)
|
||||
# now we have a fully specified series, with colors chosen. we must recursively handle
|
||||
# series recipes, which dispatch on seriestype. If a backend does not natively support a seriestype,
|
||||
# we check for a recipe that will convert that series type into one made up of lower-level components.
|
||||
# For example, a histogram is just a bar plot with binned data, a bar plot is really a filled step plot,
|
||||
# and a step plot is really just a path. So any backend that supports drawing a path will implicitly
|
||||
# be able to support step, bar, and histogram plots (and any recipes that use those components).
|
||||
_process_seriesrecipe(plt, series_attr)
|
||||
end
|
||||
|
||||
# --------------------------------
|
||||
series_attr = Attr(kw, _series_defaults)
|
||||
end
|
||||
|
||||
function _recipe_finish!(plt::Plot, plotattributes::AKW, args::Tuple)
|
||||
current(plt)
|
||||
|
||||
# do we want to force display?
|
||||
@@ -246,7 +209,6 @@ function _plot!(plt::Plot, plotattributes::AKW, args::Tuple)
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
# we're getting ready to display/output. prep for layout calcs, then update
|
||||
# the plot object after
|
||||
function prepare_output(plt::Plot)
|
||||
|
||||
+210
-541
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1261,8 +1261,8 @@ end
|
||||
yflip := true
|
||||
aspect_ratio := 1
|
||||
rs, cs, zs = findnz(z.surf)
|
||||
xlims := ignorenan_extrema(cs)
|
||||
ylims := ignorenan_extrema(rs)
|
||||
xlim := ignorenan_extrema(cs)
|
||||
ylim := ignorenan_extrema(rs)
|
||||
if plotattributes[:markershape] == :none
|
||||
markershape := :circle
|
||||
end
|
||||
|
||||
+181
-313
@@ -1,91 +1,23 @@
|
||||
|
||||
|
||||
# create a new "build_series_args" which converts all inputs into xs = Any[xitems], ys = Any[yitems].
|
||||
# Special handling for: no args, xmin/xmax, parametric, dataframes
|
||||
# Then once inputs have been converted, build the series args, map functions, etc.
|
||||
# This should cut down on boilerplate code and allow more focused dispatch on type
|
||||
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now
|
||||
|
||||
const FuncOrFuncs{F} = Union{F, Vector{F}, Matrix{F}}
|
||||
const MaybeNumber = Union{Number, Missing}
|
||||
const MaybeString = Union{AbstractString, Missing}
|
||||
const DataPoint = Union{MaybeNumber, MaybeString}
|
||||
|
||||
prepareSeriesData(x) = error("Cannot convert $(typeof(x)) to series data for plotting")
|
||||
prepareSeriesData(::Nothing) = nothing
|
||||
prepareSeriesData(t::Tuple{T, T}) where {T<:Number} = t
|
||||
prepareSeriesData(f::Function) = f
|
||||
prepareSeriesData(ar::AbstractRange{<:Number}) = ar
|
||||
function prepareSeriesData(a::AbstractArray{<:MaybeNumber})
|
||||
f = isimmutable(a) ? replace : replace!
|
||||
a = f(x -> ismissing(x) || isinf(x) ? NaN : x, map(float, a))
|
||||
end
|
||||
prepareSeriesData(a::AbstractArray{<:Missing}) = fill(NaN, axes(a))
|
||||
prepareSeriesData(a::AbstractArray{<:MaybeString}) = replace(x -> ismissing(x) ? "" : x, a)
|
||||
prepareSeriesData(s::Surface{<:AMat{<:MaybeNumber}}) = Surface(prepareSeriesData(s.surf))
|
||||
prepareSeriesData(s::Surface) = s # non-numeric Surface, such as an image
|
||||
prepareSeriesData(v::Volume) = Volume(prepareSeriesData(v.v), v.x_extents, v.y_extents, v.z_extents)
|
||||
|
||||
# default: assume x represents a single series
|
||||
series_vector(x, plotattributes) = [prepareSeriesData(x)]
|
||||
|
||||
# fixed number of blank series
|
||||
series_vector(n::Integer, plotattributes) = [zeros(0) for i in 1:n]
|
||||
|
||||
# vector of data points is a single series
|
||||
series_vector(v::AVec{<:DataPoint}, plotattributes) = [prepareSeriesData(v)]
|
||||
|
||||
# list of things (maybe other vectors, functions, or something else)
|
||||
function series_vector(v::AVec, plotattributes)
|
||||
if all(x -> x isa MaybeNumber, v)
|
||||
series_vector(Vector{MaybeNumber}(v), plotattributes)
|
||||
elseif all(x -> x isa MaybeString, v)
|
||||
series_vector(Vector{MaybeString}(v), plotattributes)
|
||||
else
|
||||
vcat((series_vector(vi, plotattributes) for vi in v)...)
|
||||
end
|
||||
end
|
||||
|
||||
# Matrix is split into columns
|
||||
function series_vector(v::AMat{<:DataPoint}, plotattributes)
|
||||
if all3D(plotattributes)
|
||||
[prepareSeriesData(Surface(v))]
|
||||
else
|
||||
[prepareSeriesData(v[:, i]) for i in axes(v, 2)]
|
||||
end
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Fillranges & ribbons
|
||||
|
||||
|
||||
process_fillrange(range::Number, plotattributes) = [range]
|
||||
process_fillrange(range, plotattributes) = series_vector(range, plotattributes)
|
||||
|
||||
process_ribbon(ribbon::Number, plotattributes) = [ribbon]
|
||||
process_ribbon(ribbon, plotattributes) = series_vector(ribbon, plotattributes)
|
||||
# ribbon as a tuple: (lower_ribbons, upper_ribbons)
|
||||
process_ribbon(ribbon::Tuple{S, T}, plotattributes) where {S, T} = collect(zip(
|
||||
series_vector(ribbon[1], plotattributes),
|
||||
series_vector(ribbon[2], plotattributes),
|
||||
))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
compute_x(x::Nothing, y::Nothing, z) = axes(z,1)
|
||||
compute_x(x::Nothing, y, z) = axes(y,1)
|
||||
compute_x(x::Function, y, z) = map(x, y)
|
||||
compute_x(x, y, z) = x
|
||||
# TODO: can we avoid the copy here? one error that crops up is that mapping functions over the same array
|
||||
# result in that array being shared. push!, etc will add too many items to that array
|
||||
|
||||
compute_y(x::Nothing, y::Nothing, z) = axes(z,2)
|
||||
compute_y(x, y::Function, z) = map(y, x)
|
||||
compute_y(x, y, z) = y
|
||||
compute_x(x::Nothing, y::Nothing, z) = axes(z,1)
|
||||
compute_x(x::Nothing, y, z) = axes(y,1)
|
||||
compute_x(x::Function, y, z) = map(x, y)
|
||||
compute_x(x, y, z) = copy(x)
|
||||
|
||||
compute_z(x, y, z::Function) = map(z, x, y)
|
||||
compute_z(x, y, z::AbstractMatrix) = Surface(z)
|
||||
compute_z(x, y, z::Nothing) = nothing
|
||||
compute_z(x, y, z) = z
|
||||
# compute_y(x::Void, y::Function, z) = error()
|
||||
compute_y(x::Nothing, y::Nothing, z) = axes(z,2)
|
||||
compute_y(x, y::Function, z) = map(y, x)
|
||||
compute_y(x, y, z) = copy(y)
|
||||
|
||||
compute_z(x, y, z::Function) = map(z, x, y)
|
||||
compute_z(x, y, z::AbstractMatrix) = Surface(z)
|
||||
compute_z(x, y, z::Nothing) = nothing
|
||||
compute_z(x, y, z) = copy(z)
|
||||
|
||||
nobigs(v::AVec{BigFloat}) = map(Float64, v)
|
||||
nobigs(v::AVec{BigInt}) = map(Int64, v)
|
||||
@@ -108,174 +40,51 @@ compute_xyz(x::Nothing, y::Nothing, z::Nothing) = error("x/y/z are all no
|
||||
|
||||
# we are going to build recipes to do the processing and splitting of the args
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# The catch-all SliceIt recipe
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# ensure we dispatch to the slicer
|
||||
struct SliceIt end
|
||||
|
||||
# The `SliceIt` recipe finishes user and type recipe processing.
|
||||
# It splits processed data into individual series data, stores in copied `plotattributes`
|
||||
# for each series and returns no arguments.
|
||||
@recipe function f(::Type{SliceIt}, x, y, z)
|
||||
|
||||
# handle data with formatting attached
|
||||
if typeof(x) <: Formatted
|
||||
xformatter := x.formatter
|
||||
x = x.data
|
||||
end
|
||||
if typeof(y) <: Formatted
|
||||
yformatter := y.formatter
|
||||
y = y.data
|
||||
end
|
||||
if typeof(z) <: Formatted
|
||||
zformatter := z.formatter
|
||||
z = z.data
|
||||
end
|
||||
|
||||
xs = series_vector(x, plotattributes)
|
||||
ys = series_vector(y, plotattributes)
|
||||
zs = series_vector(z, plotattributes)
|
||||
|
||||
fr = pop!(plotattributes, :fillrange, nothing)
|
||||
fillranges = process_fillrange(fr, plotattributes)
|
||||
mf = length(fillranges)
|
||||
|
||||
rib = pop!(plotattributes, :ribbon, nothing)
|
||||
ribbons = process_ribbon(rib, plotattributes)
|
||||
mr = length(ribbons)
|
||||
|
||||
mx = length(xs)
|
||||
my = length(ys)
|
||||
mz = length(zs)
|
||||
if mx > 0 && my > 0 && mz > 0
|
||||
for i in 1:max(mx, my, mz)
|
||||
# add a new series
|
||||
di = copy(plotattributes)
|
||||
xi, yi, zi = xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]
|
||||
di[:x], di[:y], di[:z] = compute_xyz(xi, yi, zi)
|
||||
|
||||
# handle fillrange
|
||||
fr = fillranges[mod1(i,mf)]
|
||||
di[:fillrange] = isa(fr, Function) ? map(fr, di[:x]) : fr
|
||||
|
||||
# handle ribbons
|
||||
rib = ribbons[mod1(i,mr)]
|
||||
di[:ribbon] = isa(rib, Function) ? map(rib, di[:x]) : rib
|
||||
|
||||
push!(series_list, RecipeData(di, ()))
|
||||
end
|
||||
end
|
||||
nothing # don't add a series for the main block
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Apply type recipes
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# this is the default "type recipe"... just pass the object through
|
||||
@recipe f(::Type{T}, v::T) where T = v
|
||||
|
||||
# this should catch unhandled "series recipes" and error with a nice message
|
||||
@recipe f(::Type{V}, x, y, z) where {V<:Val} = error("The backend must not support the series type $V, and there isn't a series recipe defined.")
|
||||
|
||||
function _apply_type_recipe(plotattributes, v, letter)
|
||||
_preprocess_axis_args!(plotattributes, letter)
|
||||
rdvec = RecipesBase.apply_recipe(plotattributes, typeof(v), v)
|
||||
warn_on_recipe_aliases!(plotattributes, :type, typeof(v))
|
||||
_postprocess_axis_args!(plotattributes, letter)
|
||||
return rdvec[1].args[1]
|
||||
end
|
||||
_apply_type_recipe(plotattributes, v) = RecipesBase.apply_recipe(plotattributes, typeof(v), v)[1].args[1]
|
||||
|
||||
# Handle type recipes when the recipe is defined on the elements.
|
||||
# This sort of recipe should return a pair of functions... one to convert to number,
|
||||
# and one to format tick values.
|
||||
function _apply_type_recipe(plotattributes, v::AbstractArray, letter)
|
||||
_preprocess_axis_args!(plotattributes, letter)
|
||||
# First we try to apply an array type recipe.
|
||||
w = RecipesBase.apply_recipe(plotattributes, typeof(v), v)[1].args[1]
|
||||
warn_on_recipe_aliases!(plotattributes, :type, typeof(v))
|
||||
# If the type did not change try it element-wise
|
||||
if typeof(v) == typeof(w)
|
||||
isempty(skipmissing(v)) && return Float64[]
|
||||
x = first(skipmissing(v))
|
||||
args = RecipesBase.apply_recipe(plotattributes, typeof(x), x)[1].args
|
||||
warn_on_recipe_aliases!(plotattributes, :type, typeof(x))
|
||||
_postprocess_axis_args!(plotattributes, letter)
|
||||
if length(args) == 2 && all(arg -> arg isa Function, args)
|
||||
numfunc, formatter = args
|
||||
return Formatted(map(numfunc, v), formatter)
|
||||
else
|
||||
return v
|
||||
end
|
||||
end
|
||||
_postprocess_axis_args!(plotattributes, letter)
|
||||
return w
|
||||
end
|
||||
|
||||
# special handling for Surface... need to properly unwrap and re-wrap
|
||||
_apply_type_recipe(plotattributes, v::Surface{<:AMat{<:DataPoint}}) = v
|
||||
function _apply_type_recipe(plotattributes, v::Surface)
|
||||
ret = _apply_type_recipe(plotattributes, v.surf)
|
||||
if typeof(ret) <: Formatted
|
||||
Formatted(Surface(ret.data), ret.formatter)
|
||||
function _apply_type_recipe(plotattributes, v::AbstractArray)
|
||||
isempty(skipmissing(v)) && return Float64[]
|
||||
x = first(skipmissing(v))
|
||||
args = RecipesBase.apply_recipe(plotattributes, typeof(x), x)[1].args
|
||||
if length(args) == 2 && typeof(args[1]) <: Function && typeof(args[2]) <: Function
|
||||
numfunc, formatter = args
|
||||
Formatted(map(numfunc, v), formatter)
|
||||
else
|
||||
Surface(ret.data)
|
||||
v
|
||||
end
|
||||
end
|
||||
|
||||
# don't do anything vectors of datapoints and for nothing
|
||||
_apply_type_recipe(plotattributes, v::Nothing, letter) = v
|
||||
_apply_type_recipe(plotattributes, v::AbstractArray{<:MaybeString}, letter) = v
|
||||
_apply_type_recipe(plotattributes, v::AbstractArray{<:Union{Real, Nothing}}, letter) = v
|
||||
# # special handling for Surface... need to properly unwrap and re-wrap
|
||||
# function _apply_type_recipe(plotattributes, v::Surface)
|
||||
# T = eltype(v.surf)
|
||||
# @show T
|
||||
# if T <: Integer || T <: AbstractFloat
|
||||
# v
|
||||
# else
|
||||
# ret = _apply_type_recipe(plotattributes, v.surf)
|
||||
# if typeof(ret) <: Formatted
|
||||
# Formatted(Surface(ret.data), ret.formatter)
|
||||
# else
|
||||
# v
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# axis args before type recipes should still be mapped to all axes
|
||||
function _preprocess_axis_args!(plotattributes)
|
||||
for (k, v) in plotattributes
|
||||
if is_axis_attr_noletter(k)
|
||||
pop!(plotattributes, k)
|
||||
for l in (:x, :y, :z)
|
||||
lk = Symbol(l, k)
|
||||
haskey(plotattributes, lk) || (plotattributes[lk] = v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function _preprocess_axis_args!(plotattributes, letter)
|
||||
plotattributes[:letter] = letter
|
||||
_preprocess_axis_args!(plotattributes)
|
||||
end
|
||||
|
||||
# axis args in type recipes should only be applied to the current axis
|
||||
function _postprocess_axis_args!(plotattributes, letter)
|
||||
pop!(plotattributes, :letter)
|
||||
if letter in (:x, :y, :z)
|
||||
for (k, v) in plotattributes
|
||||
if is_axis_attr_noletter(k)
|
||||
pop!(plotattributes, k)
|
||||
lk = Symbol(letter, k)
|
||||
haskey(plotattributes, lk) || (plotattributes[lk] = v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Fallback user recipes calling type recipes
|
||||
# --------------------------------------------------------------------
|
||||
# don't do anything for ints or floats
|
||||
_apply_type_recipe(plotattributes, v::AbstractArray{T}) where {T<:Union{Integer,AbstractFloat}} = v
|
||||
|
||||
# handle "type recipes" by converting inputs, and then either re-calling or slicing
|
||||
@recipe function f(x, y, z)
|
||||
wrap_surfaces!(plotattributes, x, y, z)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(plotattributes, x, :x)
|
||||
newx = _apply_type_recipe(plotattributes, x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(plotattributes, y, :y)
|
||||
newy = _apply_type_recipe(plotattributes, y)
|
||||
y === newy || (did_replace = true)
|
||||
newz = _apply_type_recipe(plotattributes, z, :z)
|
||||
newz = _apply_type_recipe(plotattributes, z)
|
||||
z === newz || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy, newz
|
||||
@@ -284,11 +93,10 @@ end
|
||||
end
|
||||
end
|
||||
@recipe function f(x, y)
|
||||
wrap_surfaces!(plotattributes, x, y)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(plotattributes, x, :x)
|
||||
newx = _apply_type_recipe(plotattributes, x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(plotattributes, y, :y)
|
||||
newy = _apply_type_recipe(plotattributes, y)
|
||||
y === newy || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy
|
||||
@@ -297,8 +105,7 @@ end
|
||||
end
|
||||
end
|
||||
@recipe function f(y)
|
||||
wrap_surfaces!(plotattributes, y)
|
||||
newy = _apply_type_recipe(plotattributes, y, :y)
|
||||
newy = _apply_type_recipe(plotattributes, y)
|
||||
if y !== newy
|
||||
newy
|
||||
else
|
||||
@@ -311,7 +118,7 @@ end
|
||||
@recipe function f(v1, v2, v3, v4, vrest...)
|
||||
did_replace = false
|
||||
newargs = map(v -> begin
|
||||
newv = _apply_type_recipe(plotattributes, v, :unknown)
|
||||
newv = _apply_type_recipe(plotattributes, v)
|
||||
if newv !== v
|
||||
did_replace = true
|
||||
end
|
||||
@@ -324,14 +131,12 @@ end
|
||||
end
|
||||
|
||||
|
||||
# # --------------------------------------------------------------------
|
||||
# # 1 argument
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
# helper function to ensure relevant attributes are wrapped by Surface
|
||||
function wrap_surfaces!(plotattributes, args...) end
|
||||
wrap_surfaces!(plotattributes, x::AMat, y::AMat, z::AMat) = wrap_surfaces!(plotattributes)
|
||||
wrap_surfaces!(plotattributes, x::AVec, y::AVec, z::AMat) = wrap_surfaces!(plotattributes)
|
||||
function wrap_surfaces!(plotattributes, x::AVec, y::AVec, z::Surface)
|
||||
wrap_surfaces!(plotattributes)
|
||||
end
|
||||
function wrap_surfaces!(plotattributes)
|
||||
function wrap_surfaces(plotattributes::AKW)
|
||||
if haskey(plotattributes, :fill_z)
|
||||
v = plotattributes[:fill_z]
|
||||
if !isa(v, Surface)
|
||||
@@ -340,57 +145,41 @@ function wrap_surfaces!(plotattributes)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 1 argument
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@recipe f(n::Integer) = is3d(get(plotattributes,:seriestype,:path)) ? (SliceIt, n, n, n) : (SliceIt, n, n, nothing)
|
||||
|
||||
all3D(plotattributes) = trueOrAllTrue(
|
||||
st -> st in (
|
||||
:contour,
|
||||
:contourf,
|
||||
:heatmap,
|
||||
:surface,
|
||||
:wireframe,
|
||||
:contour3d,
|
||||
:image,
|
||||
:plots_heatmap,
|
||||
),
|
||||
get(plotattributes, :seriestype, :none),
|
||||
)
|
||||
|
||||
# return a surface if this is a 3d plot, otherwise let it be sliced up
|
||||
@recipe function f(mat::AMat)
|
||||
@recipe function f(mat::AMat{T}) where T<:Union{Integer,AbstractFloat,Missing}
|
||||
if all3D(plotattributes)
|
||||
n, m = axes(mat)
|
||||
m, n, Surface(mat)
|
||||
n,m = axes(mat)
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, m, n, Surface(mat)
|
||||
else
|
||||
nothing, mat, nothing
|
||||
SliceIt, nothing, mat, nothing
|
||||
end
|
||||
end
|
||||
|
||||
# if a matrix is wrapped by Formatted, do similar logic, but wrap data with Surface
|
||||
@recipe function f(fmt::Formatted{<:AMat})
|
||||
@recipe function f(fmt::Formatted{T}) where T<:AbstractMatrix
|
||||
if all3D(plotattributes)
|
||||
mat = fmt.data
|
||||
n, m = axes(mat)
|
||||
m, n, Formatted(Surface(mat), fmt.formatter)
|
||||
n,m = axes(mat)
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, m, n, Formatted(Surface(mat), fmt.formatter)
|
||||
else
|
||||
nothing, fmt, nothing
|
||||
SliceIt, nothing, fmt, nothing
|
||||
end
|
||||
end
|
||||
|
||||
# assume this is a Volume, so construct one
|
||||
@recipe function f(vol::AbstractArray{<:MaybeNumber, 3}, args...)
|
||||
@recipe function f(vol::AbstractArray{T,3}, args...) where T<:Union{Number,Missing}
|
||||
seriestype := :volume
|
||||
SliceIt, nothing, Volume(vol, args...), nothing
|
||||
end
|
||||
|
||||
|
||||
# images - grays
|
||||
function clamp_greys!(mat::AMat{<:Gray})
|
||||
# # images - grays
|
||||
function clamp_greys!(mat::AMat{T}) where T<:Gray
|
||||
for i in eachindex(mat)
|
||||
mat[i].val < 0 && (mat[i] = Gray(0))
|
||||
mat[i].val > 1 && (mat[i] = Gray(1))
|
||||
@@ -398,7 +187,7 @@ function clamp_greys!(mat::AMat{<:Gray})
|
||||
mat
|
||||
end
|
||||
|
||||
@recipe function f(mat::AMat{<:Gray})
|
||||
@recipe function f(mat::AMat{T}) where T<:Gray
|
||||
n, m = axes(mat)
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
@@ -413,7 +202,8 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
# images - colors
|
||||
# # images - colors
|
||||
|
||||
@recipe function f(mat::AMat{T}) where T<:Colorant
|
||||
n, m = axes(mat)
|
||||
|
||||
@@ -431,7 +221,8 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
# plotting arbitrary shapes/polygons
|
||||
#
|
||||
# # plotting arbitrary shapes/polygons
|
||||
|
||||
@recipe function f(shape::Shape)
|
||||
seriestype --> :shape
|
||||
@@ -465,21 +256,40 @@ end
|
||||
axis_limits(plt[1], :x)
|
||||
catch
|
||||
xinv = invscalefunc(get(plotattributes, :xscale, :identity))
|
||||
xm = PlotUtils.tryrange(f, xinv.([-5,-1,0,0.01]))
|
||||
xm, PlotUtils.tryrange(f, filter(x->x>xm, xinv.([5,1,0.99, 0, -0.01])))
|
||||
xm = tryrange(f, xinv.([-5,-1,0,0.01]))
|
||||
xm, tryrange(f, filter(x->x>xm, xinv.([5,1,0.99, 0, -0.01])))
|
||||
end
|
||||
end
|
||||
|
||||
f, xmin, xmax
|
||||
end
|
||||
|
||||
# try some intervals over which the function may be defined
|
||||
function tryrange(F::AbstractArray, vec)
|
||||
rets = [tryrange(f, vec) for f in F] # get the preferred for each
|
||||
maxind = maximum(indexin(rets, vec)) # get the last attempt that succeeded (most likely to fit all)
|
||||
rets .= [tryrange(f, vec[maxind:maxind]) for f in F] # ensure that all functions compute there
|
||||
rets[1]
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 2 arguments
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# if functions come first, just swap the order (not to be confused with parametric
|
||||
# functions... as there would be more than one function passed in)
|
||||
function tryrange(F, vec)
|
||||
for v in vec
|
||||
try
|
||||
tmp = F(v)
|
||||
return v
|
||||
catch
|
||||
end
|
||||
end
|
||||
error("$F is not a Function, or is not defined at any of the values $vec")
|
||||
end
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 2 arguments
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
# # if functions come first, just swap the order (not to be confused with parametric functions...
|
||||
# # as there would be more than one function passed in)
|
||||
|
||||
@recipe function f(f::FuncOrFuncs{F}, x) where F<:Function
|
||||
F2 = typeof(x)
|
||||
@@ -487,25 +297,58 @@ end
|
||||
x, f
|
||||
end
|
||||
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # 3 arguments
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
# # 3d line or scatter
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 3 arguments
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# surface-like... function
|
||||
@recipe function f(x::AVec, y::AVec, zf::Function)
|
||||
x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
|
||||
@recipe function f(x::AVec, y::AVec, z::AVec)
|
||||
# st = get(plotattributes, :seriestype, :none)
|
||||
# if st == :scatter
|
||||
# plotattributes[:seriestype] = :scatter3d
|
||||
# elseif !is3d(st)
|
||||
# plotattributes[:seriestype] = :path3d
|
||||
# end
|
||||
SliceIt, x, y, z
|
||||
end
|
||||
|
||||
# surface-like... matrix grid
|
||||
@recipe function f(x::AMat, y::AMat, z::AMat)
|
||||
# st = get(plotattributes, :seriestype, :none)
|
||||
# if size(x) == size(y) == size(z)
|
||||
# if !is3d(st)
|
||||
# seriestype := :path3d
|
||||
# end
|
||||
# end
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, x, y, z
|
||||
end
|
||||
|
||||
#
|
||||
# # surface-like... function
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, zf::Function)
|
||||
# x = X <: Number ? sort(x) : x
|
||||
# y = Y <: Number ? sort(y) : y
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
|
||||
end
|
||||
|
||||
#
|
||||
# # surface-like... matrix grid
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, z::AMat)
|
||||
if !like_surface(get(plotattributes, :seriestype, :none))
|
||||
plotattributes[:seriestype] = :contour
|
||||
end
|
||||
x, y, Surface(z)
|
||||
wrap_surfaces(plotattributes)
|
||||
SliceIt, x, y, Surface(z)
|
||||
end
|
||||
|
||||
# images - grays
|
||||
# # images - grays
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Gray
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
@@ -520,7 +363,8 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
# images - colors
|
||||
# # images - colors
|
||||
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Colorant
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
@@ -535,19 +379,27 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Parametric functions
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Parametric functions
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# special handling... xmin/xmax with parametric function(s)
|
||||
#
|
||||
# # special handling... xmin/xmax with parametric function(s)
|
||||
@recipe function f(f::Function, xmin::Number, xmax::Number)
|
||||
xscale, yscale = [get(plotattributes, sym, :identity) for sym=(:xscale,:yscale)]
|
||||
_scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
|
||||
end
|
||||
@recipe function f(fs::AbstractArray{F}, xmin::Number, xmax::Number) where F<:Function
|
||||
xscale, yscale = [get(plotattributes, sym, :identity) for sym=(:xscale,:yscale)]
|
||||
unzip(_scaled_adapted_grid.(fs, xscale, yscale, xmin, xmax))
|
||||
xs = Array{Any}(undef, length(fs))
|
||||
ys = Array{Any}(undef, length(fs))
|
||||
for (i, (x, y)) in enumerate(_scaled_adapted_grid(f, xscale, yscale, xmin, xmax) for f in fs)
|
||||
xs[i] = x
|
||||
ys[i] = y
|
||||
end
|
||||
xs, ys
|
||||
end
|
||||
@recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, u::AVec) where {F<:Function,G<:Function} = mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u)
|
||||
@recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, umin::Number, umax::Number, n = 200) where {F<:Function,G<:Function} = fx, fy, range(umin, stop = umax, length = n)
|
||||
@@ -558,7 +410,8 @@ function _scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
|
||||
xinv.(xs), yinv.(ys)
|
||||
end
|
||||
|
||||
# special handling... 3D parametric function(s)
|
||||
#
|
||||
# # special handling... 3D parametric function(s)
|
||||
@recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, u::AVec) where {F<:Function,G<:Function,H<:Function}
|
||||
mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u)
|
||||
end
|
||||
@@ -566,10 +419,12 @@ end
|
||||
fx, fy, fz, range(umin, stop = umax, length = numPoints)
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Lists of tuples and GeometryTypes.Points
|
||||
# --------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # Lists of tuples and GeometryTypes.Points
|
||||
# # --------------------------------------------------------------------
|
||||
#
|
||||
|
||||
@recipe f(v::AVec{<:Tuple}) = unzip(v)
|
||||
@recipe f(v::AVec{<:GeometryTypes.Point}) = unzip(v)
|
||||
@@ -579,10 +434,23 @@ end
|
||||
# Special case for 4-tuples in :ohlc series
|
||||
@recipe f(xyuv::AVec{<:Tuple{R1,R2,R3,R4}}) where {R1,R2,R3,R4} = get(plotattributes,:seriestype,:path)==:ohlc ? OHLC[OHLC(t...) for t in xyuv] : unzip(xyuv)
|
||||
|
||||
#
|
||||
# # --------------------------------------------------------------------
|
||||
# # handle grouping
|
||||
# # --------------------------------------------------------------------
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# handle grouping
|
||||
# --------------------------------------------------------------------
|
||||
# @recipe function f(groupby::GroupBy, args...)
|
||||
# for (i,glab) in enumerate(groupby.groupLabels)
|
||||
# # create a new series, with the label of the group, and an idxfilter (to be applied in slice_and_dice)
|
||||
# # TODO: use @series instead
|
||||
# @show i, glab, groupby.groupIds[i]
|
||||
# di = copy(plotattributes)
|
||||
# get!(di, :label, string(glab))
|
||||
# get!(di, :idxfilter, groupby.groupIds[i])
|
||||
# push!(series_list, RecipeData(di, args))
|
||||
# end
|
||||
# nothing
|
||||
# end
|
||||
|
||||
splittable_kw(key, val, lengthGroup) = false
|
||||
splittable_kw(key, val::AbstractArray, lengthGroup) = !(key in (:group, :color_palette)) && length(axes(val,1)) == lengthGroup
|
||||
|
||||
+2
-3
@@ -271,7 +271,6 @@ Draw a 3D surface plot.
|
||||
|
||||
# Example
|
||||
```julia-repl
|
||||
julia> using LinearAlgebra
|
||||
julia> x = y = range(-3, 3, length = 100)
|
||||
julia> surface(x, y, (x, y) -> sinc(norm([x, y])))
|
||||
```
|
||||
@@ -429,8 +428,8 @@ Add annotations to an existing plot.
|
||||
|
||||
# Arguments
|
||||
|
||||
- `anns`: An `AbstractVector` of tuples of the form `(x,y,text)`. The `text` object
|
||||
can be a `String` or `PlotText`.
|
||||
- `anns`: An `AbstractVector` of tuples of the form (x,y,text). The text object
|
||||
can be an String or PlotText
|
||||
|
||||
# Example
|
||||
```julia-repl
|
||||
|
||||
+3
-4
@@ -26,9 +26,8 @@ struct Attr <: AbstractDict{Symbol,Any}
|
||||
defaults::KW
|
||||
end
|
||||
|
||||
function Base.getindex(attr::Attr, k)
|
||||
return haskey(attr.explicit, k) ? attr.explicit[k] : attr.defaults[k]
|
||||
end
|
||||
Base.getindex(attr::Attr, k) = haskey(attr.explicit,k) ?
|
||||
attr.explicit[k] : attr.defaults[k]
|
||||
Base.haskey(attr::Attr, k) = haskey(attr.explicit,k) || haskey(attr.defaults,k)
|
||||
Base.get(attr::Attr, k, default) = haskey(attr, k) ? attr[k] : default
|
||||
function Base.get!(attr::Attr, k, default)
|
||||
@@ -42,7 +41,7 @@ end
|
||||
function Base.delete!(attr::Attr, k)
|
||||
haskey(attr.explicit, k) && delete!(attr.explicit, k)
|
||||
haskey(attr.defaults, k) && delete!(attr.defaults, k)
|
||||
end
|
||||
end
|
||||
Base.length(attr::Attr) = length(union(keys(attr.explicit), keys(attr.defaults)))
|
||||
function Base.iterate(attr::Attr)
|
||||
exp_keys = keys(attr.explicit)
|
||||
|
||||
+307
-165
@@ -1,4 +1,119 @@
|
||||
|
||||
calcMidpoints(edges::AbstractVector) = Float64[0.5 * (edges[i] + edges[i+1]) for i in 1:length(edges)-1]
|
||||
|
||||
"Make histogram-like bins of data"
|
||||
function binData(data, nbins)
|
||||
lo, hi = ignorenan_extrema(data)
|
||||
edges = collect(range(lo, stop=hi, length=nbins+1))
|
||||
midpoints = calcMidpoints(edges)
|
||||
buckets = Int[max(2, min(searchsortedfirst(edges, x), length(edges)))-1 for x in data]
|
||||
counts = zeros(Int, length(midpoints))
|
||||
for b in buckets
|
||||
counts[b] += 1
|
||||
end
|
||||
edges, midpoints, buckets, counts
|
||||
end
|
||||
|
||||
"""
|
||||
A hacky replacement for a histogram when the backend doesn't support histograms directly.
|
||||
Convert it into a bar chart with the appropriate x/y values.
|
||||
"""
|
||||
function histogramHack(; kw...)
|
||||
plotattributes = KW(kw)
|
||||
|
||||
# we assume that the y kwarg is set with the data to be binned, and nbins is also defined
|
||||
edges, midpoints, buckets, counts = binData(plotattributes[:y], plotattributes[:bins])
|
||||
plotattributes[:x] = midpoints
|
||||
plotattributes[:y] = float(counts)
|
||||
plotattributes[:seriestype] = :bar
|
||||
plotattributes[:fillrange] = plotattributes[:fillrange] === nothing ? 0.0 : plotattributes[:fillrange]
|
||||
plotattributes
|
||||
end
|
||||
|
||||
"""
|
||||
A hacky replacement for a bar graph when the backend doesn't support bars directly.
|
||||
Convert it into a line chart with fillrange set.
|
||||
"""
|
||||
function barHack(; kw...)
|
||||
plotattributes = KW(kw)
|
||||
midpoints = plotattributes[:x]
|
||||
heights = plotattributes[:y]
|
||||
fillrange = plotattributes[:fillrange] === nothing ? 0.0 : plotattributes[:fillrange]
|
||||
|
||||
# estimate the edges
|
||||
dists = diff(midpoints) * 0.5
|
||||
edges = zeros(length(midpoints)+1)
|
||||
for i in eachindex(edges)
|
||||
if i == 1
|
||||
edge = midpoints[1] - dists[1]
|
||||
elseif i == length(edges)
|
||||
edge = midpoints[i-1] + dists[i-2]
|
||||
else
|
||||
edge = midpoints[i-1] + dists[i-1]
|
||||
end
|
||||
edges[i] = edge
|
||||
end
|
||||
|
||||
x = Float64[]
|
||||
y = Float64[]
|
||||
for i in eachindex(heights)
|
||||
e1, e2 = edges[i:i+1]
|
||||
append!(x, [e1, e1, e2, e2])
|
||||
append!(y, [fillrange, heights[i], heights[i], fillrange])
|
||||
end
|
||||
|
||||
plotattributes[:x] = x
|
||||
plotattributes[:y] = y
|
||||
plotattributes[:seriestype] = :path
|
||||
plotattributes[:fillrange] = fillrange
|
||||
plotattributes
|
||||
end
|
||||
|
||||
|
||||
"""
|
||||
A hacky replacement for a sticks graph when the backend doesn't support sticks directly.
|
||||
Convert it into a line chart that traces the sticks, and a scatter that sets markers at the points.
|
||||
"""
|
||||
function sticksHack(; kw...)
|
||||
plotattributesLine = KW(kw)
|
||||
plotattributesScatter = copy(plotattributesLine)
|
||||
|
||||
# these are the line vertices
|
||||
x = Float64[]
|
||||
y = Float64[]
|
||||
fillrange = plotattributesLine[:fillrange] === nothing ? 0.0 : plotattributesLine[:fillrange]
|
||||
|
||||
# calculate the vertices
|
||||
yScatter = plotattributesScatter[:y]
|
||||
for (i,xi) in enumerate(plotattributesScatter[:x])
|
||||
yi = yScatter[i]
|
||||
for j in 1:3 push!(x, xi) end
|
||||
append!(y, [fillrange, yScatter[i], fillrange])
|
||||
end
|
||||
|
||||
# change the line args
|
||||
plotattributesLine[:x] = x
|
||||
plotattributesLine[:y] = y
|
||||
plotattributesLine[:seriestype] = :path
|
||||
plotattributesLine[:markershape] = :none
|
||||
plotattributesLine[:fillrange] = nothing
|
||||
|
||||
# change the scatter args
|
||||
plotattributesScatter[:seriestype] = :none
|
||||
|
||||
plotattributesLine, plotattributesScatter
|
||||
end
|
||||
|
||||
function regressionXY(x, y)
|
||||
# regress
|
||||
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
|
||||
|
||||
# make a line segment
|
||||
regx = [ignorenan_minimum(x), ignorenan_maximum(x)]
|
||||
regy = β * regx + α
|
||||
regx, regy
|
||||
end
|
||||
|
||||
function replace_image_with_heatmap(z::Array{T}) where T<:Colorant
|
||||
n, m = size(z)
|
||||
colors = ColorGradient(vec(z))
|
||||
@@ -147,10 +262,10 @@ mapFuncOrFuncs(f::Function, u::AVec) = map(f, u)
|
||||
mapFuncOrFuncs(fs::AVec{F}, u::AVec) where {F<:Function} = [map(f, u) for f in fs]
|
||||
|
||||
for i in 2:4
|
||||
@eval begin
|
||||
unzip(v::Union{AVec{<:Tuple{Vararg{T,$i} where T}},
|
||||
@eval begin
|
||||
unzip(v::Union{AVec{<:Tuple{Vararg{T,$i} where T}},
|
||||
AVec{<:GeometryTypes.Point{$i}}}) = $(Expr(:tuple, (:([t[$j] for t in v]) for j=1:i)...))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unzip(v::Union{AVec{<:GeometryTypes.Point{N}},
|
||||
@@ -160,13 +275,15 @@ unzip(v::Union{AVec{<:GeometryTypes.Point},
|
||||
|
||||
# given 2-element lims and a vector of data x, widen lims to account for the extrema of x
|
||||
function _expand_limits(lims, x)
|
||||
try
|
||||
e1, e2 = ignorenan_extrema(x)
|
||||
lims[1] = NaNMath.min(lims[1], e1)
|
||||
lims[2] = NaNMath.max(lims[2], e2)
|
||||
catch
|
||||
end
|
||||
nothing
|
||||
try
|
||||
e1, e2 = ignorenan_extrema(x)
|
||||
lims[1] = NaNMath.min(lims[1], e1)
|
||||
lims[2] = NaNMath.max(lims[2], e2)
|
||||
# catch err
|
||||
# @warn(err)
|
||||
catch
|
||||
end
|
||||
nothing
|
||||
end
|
||||
|
||||
expand_data(v, n::Integer) = [_cycle(v, i) for i=1:n]
|
||||
@@ -184,21 +301,21 @@ function addOrReplace(v::AbstractVector, t::DataType, args...; kw...)
|
||||
end
|
||||
|
||||
function replaceType(vec, val)
|
||||
filter!(x -> !isa(x, typeof(val)), vec)
|
||||
push!(vec, val)
|
||||
filter!(x -> !isa(x, typeof(val)), vec)
|
||||
push!(vec, val)
|
||||
end
|
||||
|
||||
function replaceAlias!(plotattributes::AKW, k::Symbol, aliases::Dict{Symbol,Symbol})
|
||||
if haskey(aliases, k)
|
||||
plotattributes[aliases[k]] = pop_kw!(plotattributes, k)
|
||||
end
|
||||
if haskey(aliases, k)
|
||||
plotattributes[aliases[k]] = pop_kw!(plotattributes, k)
|
||||
end
|
||||
end
|
||||
|
||||
function replaceAliases!(plotattributes::AKW, aliases::Dict{Symbol,Symbol})
|
||||
ks = collect(keys(plotattributes))
|
||||
for k in ks
|
||||
replaceAlias!(plotattributes, k, aliases)
|
||||
end
|
||||
ks = collect(keys(plotattributes))
|
||||
for k in ks
|
||||
replaceAlias!(plotattributes, k, aliases)
|
||||
end
|
||||
end
|
||||
|
||||
createSegments(z) = collect(repeat(reshape(z,1,:),2,1))[2:end]
|
||||
@@ -217,20 +334,20 @@ const _scale_base = Dict{Symbol, Real}(
|
||||
)
|
||||
|
||||
function _heatmap_edges(v::AVec, isedges::Bool = false)
|
||||
length(v) == 1 && return v[1] .+ [-0.5, 0.5]
|
||||
if isedges return v end
|
||||
# `isedges = true` means that v is a vector which already describes edges
|
||||
# and does not need to be extended.
|
||||
vmin, vmax = ignorenan_extrema(v)
|
||||
extra_min = (v[2] - v[1]) / 2
|
||||
extra_max = (v[end] - v[end - 1]) / 2
|
||||
vcat(vmin-extra_min, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra_max)
|
||||
length(v) == 1 && return v[1] .+ [-0.5, 0.5]
|
||||
if isedges return v end
|
||||
# `isedges = true` means that v is a vector which already describes edges
|
||||
# and does not need to be extended.
|
||||
vmin, vmax = ignorenan_extrema(v)
|
||||
extra_min = (v[2] - v[1]) / 2
|
||||
extra_max = (v[end] - v[end - 1]) / 2
|
||||
vcat(vmin-extra_min, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra_max)
|
||||
end
|
||||
|
||||
"create an (n+1) list of the outsides of heatmap rectangles"
|
||||
function heatmap_edges(v::AVec, scale::Symbol = :identity, isedges::Bool = false)
|
||||
f, invf = scalefunc(scale), invscalefunc(scale)
|
||||
map(invf, _heatmap_edges(map(f,v), isedges))
|
||||
f, invf = scalefunc(scale), invscalefunc(scale)
|
||||
map(invf, _heatmap_edges(map(f,v), isedges))
|
||||
end
|
||||
|
||||
function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size::Tuple{Int, Int})
|
||||
@@ -250,8 +367,8 @@ function heatmap_edges(x::AVec, xscale::Symbol, y::AVec, yscale::Symbol, z_size:
|
||||
end
|
||||
|
||||
function is_uniformly_spaced(v; tol=1e-6)
|
||||
dv = diff(v)
|
||||
maximum(dv) - minimum(dv) < tol * mean(abs.(dv))
|
||||
dv = diff(v)
|
||||
maximum(dv) - minimum(dv) < tol * mean(abs.(dv))
|
||||
end
|
||||
|
||||
function convert_to_polar(theta, r, r_extrema = ignorenan_extrema(r))
|
||||
@@ -263,11 +380,11 @@ function convert_to_polar(theta, r, r_extrema = ignorenan_extrema(r))
|
||||
end
|
||||
|
||||
function fakedata(sz...)
|
||||
y = zeros(sz...)
|
||||
for r in 2:size(y,1)
|
||||
y[r,:] = 0.95 * vec(y[r-1,:]) + randn(size(y,2))
|
||||
end
|
||||
y
|
||||
y = zeros(sz...)
|
||||
for r in 2:size(y,1)
|
||||
y[r,:] = 0.95 * vec(y[r-1,:]) + randn(size(y,2))
|
||||
end
|
||||
y
|
||||
end
|
||||
|
||||
isijulia() = :IJulia in nameof.(collect(values(Base.loaded_modules)))
|
||||
@@ -600,9 +717,6 @@ makekw(; kw...) = KW(kw)
|
||||
wraptuple(x::Tuple) = x
|
||||
wraptuple(x) = (x,)
|
||||
|
||||
trueOrAllTrue(f::Function, x::AbstractArray) = all(f, x)
|
||||
trueOrAllTrue(f::Function, x) = f(x)
|
||||
|
||||
allLineTypes(arg) = trueOrAllTrue(a -> get(_typeAliases, a, a) in _allTypes, arg)
|
||||
allStyles(arg) = trueOrAllTrue(a -> get(_styleAliases, a, a) in _allStyles, arg)
|
||||
allShapes(arg) = trueOrAllTrue(a -> is_marker_supported(get(_markerAliases, a, a)), arg) ||
|
||||
@@ -635,107 +749,119 @@ function with(f::Function, args...; kw...)
|
||||
newdefs[:legend] = false
|
||||
end
|
||||
|
||||
# dict to store old and new keyword args for anything that changes
|
||||
olddefs = KW()
|
||||
for k in keys(newdefs)
|
||||
olddefs[k] = default(k)
|
||||
# dict to store old and new keyword args for anything that changes
|
||||
olddefs = KW()
|
||||
for k in keys(newdefs)
|
||||
olddefs[k] = default(k)
|
||||
end
|
||||
|
||||
# save the backend
|
||||
if CURRENT_BACKEND.sym == :none
|
||||
_pick_default_backend()
|
||||
end
|
||||
oldbackend = CURRENT_BACKEND.sym
|
||||
|
||||
for arg in args
|
||||
|
||||
# change backend?
|
||||
if arg in backends()
|
||||
backend(arg)
|
||||
end
|
||||
|
||||
# save the backend
|
||||
if CURRENT_BACKEND.sym == :none
|
||||
_pick_default_backend()
|
||||
end
|
||||
oldbackend = CURRENT_BACKEND.sym
|
||||
# # TODO: generalize this strategy to allow args as much as possible
|
||||
# # as in: with(:gr, :scatter, :legend, :grid) do; ...; end
|
||||
# # TODO: can we generalize this enough to also do something similar in the plot commands??
|
||||
|
||||
for arg in args
|
||||
# k = :seriestype
|
||||
# if arg in _allTypes
|
||||
# olddefs[k] = default(k)
|
||||
# newdefs[k] = arg
|
||||
# elseif haskey(_typeAliases, arg)
|
||||
# olddefs[k] = default(k)
|
||||
# newdefs[k] = _typeAliases[arg]
|
||||
# end
|
||||
|
||||
# change backend?
|
||||
if arg in backends()
|
||||
backend(arg)
|
||||
end
|
||||
|
||||
# TODO: generalize this strategy to allow args as much as possible
|
||||
# as in: with(:gr, :scatter, :legend, :grid) do; ...; end
|
||||
# TODO: can we generalize this enough to also do something similar in the plot commands??
|
||||
|
||||
# k = :seriestype
|
||||
# if arg in _allTypes
|
||||
# olddefs[k] = default(k)
|
||||
# newdefs[k] = arg
|
||||
# elseif haskey(_typeAliases, arg)
|
||||
# olddefs[k] = default(k)
|
||||
# newdefs[k] = _typeAliases[arg]
|
||||
# end
|
||||
|
||||
k = :legend
|
||||
if arg in (k, :leg)
|
||||
olddefs[k] = default(k)
|
||||
newdefs[k] = true
|
||||
end
|
||||
|
||||
k = :grid
|
||||
if arg == k
|
||||
olddefs[k] = default(k)
|
||||
newdefs[k] = true
|
||||
end
|
||||
k = :legend
|
||||
if arg in (k, :leg)
|
||||
olddefs[k] = default(k)
|
||||
newdefs[k] = true
|
||||
end
|
||||
|
||||
# display(olddefs)
|
||||
# display(newdefs)
|
||||
|
||||
# now set all those defaults
|
||||
default(; newdefs...)
|
||||
|
||||
# call the function
|
||||
ret = f()
|
||||
|
||||
# put the defaults back
|
||||
default(; olddefs...)
|
||||
|
||||
# revert the backend
|
||||
if CURRENT_BACKEND.sym != oldbackend
|
||||
backend(oldbackend)
|
||||
k = :grid
|
||||
if arg == k
|
||||
olddefs[k] = default(k)
|
||||
newdefs[k] = true
|
||||
end
|
||||
end
|
||||
|
||||
# return the result of the function
|
||||
ret
|
||||
# display(olddefs)
|
||||
# display(newdefs)
|
||||
|
||||
# now set all those defaults
|
||||
default(; newdefs...)
|
||||
|
||||
# call the function
|
||||
ret = f()
|
||||
|
||||
# put the defaults back
|
||||
default(; olddefs...)
|
||||
|
||||
# revert the backend
|
||||
if CURRENT_BACKEND.sym != oldbackend
|
||||
backend(oldbackend)
|
||||
end
|
||||
|
||||
# return the result of the function
|
||||
ret
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
mutable struct DebugMode
|
||||
on::Bool
|
||||
on::Bool
|
||||
end
|
||||
const _debugMode = DebugMode(false)
|
||||
|
||||
function debugplots(on = true)
|
||||
_debugMode.on = on
|
||||
_debugMode.on = on
|
||||
end
|
||||
|
||||
debugshow(io, x) = show(io, x)
|
||||
debugshow(io, x::AbstractArray) = print(io, summary(x))
|
||||
|
||||
function dumpdict(io::IO, plotattributes::AKW, prefix = "", alwaysshow = false)
|
||||
_debugMode.on || alwaysshow || return
|
||||
println(io)
|
||||
if prefix != ""
|
||||
println(io, prefix, ":")
|
||||
end
|
||||
for k in sort(collect(keys(plotattributes)))
|
||||
@printf("%14s: ", k)
|
||||
debugshow(io, plotattributes[k])
|
||||
println(io)
|
||||
end
|
||||
_debugMode.on || alwaysshow || return
|
||||
println(io)
|
||||
if prefix != ""
|
||||
println(io, prefix, ":")
|
||||
end
|
||||
for k in sort(collect(keys(plotattributes)))
|
||||
@printf("%14s: ", k)
|
||||
debugshow(io, plotattributes[k])
|
||||
println(io)
|
||||
end
|
||||
println(io)
|
||||
end
|
||||
DD(io::IO, plotattributes::AKW, prefix = "") = dumpdict(io, plotattributes, prefix, true)
|
||||
DD(plotattributes::AKW, prefix = "") = DD(stdout, plotattributes, prefix)
|
||||
|
||||
function dumpcallstack()
|
||||
error() # well... you wanted the stacktrace, didn't you?!?
|
||||
error() # well... you wanted the stacktrace, didn't you?!?
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# ---------------------------------------------------------------
|
||||
# used in updating an existing series
|
||||
|
||||
extendSeriesByOne(v::UnitRange{Int}, n::Int = 1) = isempty(v) ? (1:n) : (minimum(v):maximum(v)+n)
|
||||
extendSeriesByOne(v::AVec, n::Integer = 1) = isempty(v) ? (1:n) : vcat(v, (1:n) + ignorenan_maximum(v))
|
||||
extendSeriesData(v::AbstractRange{T}, z::Real) where {T} = extendSeriesData(float(collect(v)), z)
|
||||
extendSeriesData(v::AbstractRange{T}, z::AVec) where {T} = extendSeriesData(float(collect(v)), z)
|
||||
extendSeriesData(v::AVec{T}, z::Real) where {T} = (push!(v, convert(T, z)); v)
|
||||
extendSeriesData(v::AVec{T}, z::AVec) where {T} = (append!(v, convert(Vector{T}, z)); v)
|
||||
|
||||
|
||||
# -------------------------------------------------------
|
||||
# NOTE: backends should implement the following methods to get/set the x/y/z data objects
|
||||
|
||||
@@ -779,60 +905,33 @@ Base.setindex!(plt::Plot, xy::Tuple{X,Y}, i::Integer) where {X,Y} = (setxy!(plt,
|
||||
Base.setindex!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z} = (setxyz!(plt, xyz, i); plt)
|
||||
|
||||
# -------------------------------------------------------
|
||||
|
||||
# operate on individual series
|
||||
|
||||
Base.push!(series::Series, args...) = extend_series!(series, args...)
|
||||
Base.append!(series::Series, args...) = extend_series!(series, args...)
|
||||
|
||||
function extend_series!(series::Series, yi)
|
||||
y = extend_series_data!(series, yi, :y)
|
||||
x = extend_to_length!(series[:x], length(y))
|
||||
expand_extrema!(series[:subplot][:xaxis], x)
|
||||
return x, y
|
||||
function push_x!(series::Series, xi)
|
||||
push!(series[:x], xi)
|
||||
expand_extrema!(series[:subplot][:xaxis], xi)
|
||||
return
|
||||
end
|
||||
function push_y!(series::Series, yi)
|
||||
push!(series[:y], yi)
|
||||
expand_extrema!(series[:subplot][:yaxis], yi)
|
||||
return
|
||||
end
|
||||
function push_z!(series::Series, zi)
|
||||
push!(series[:z], zi)
|
||||
expand_extrema!(series[:subplot][:zaxis], zi)
|
||||
return
|
||||
end
|
||||
|
||||
function extend_series!(series::Series, xi, yi)
|
||||
x = extend_series_data!(series, xi, :x)
|
||||
y = extend_series_data!(series, yi, :y)
|
||||
return x, y
|
||||
end
|
||||
|
||||
function extend_series!(series::Series, xi, yi, zi)
|
||||
x = extend_series_data!(series, xi, :x)
|
||||
y = extend_series_data!(series, yi, :y)
|
||||
z = extend_series_data!(series, zi, :z)
|
||||
return x, y, z
|
||||
end
|
||||
|
||||
function extend_series_data!(series::Series, v, letter)
|
||||
copy_series!(series, letter)
|
||||
d = extend_by_data!(series[letter], v)
|
||||
expand_extrema!(series[:subplot][Symbol(letter, :axis)], d)
|
||||
return d
|
||||
end
|
||||
|
||||
function copy_series!(series, letter)
|
||||
plt = series[:plot_object]
|
||||
for s in plt.series_list
|
||||
for l in (:x, :y, :z)
|
||||
if s !== series || l !== letter
|
||||
if s[l] === series[letter]
|
||||
series[letter] = copy(series[letter])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
extend_to_length!(v::AbstractRange, n) = range(first(v), step = step(v), length = n)
|
||||
function extend_to_length!(v::AbstractVector, n)
|
||||
vmax = isempy(v) ? 0 : ignorenan_maximum(v)
|
||||
extend_by_data!(v, vmax .+ (1:(n - length(v))))
|
||||
end
|
||||
extend_by_data!(v::AbstractVector, x) = isimmutable(v) ? vcat(v, x) : push!(v, x)
|
||||
function extend_by_data!(v::AbstractVector, x::AbstractVector)
|
||||
isimmutable(v) ? vcat(v, x) : append!(v, x)
|
||||
function Base.push!(series::Series, yi)
|
||||
x = extendSeriesByOne(series[:x])
|
||||
expand_extrema!(series[:subplot][:xaxis], x[end])
|
||||
series[:x] = x
|
||||
push_y!(series, yi)
|
||||
end
|
||||
Base.push!(series::Series, xi, yi) = (push_x!(series,xi); push_y!(series,yi))
|
||||
Base.push!(series::Series, xi, yi, zi) = (push_x!(series,xi); push_y!(series,yi); push_z!(series,zi))
|
||||
|
||||
# -------------------------------------------------------
|
||||
|
||||
@@ -866,16 +965,59 @@ end
|
||||
# -------------------------------------------------------
|
||||
# push/append for one series
|
||||
|
||||
Base.push!(plt::Plot, args::Real...) = push!(plt, 1, args...)
|
||||
Base.push!(plt::Plot, i::Integer, args::Real...) = push!(plt.series_list[i], args...)
|
||||
Base.append!(plt::Plot, args::AbstractVector...) = append!(plt, 1, args...)
|
||||
Base.append!(plt::Plot, i::Integer, args::Real...) = append!(plt.series_list[i], args...)
|
||||
# push value to first series
|
||||
Base.push!(plt::Plot, y::Real) = push!(plt, 1, y)
|
||||
Base.push!(plt::Plot, x::Real, y::Real) = push!(plt, 1, x, y)
|
||||
Base.push!(plt::Plot, x::Real, y::Real, z::Real) = push!(plt, 1, x, y, z)
|
||||
|
||||
# y only
|
||||
function Base.push!(plt::Plot, i::Integer, y::Real)
|
||||
xdata, ydata = getxy(plt, i)
|
||||
setxy!(plt, (extendSeriesByOne(xdata), extendSeriesData(ydata, y)), i)
|
||||
plt
|
||||
end
|
||||
function Base.append!(plt::Plot, i::Integer, y::AVec)
|
||||
xdata, ydata = plt[i]
|
||||
if !isa(xdata, UnitRange{Int})
|
||||
error("Expected x is a UnitRange since you're trying to push a y value only")
|
||||
end
|
||||
plt[i] = (extendSeriesByOne(xdata, length(y)), extendSeriesData(ydata, y))
|
||||
plt
|
||||
end
|
||||
|
||||
# x and y
|
||||
function Base.push!(plt::Plot, i::Integer, x::Real, y::Real)
|
||||
xdata, ydata = getxy(plt, i)
|
||||
setxy!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y)), i)
|
||||
plt
|
||||
end
|
||||
function Base.append!(plt::Plot, i::Integer, x::AVec, y::AVec)
|
||||
@assert length(x) == length(y)
|
||||
xdata, ydata = getxy(plt, i)
|
||||
setxy!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y)), i)
|
||||
plt
|
||||
end
|
||||
|
||||
# x, y, and z
|
||||
function Base.push!(plt::Plot, i::Integer, x::Real, y::Real, z::Real)
|
||||
# @show i, x, y, z
|
||||
xdata, ydata, zdata = getxyz(plt, i)
|
||||
# @show xdata, ydata, zdata
|
||||
setxyz!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y), extendSeriesData(zdata, z)), i)
|
||||
plt
|
||||
end
|
||||
function Base.append!(plt::Plot, i::Integer, x::AVec, y::AVec, z::AVec)
|
||||
@assert length(x) == length(y) == length(z)
|
||||
xdata, ydata, zdata = getxyz(plt, i)
|
||||
setxyz!(plt, (extendSeriesData(xdata, x), extendSeriesData(ydata, y), extendSeriesData(zdata, z)), i)
|
||||
plt
|
||||
end
|
||||
|
||||
# tuples
|
||||
Base.push!(plt::Plot, t::Tuple) = push!(plt, 1, t...)
|
||||
Base.push!(plt::Plot, i::Integer, t::Tuple) = push!(plt, i, t...)
|
||||
Base.append!(plt::Plot, t::Tuple) = append!(plt, 1, t...)
|
||||
Base.append!(plt::Plot, i::Integer, t::Tuple) = append!(plt, i, t...)
|
||||
Base.push!(plt::Plot, xy::Tuple{X,Y}) where {X,Y} = push!(plt, 1, xy...)
|
||||
Base.push!(plt::Plot, xyz::Tuple{X,Y,Z}) where {X,Y,Z} = push!(plt, 1, xyz...)
|
||||
Base.push!(plt::Plot, i::Integer, xy::Tuple{X,Y}) where {X,Y} = push!(plt, i, xy...)
|
||||
Base.push!(plt::Plot, i::Integer, xyz::Tuple{X,Y,Z}) where {X,Y,Z} = push!(plt, i, xyz...)
|
||||
|
||||
# -------------------------------------------------------
|
||||
# push/append for all series
|
||||
|
||||
@@ -9,7 +9,6 @@ using LibGit2
|
||||
using GeometryTypes
|
||||
using Dates
|
||||
|
||||
include("test_hdf5plots.jl")
|
||||
include("test_pgfplotsx.jl")
|
||||
|
||||
reference_dir(args...) = joinpath(homedir(), ".julia", "dev", "PlotReferenceImages", args...)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using Plots, HDF5
|
||||
|
||||
|
||||
@testset "HDF5_Plots" begin
|
||||
fname = "tmpplotsave.hdf5"
|
||||
hdf5()
|
||||
|
||||
x = 1:10
|
||||
psrc=plot(x, x.*x); #Create some plot
|
||||
Plots.hdf5plot_write(psrc, fname)
|
||||
|
||||
#Read back file:
|
||||
gr() #Choose some fast backend likely to work in test environment.
|
||||
pread = Plots.hdf5plot_read(fname)
|
||||
|
||||
#Make sure data made it through:
|
||||
@test psrc.subplots[1].series_list[1][:x] == pread.subplots[1].series_list[1][:x]
|
||||
@test psrc.subplots[1].series_list[1][:y] == pread.subplots[1].series_list[1][:y]
|
||||
|
||||
#display(pread) #Don't display. Regression env might not support
|
||||
end #testset
|
||||
+2
-25
@@ -20,19 +20,6 @@ end
|
||||
@test count(x -> x isa PGFPlotsX.Plot, axis.contents) == 1
|
||||
@test !haskey(axis.contents[1].options.dict, "fill")
|
||||
|
||||
@testset "Legends" begin
|
||||
legends_plot = plot( rand(5,2), lab = ["1" ""] )
|
||||
scatter!(legends_plot, rand(5) )
|
||||
Plots._update_plot_object(legends_plot)
|
||||
axis_contents = Plots.pgfx_axes(legends_plot.o)[1].contents
|
||||
leg_entries = filter( x -> x isa PGFPlotsX.LegendEntry, axis_contents )
|
||||
series = filter( x -> x isa PGFPlotsX.Plot, axis_contents )
|
||||
@test length(leg_entries) == 2
|
||||
@test !haskey(series[1].options.dict, "forget plot")
|
||||
@test haskey(series[2].options.dict, "forget plot")
|
||||
@test !haskey(series[3].options.dict, "forget plot")
|
||||
end # testset
|
||||
|
||||
@testset "3D docs example" begin
|
||||
n = 100
|
||||
ts = range(0, stop = 8π, length = n)
|
||||
@@ -87,18 +74,8 @@ end
|
||||
@test marker.options["mark options"]["line width"] == 1
|
||||
end # testset
|
||||
@testset "Plot in pieces" begin
|
||||
pic = plot(rand(100) / 3, reg = true, fill = (0, :green))
|
||||
scatter!(pic, rand(100), markersize = 6, c = :orange)
|
||||
Plots._update_plot_object(pic)
|
||||
axis_contents = Plots.pgfx_axes(pic.o)[1].contents
|
||||
leg_entries = filter( x -> x isa PGFPlotsX.LegendEntry, axis_contents )
|
||||
series = filter( x -> x isa PGFPlotsX.Plot, axis_contents )
|
||||
@test length(leg_entries) == 2
|
||||
@test length(series) == 4
|
||||
@test haskey(series[1].options.dict, "forget plot")
|
||||
@test !haskey(series[2].options.dict, "forget plot")
|
||||
@test haskey(series[3].options.dict, "forget plot")
|
||||
@test !haskey(series[4].options.dict, "forget plot")
|
||||
plot(rand(100) / 3, reg = true, fill = (0, :green))
|
||||
scatter!(rand(100), markersize = 6, c = :orange)
|
||||
end # testset
|
||||
@testset "Marker types" begin
|
||||
markers = filter((m -> begin
|
||||
|
||||
Reference in New Issue
Block a user