Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a1fe30567 | |||
| db685b6ee0 | |||
| fcb9078838 | |||
| 3310025602 | |||
| 122a470078 | |||
| bb0b6e5d33 | |||
| 7185e36795 | |||
| d111c2ba91 | |||
| 5dff00e2a3 |
+4
-13
@@ -6,14 +6,10 @@ os:
|
||||
julia:
|
||||
- 1.1
|
||||
- nightly
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- at-spi2-core
|
||||
- libgtk-3-dev
|
||||
- xauth
|
||||
- xvfb
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- julia: nightly
|
||||
|
||||
sudo: required
|
||||
before_install:
|
||||
@@ -22,8 +18,3 @@ before_install:
|
||||
|
||||
notifications:
|
||||
email: true
|
||||
|
||||
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)'
|
||||
|
||||
@@ -11,15 +11,6 @@
|
||||
---
|
||||
## (current master)
|
||||
|
||||
## 0.26.3
|
||||
- fix `vline` with dates
|
||||
- fix PyPlot logscale bug
|
||||
- avoid annotation clipping for PyPlot
|
||||
- allow plotting of Any vectors and 3D plotting again in convertToAnyVector
|
||||
- specify legend title font in GR and PyPlot
|
||||
- delete `pushtomaster.sh`
|
||||
- use `=== nothing`
|
||||
|
||||
## 0.26.2
|
||||
- improve empty animation build error
|
||||
- fix GR axis flip for heatmaps and images
|
||||
|
||||
+5
-6
@@ -1,7 +1,7 @@
|
||||
name = "Plots"
|
||||
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
|
||||
author = ["Tom Breloff (@tbreloff)"]
|
||||
version = "0.27.0"
|
||||
version = "0.26.2"
|
||||
|
||||
[deps]
|
||||
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
|
||||
@@ -34,25 +34,24 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
||||
FixedPointNumbers = "≥ 0.3.0"
|
||||
GR = "≥ 0.31.0"
|
||||
PlotThemes = "≥ 0.1.3"
|
||||
PlotUtils = "≥ 0.6.0"
|
||||
PlotUtils = "≥ 0.4.1"
|
||||
RecipesBase = "≥ 0.6.0"
|
||||
StatsBase = "≥ 0.14.0"
|
||||
julia = "≥ 1.0.0"
|
||||
|
||||
[extras]
|
||||
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
|
||||
GeometryTypes = "4d00f742-c7ba-57c2-abde-4428a4b178cb"
|
||||
Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44"
|
||||
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
|
||||
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
|
||||
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
|
||||
LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
|
||||
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
|
||||
RDatasets = "ce6b1742-4840-55fa-b093-852dadbb1d8b"
|
||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||
StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd"
|
||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
|
||||
VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92"
|
||||
BinaryProvider = "b99e7846-7c00-51b0-8f62-c81ae34c0232"
|
||||
|
||||
[targets]
|
||||
test = ["FileIO", "GeometryTypes", "Gtk", "ImageMagick", "Images", "LaTeXStrings", "LibGit2", "Random", "RDatasets", "StatsPlots", "Test", "UnicodePlots", "VisualRegressionTests"]
|
||||
test = ["BinaryProvider", "Pkg", "Test", "Random", "StatsPlots", "VisualRegressionTests", "LaTeXStrings", "Images", "ImageMagick", "RDatasets", "FileIO", "UnicodePlots"]
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
git checkout master
|
||||
git merge --ff-only dev
|
||||
git push origin master
|
||||
git checkout dev
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
module Plots
|
||||
|
||||
const _current_plots_version = VersionNumber(split(first(filter(line -> occursin("version", line), readlines(normpath(@__DIR__, "..", "Project.toml")))), "\"")[2])
|
||||
_current_plots_version = v"0.26.1"
|
||||
|
||||
using Reexport
|
||||
|
||||
@@ -220,6 +220,7 @@ end
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
const CURRENT_BACKEND = CurrentBackend(:none)
|
||||
const CURRENT_BACKEND = Plots.CurrentBackend(:gr)
|
||||
gr()
|
||||
|
||||
end # module
|
||||
|
||||
+1
-8
@@ -1,5 +1,5 @@
|
||||
|
||||
const _arg_desc = KW(
|
||||
const _arg_desc = Dict{Symbol,String}(
|
||||
|
||||
# series args
|
||||
:label => "String type. The label for a series, which appears in a legend. If empty, no legend entry is added.",
|
||||
@@ -95,16 +95,9 @@ const _arg_desc = KW(
|
||||
:legendfontvalign => "Symbol. Font vertical alignment of legend entries: :vcenter, :top, :bottom or :center",
|
||||
:legendfontrotation => "Real. Font rotation of legend entries",
|
||||
:legendfontcolor => "Color Type. Font color of legend entries",
|
||||
:legendtitlefontfamily => "String or Symbol. Font family of the legend title.",
|
||||
:legendtitlefontsize => "Integer. Font pointsize the legend title.",
|
||||
:legendtitlefonthalign => "Symbol. Font horizontal alignment of the legend title: :hcenter, :left, :right or :center",
|
||||
:legendtitlefontvalign => "Symbol. Font vertical alignment of the legend title: :vcenter, :top, :bottom or :center",
|
||||
:legendtitlefontrotation => "Real. Font rotation of the legend title",
|
||||
:legendtitlefontcolor => "Color Type. Font color of the legend title",
|
||||
:colorbar => "Bool (show the colorbar?) or Symbol (colorbar position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:legend` (matches legend value) (note: only some may be supported in each backend)",
|
||||
:clims => "`:auto` or NTuple{2,Number}. Fixes the limits of the colorbar.",
|
||||
:legendfont => "Font. Font of legend items.",
|
||||
:legendtitlefont => "Font. Font of the legend title.",
|
||||
:annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String or PlotText (created with `text(args...)`) Add one-off text annotations at the x,y coordinates.",
|
||||
:projection => "Symbol or String. '3d' or 'polar'",
|
||||
:aspect_ratio => "Symbol (:equal) or Number. Plot area is resized so that 1 y-unit is the same size as `aspect_ratio` x-units.",
|
||||
|
||||
+51
-69
@@ -2,7 +2,7 @@
|
||||
|
||||
const _keyAliases = Dict{Symbol,Symbol}()
|
||||
|
||||
function add_aliases(sym::Symbol, aliases::Symbol...)
|
||||
@noinline function add_aliases(sym::Symbol, aliases::Symbol...)
|
||||
for alias in aliases
|
||||
if haskey(_keyAliases, alias)
|
||||
error("Already an alias $alias => $(_keyAliases[alias])... can't also alias $sym")
|
||||
@@ -11,7 +11,7 @@ function add_aliases(sym::Symbol, aliases::Symbol...)
|
||||
end
|
||||
end
|
||||
|
||||
function add_non_underscore_aliases!(aliases::Dict{Symbol,Symbol})
|
||||
@noinline function add_non_underscore_aliases!(aliases::Dict{Symbol,Symbol})
|
||||
for (k,v) in aliases
|
||||
s = string(k)
|
||||
if '_' in s
|
||||
@@ -84,17 +84,17 @@ const _histogram_like = [:histogram, :barhist, :barbins]
|
||||
const _line_like = [:line, :path, :steppre, :steppost]
|
||||
const _surface_like = [:contour, :contourf, :contour3d, :heatmap, :surface, :wireframe, :image]
|
||||
|
||||
like_histogram(seriestype::Symbol) = seriestype in _histogram_like
|
||||
like_line(seriestype::Symbol) = seriestype in _line_like
|
||||
like_surface(seriestype::Symbol) = seriestype in _surface_like
|
||||
@noinline like_histogram(seriestype::Symbol) = seriestype in _histogram_like
|
||||
@noinline like_line(seriestype::Symbol) = seriestype in _line_like
|
||||
@noinline like_surface(seriestype::Symbol) = seriestype in _surface_like
|
||||
|
||||
is3d(seriestype::Symbol) = seriestype in _3dTypes
|
||||
is3d(series::Series) = is3d(series.plotattributes)
|
||||
is3d(plotattributes::KW) = trueOrAllTrue(is3d, Symbol(plotattributes[:seriestype]))
|
||||
@noinline is3d(seriestype::Symbol) = seriestype in _3dTypes
|
||||
@noinline is3d(series::Series) = is3d(series.plotattributes)
|
||||
@noinline is3d(plotattributes::KW) = trueOrAllTrue(is3d, Symbol(plotattributes[:seriestype]))
|
||||
|
||||
is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d"
|
||||
ispolar(sp::Subplot) = string(sp.attr[:projection]) == "polar"
|
||||
ispolar(series::Series) = ispolar(series.plotattributes[:subplot])
|
||||
@noinline is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d"
|
||||
@noinline ispolar(sp::Subplot) = string(sp.attr[:projection]) == "polar"
|
||||
@noinline ispolar(series::Series) = ispolar(series.plotattributes[:subplot])
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -188,9 +188,9 @@ const _allGridSyms = [:x, :y, :z,
|
||||
:all, :both, :on, :yes, :show,
|
||||
:none, :off, :no, :hide]
|
||||
const _allGridArgs = [_allGridSyms; string.(_allGridSyms); nothing]
|
||||
hasgrid(arg::Nothing, letter) = false
|
||||
hasgrid(arg::Bool, letter) = arg
|
||||
function hasgrid(arg::Symbol, letter)
|
||||
@noinline hasgrid(arg::Nothing, letter) = false
|
||||
@noinline hasgrid(arg::Bool, letter) = arg
|
||||
@noinline function hasgrid(arg::Symbol, letter)
|
||||
if arg in _allGridSyms
|
||||
arg in (:all, :both, :on) || occursin(string(letter), string(arg))
|
||||
else
|
||||
@@ -198,7 +198,7 @@ function hasgrid(arg::Symbol, letter)
|
||||
true
|
||||
end
|
||||
end
|
||||
hasgrid(arg::AbstractString, letter) = hasgrid(Symbol(arg), letter)
|
||||
@noinline hasgrid(arg::AbstractString, letter) = hasgrid(Symbol(arg), letter)
|
||||
|
||||
const _allShowaxisSyms = [:x, :y, :z,
|
||||
:xy, :xz, :yx, :yz, :zx, :zy,
|
||||
@@ -206,9 +206,9 @@ const _allShowaxisSyms = [:x, :y, :z,
|
||||
:all, :both, :on, :yes, :show,
|
||||
:off, :no, :hide]
|
||||
const _allShowaxisArgs = [_allGridSyms; string.(_allGridSyms)]
|
||||
showaxis(arg::Nothing, letter) = false
|
||||
showaxis(arg::Bool, letter) = arg
|
||||
function showaxis(arg::Symbol, letter)
|
||||
@noinline showaxis(arg::Nothing, letter) = false
|
||||
@noinline showaxis(arg::Bool, letter) = arg
|
||||
@noinline function showaxis(arg::Symbol, letter)
|
||||
if arg in _allGridSyms
|
||||
arg in (:all, :both, :on, :yes) || occursin(string(letter), string(arg))
|
||||
else
|
||||
@@ -216,7 +216,7 @@ function showaxis(arg::Symbol, letter)
|
||||
true
|
||||
end
|
||||
end
|
||||
showaxis(arg::AbstractString, letter) = hasgrid(Symbol(arg), letter)
|
||||
@noinline showaxis(arg::AbstractString, letter) = hasgrid(Symbol(arg), letter)
|
||||
|
||||
const _allFramestyles = [:box, :semi, :axes, :origin, :zerolines, :grid, :none]
|
||||
const _framestyleAliases = Dict{Symbol, Symbol}(
|
||||
@@ -336,12 +336,6 @@ const _subplot_defaults = KW(
|
||||
:legendfontvalign => :vcenter,
|
||||
:legendfontrotation => 0.0,
|
||||
:legendfontcolor => :match,
|
||||
:legendtitlefontfamily => :match,
|
||||
:legendtitlefontsize => 11,
|
||||
:legendtitlefonthalign => :hcenter,
|
||||
:legendtitlefontvalign => :vcenter,
|
||||
:legendtitlefontrotation => 0.0,
|
||||
:legendtitlefontcolor => :match,
|
||||
:annotations => [], # annotation tuples... list of (x,y,annotation)
|
||||
:projection => :none, # can also be :polar or :3d
|
||||
:aspect_ratio => :none, # choose from :none or :equal
|
||||
@@ -454,9 +448,8 @@ const _initial_defaults = deepcopy(_all_defaults)
|
||||
const _initial_axis_defaults = deepcopy(_axis_defaults)
|
||||
|
||||
# to be able to reset font sizes to initial values
|
||||
const _initial_fontsizes = Dict(:titlefontsize => _subplot_defaults[:titlefontsize],
|
||||
const _initial_fontsizes = Dict{Any,Any}(:titlefontsize => _subplot_defaults[:titlefontsize],
|
||||
:legendfontsize => _subplot_defaults[:legendfontsize],
|
||||
:legendtitlefontsize => _subplot_defaults[:legendtitlefontsize],
|
||||
:tickfontsize => _axis_defaults[:tickfontsize],
|
||||
:guidefontsize => _axis_defaults[:guidefontsize])
|
||||
|
||||
@@ -466,15 +459,15 @@ RecipesBase.is_key_supported(k::Symbol) = is_attr_supported(k)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
makeplural(s::Symbol) = Symbol(string(s,"s"))
|
||||
@noinline makeplural(s::Symbol) = Symbol(string(s,"s"))
|
||||
|
||||
autopick(arr::AVec, idx::Integer) = arr[mod1(idx,length(arr))]
|
||||
autopick(notarr, idx::Integer) = notarr
|
||||
@noinline autopick(arr::AVec, idx::Integer) = arr[mod1(idx,length(arr))]
|
||||
@noinline autopick(notarr, idx::Integer) = notarr
|
||||
|
||||
autopick_ignore_none_auto(arr::AVec, idx::Integer) = autopick(setdiff(arr, [:none, :auto]), idx)
|
||||
autopick_ignore_none_auto(notarr, idx::Integer) = notarr
|
||||
@noinline autopick_ignore_none_auto(arr::AVec, idx::Integer) = autopick(setdiff(arr, [:none, :auto]), idx)
|
||||
@noinline autopick_ignore_none_auto(notarr, idx::Integer) = notarr
|
||||
|
||||
function aliasesAndAutopick(plotattributes::KW, sym::Symbol, aliases::Dict{Symbol,Symbol}, options::AVec, plotIndex::Int)
|
||||
@noinline function aliasesAndAutopick(plotattributes::KW, sym::Symbol, aliases::Dict{Symbol,Symbol}, options::AVec, plotIndex::Int)
|
||||
if plotattributes[sym] == :auto
|
||||
plotattributes[sym] = autopick_ignore_none_auto(options, plotIndex)
|
||||
elseif haskey(aliases, plotattributes[sym])
|
||||
@@ -482,7 +475,7 @@ function aliasesAndAutopick(plotattributes::KW, sym::Symbol, aliases::Dict{Symbo
|
||||
end
|
||||
end
|
||||
|
||||
function aliases(aliasMap::Dict{Symbol,Symbol}, val)
|
||||
@noinline function aliases(aliasMap::Dict{Symbol,Symbol}, val)
|
||||
sortedkeys(filter((k,v)-> v==val, aliasMap))
|
||||
end
|
||||
|
||||
@@ -886,9 +879,9 @@ function processFontArg!(plotattributes::KW, fontname::Symbol, arg)
|
||||
end
|
||||
end
|
||||
|
||||
_replace_markershape(shape::Symbol) = get(_markerAliases, shape, shape)
|
||||
_replace_markershape(shapes::AVec) = map(_replace_markershape, shapes)
|
||||
_replace_markershape(shape) = shape
|
||||
@noinline _replace_markershape(shape::Symbol) = get(_markerAliases, shape, shape)
|
||||
@noinline _replace_markershape(shapes::AVec) = map(_replace_markershape, shapes)
|
||||
@noinline _replace_markershape(shape) = shape
|
||||
|
||||
function _add_markershape(plotattributes::KW)
|
||||
# add the markershape if it needs to be added... hack to allow "m=10" to add a shape,
|
||||
@@ -937,15 +930,6 @@ function preprocessArgs!(plotattributes::KW)
|
||||
end
|
||||
end
|
||||
|
||||
# vline accesses the y argument but actually maps it to the x axis.
|
||||
# Hence, we have to swap formatters
|
||||
if get(plotattributes, :seriestype, :path) == :vline
|
||||
xformatter = get(plotattributes, :xformatter, :auto)
|
||||
yformatter = get(plotattributes, :yformatter, :auto)
|
||||
plotattributes[:xformatter] = yformatter
|
||||
plotattributes[:yformatter] = xformatter
|
||||
end
|
||||
|
||||
# handle grid args common to all axes
|
||||
args = pop!(plotattributes, :grid, ())
|
||||
for arg in wraptuple(args)
|
||||
@@ -977,7 +961,7 @@ function preprocessArgs!(plotattributes::KW)
|
||||
end
|
||||
end
|
||||
# fonts
|
||||
for fontname in (:titlefont, :legendfont, :legendtitlefont)
|
||||
for fontname in (:titlefont, :legendfont)
|
||||
args = pop!(plotattributes, fontname, ())
|
||||
for arg in wraptuple(args)
|
||||
processFontArg!(plotattributes, fontname, arg)
|
||||
@@ -1090,7 +1074,7 @@ end
|
||||
|
||||
|
||||
# this is when given a vector-type of values to group by
|
||||
function extractGroupArgs(v::AVec, args...; legendEntry = string)
|
||||
@noinline function extractGroupArgs(v::AVec, args...; legendEntry = string)
|
||||
groupLabels = sort(collect(unique(v)))
|
||||
n = length(groupLabels)
|
||||
if n > 100
|
||||
@@ -1100,17 +1084,17 @@ function extractGroupArgs(v::AVec, args...; legendEntry = string)
|
||||
GroupBy(map(legendEntry, groupLabels), groupIds)
|
||||
end
|
||||
|
||||
legendEntryFromTuple(ns::Tuple) = join(ns, ' ')
|
||||
@noinline legendEntryFromTuple(ns::Tuple) = join(ns, ' ')
|
||||
|
||||
# this is when given a tuple of vectors of values to group by
|
||||
function extractGroupArgs(vs::Tuple, args...)
|
||||
@noinline function extractGroupArgs(vs::Tuple, args...)
|
||||
isempty(vs) && return GroupBy([""], [1:size(args[1],1)])
|
||||
v = map(tuple, vs...)
|
||||
extractGroupArgs(v, args...; legendEntry = legendEntryFromTuple)
|
||||
end
|
||||
|
||||
# allow passing NamedTuples for a named legend entry
|
||||
legendEntryFromTuple(ns::NamedTuple) =
|
||||
@noinline legendEntryFromTuple(ns::NamedTuple) =
|
||||
join(["$k = $v" for (k, v) in pairs(ns)], ", ")
|
||||
|
||||
function extractGroupArgs(vs::NamedTuple, args...)
|
||||
@@ -1120,22 +1104,22 @@ function extractGroupArgs(vs::NamedTuple, args...)
|
||||
end
|
||||
|
||||
# expecting a mapping of "group label" to "group indices"
|
||||
function extractGroupArgs(idxmap::Dict{T,V}, args...) where {T, V<:AVec{Int}}
|
||||
@noinline function extractGroupArgs(idxmap::Dict{T,V}, args...) where {T, V<:AVec{Int}}
|
||||
groupLabels = sortedkeys(idxmap)
|
||||
groupIds = Vector{Int}[collect(idxmap[k]) for k in groupLabels]
|
||||
GroupBy(groupLabels, groupIds)
|
||||
end
|
||||
|
||||
filter_data(v::AVec, idxfilter::AVec{Int}) = v[idxfilter]
|
||||
filter_data(v, idxfilter) = v
|
||||
@noinline filter_data(v::AVec, idxfilter::AVec{Int}) = v[idxfilter]
|
||||
@noinline filter_data(v, idxfilter) = v
|
||||
|
||||
function filter_data!(plotattributes::KW, idxfilter)
|
||||
@noinline function filter_data!(plotattributes::KW, idxfilter)
|
||||
for s in (:x, :y, :z)
|
||||
plotattributes[s] = filter_data(get(plotattributes, s, nothing), idxfilter)
|
||||
end
|
||||
end
|
||||
|
||||
function _filter_input_data!(plotattributes::KW)
|
||||
@noinline function _filter_input_data!(plotattributes::KW)
|
||||
idxfilter = pop!(plotattributes, :idxfilter, nothing)
|
||||
if idxfilter !== nothing
|
||||
filter_data!(plotattributes, idxfilter)
|
||||
@@ -1198,7 +1182,7 @@ end
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
function convertLegendValue(val::Symbol)
|
||||
@noinline function convertLegendValue(val::Symbol)
|
||||
if val in (:both, :all, :yes)
|
||||
:best
|
||||
elseif val in (:no, :none)
|
||||
@@ -1209,10 +1193,10 @@ function convertLegendValue(val::Symbol)
|
||||
error("Invalid symbol for legend: $val")
|
||||
end
|
||||
end
|
||||
convertLegendValue(val::Bool) = val ? :best : :none
|
||||
convertLegendValue(val::Nothing) = :none
|
||||
convertLegendValue(v::Tuple{S,T}) where {S<:Real, T<:Real} = v
|
||||
convertLegendValue(v::AbstractArray) = map(convertLegendValue, v)
|
||||
@noinline convertLegendValue(val::Bool) = val ? :best : :none
|
||||
@noinline convertLegendValue(val::Nothing) = :none
|
||||
@noinline convertLegendValue(v::Tuple{S,T}) where {S<:Real, T<:Real} = v
|
||||
@noinline convertLegendValue(v::AbstractArray) = map(convertLegendValue, v)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -1221,12 +1205,12 @@ convertLegendValue(v::AbstractArray) = map(convertLegendValue, v)
|
||||
# multi-row matrices will give a column
|
||||
# InputWrapper just gives the contents
|
||||
# anything else is returned as-is
|
||||
function slice_arg(v::AMat, idx::Int)
|
||||
@noinline function slice_arg(v::AMat, idx::Int)
|
||||
c = mod1(idx, size(v,2))
|
||||
size(v,1) == 1 ? v[1,c] : v[:,c]
|
||||
end
|
||||
slice_arg(wrapper::InputWrapper, idx) = wrapper.obj
|
||||
slice_arg(v, idx) = v
|
||||
@noinline slice_arg(wrapper::InputWrapper, idx) = wrapper.obj
|
||||
@noinline slice_arg(v, idx) = v
|
||||
|
||||
|
||||
# given an argument key (k), we want to extract the argument value for this index.
|
||||
@@ -1287,10 +1271,8 @@ const _match_map = KW(
|
||||
:bottom_margin => :margin,
|
||||
:titlefontfamily => :fontfamily_subplot,
|
||||
:legendfontfamily => :fontfamily_subplot,
|
||||
:legendtitlefontfamily => :fontfamily_subplot,
|
||||
:titlefontcolor => :foreground_color_subplot,
|
||||
:legendfontcolor => :foreground_color_subplot,
|
||||
:legendtitlefontcolor => :foreground_color_subplot,
|
||||
:tickfontcolor => :foreground_color_text,
|
||||
:guidefontcolor => :foreground_color_guide,
|
||||
)
|
||||
@@ -1527,9 +1509,9 @@ end
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
has_black_border_for_default(st) = error("The seriestype attribute only accepts Symbols, you passed the $(typeof(st)) $st.")
|
||||
has_black_border_for_default(st::Function) = error("The seriestype attribute only accepts Symbols, you passed the function $st.")
|
||||
function has_black_border_for_default(st::Symbol)
|
||||
@noinline has_black_border_for_default(st) = error("The seriestype attribute only accepts Symbols, you passed the $(typeof(st)) $st.")
|
||||
@noinline has_black_border_for_default(st::Function) = error("The seriestype attribute only accepts Symbols, you passed the function $st.")
|
||||
@noinline function has_black_border_for_default(st::Symbol)
|
||||
like_histogram(st) || st in (:hexbin, :bar, :shape)
|
||||
end
|
||||
|
||||
|
||||
+1
-3
@@ -218,8 +218,6 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
|
||||
map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, :plain))
|
||||
elseif formatter == :scientific
|
||||
Showoff.showoff(unscaled_ticks, :scientific)
|
||||
elseif formatter == :latex
|
||||
map(x -> string("\$", replace(convert_sci_unicode(x), '×' => "\\times"), "\$"), Showoff.showoff(unscaled_ticks, :auto))
|
||||
else
|
||||
# there was an override for the formatter... use that on the unscaled ticks
|
||||
map(formatter, unscaled_ticks)
|
||||
@@ -756,5 +754,5 @@ function axis_drawing_info(sp::Subplot)
|
||||
end
|
||||
end
|
||||
|
||||
xticks, yticks, xaxis_segs, yaxis_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xminorgrid_segs, yminorgrid_segs, xborder_segs, yborder_segs
|
||||
Any[xticks, yticks, xaxis_segs, yaxis_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xminorgrid_segs, yminorgrid_segs, xborder_segs, yborder_segs]
|
||||
end
|
||||
|
||||
+29
-19
@@ -47,17 +47,17 @@ end
|
||||
# ---------------------------------------------------------
|
||||
|
||||
# don't do anything as a default
|
||||
_create_backend_figure(plt::Plot) = nothing
|
||||
_prepare_plot_object(plt::Plot) = nothing
|
||||
_initialize_subplot(plt::Plot, sp::Subplot) = nothing
|
||||
@noinline _create_backend_figure(plt::Plot) = nothing
|
||||
@noinline _prepare_plot_object(plt::Plot) = nothing
|
||||
@noinline _initialize_subplot(plt::Plot, sp::Subplot) = nothing
|
||||
|
||||
_series_added(plt::Plot, series::Series) = nothing
|
||||
_series_updated(plt::Plot, series::Series) = nothing
|
||||
@noinline _series_added(plt::Plot, series::Series) = nothing
|
||||
@noinline _series_updated(plt::Plot, series::Series) = nothing
|
||||
|
||||
_before_layout_calcs(plt::Plot) = nothing
|
||||
@noinline _before_layout_calcs(plt::Plot) = nothing
|
||||
|
||||
title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefontsize] * pt
|
||||
guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefontsize] * pt
|
||||
@noinline title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefontsize] * pt
|
||||
@noinline guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefontsize] * pt
|
||||
|
||||
"Returns the (width,height) of a text label."
|
||||
function text_size(lablen::Int, sz::Number, rot::Number = 0)
|
||||
@@ -123,20 +123,32 @@ function _update_min_padding!(sp::Subplot)
|
||||
sp.minpad = (leftpad, toppad, rightpad, bottompad)
|
||||
end
|
||||
|
||||
_update_plot_object(plt::Plot) = nothing
|
||||
@noinline _update_plot_object(plt::Plot) = nothing
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
|
||||
mutable struct CurrentBackend
|
||||
sym::Symbol
|
||||
pkg::AbstractBackend
|
||||
mutable struct CurrentBackend{sym,T}
|
||||
pkg::T
|
||||
end
|
||||
function CurrentBackend(sym::Symbol)
|
||||
bkend = _backend_instance(sym)
|
||||
CurrentBackend{sym,typeof(bkend)}(bkend)
|
||||
end
|
||||
|
||||
function Base.getproperty(bkend::CurrentBackend{sym,T},x::Symbol) where {sym,T}
|
||||
if x === :sym
|
||||
return sym
|
||||
elseif x === :pkg
|
||||
return getfield(bkend,:pkg)
|
||||
else
|
||||
error("Must be sym or pkg")
|
||||
end
|
||||
end
|
||||
CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym))
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
_fallback_default_backend() = backend(GRBackend())
|
||||
@noinline _fallback_default_backend() = backend(GRBackend())
|
||||
|
||||
function _pick_default_backend()
|
||||
env_default = get(ENV, "PLOTS_DEFAULT_BACKEND", "")
|
||||
@@ -177,8 +189,7 @@ function backend(pkg::AbstractBackend)
|
||||
_initialize_backend(pkg)
|
||||
push!(_initialized_backends, sym)
|
||||
end
|
||||
CURRENT_BACKEND.sym = sym
|
||||
CURRENT_BACKEND.pkg = pkg
|
||||
CURRENT_BACKEND = Plots.CurrentBackend(sym)
|
||||
pkg
|
||||
end
|
||||
|
||||
@@ -294,9 +305,8 @@ function _initialize_backend(pkg::AbstractBackend)
|
||||
end
|
||||
end
|
||||
|
||||
_initialize_backend(pkg::GRBackend) = nothing
|
||||
|
||||
_initialize_backend(pkg::PlotlyBackend) = nothing
|
||||
@noinline _initialize_backend(pkg::GRBackend) = nothing
|
||||
@noinline _initialize_backend(pkg::PlotlyBackend) = nothing
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
+602
-546
File diff suppressed because it is too large
Load Diff
@@ -469,7 +469,7 @@ function plotly_series(plt::Plot, series::Series)
|
||||
plotattributes_out[:showlegend] = should_add_to_legend(series)
|
||||
|
||||
if st == :straightline
|
||||
x, y = straightline_data(series, 100)
|
||||
x, y = straightline_data(series)
|
||||
z = series[:z]
|
||||
else
|
||||
x, y, z = series[:x], series[:y], series[:z]
|
||||
@@ -584,7 +584,7 @@ function plotly_series_shapes(plt::Plot, series::Series, clims)
|
||||
)
|
||||
|
||||
x, y = (plotly_data(series, letter, data)
|
||||
for (letter, data) in zip((:x, :y), shape_data(series, 100))
|
||||
for (letter, data) in zip((:x, :y), shape_data(series))
|
||||
)
|
||||
|
||||
for (i,rng) in enumerate(segments)
|
||||
|
||||
@@ -927,7 +927,7 @@ function py_set_scale(ax, sp::Subplot, axis::Axis)
|
||||
elseif scale == :log10
|
||||
10
|
||||
end
|
||||
kw[Symbol(:linthresh,letter)] = NaNMath.max(1e-16, py_compute_axis_minval(sp, axis))
|
||||
kw[Symbol(:linthresh,letter)] = NaNMath.min(1e-16, py_compute_axis_minval(sp, axis))
|
||||
"symlog"
|
||||
end
|
||||
func(arg; kw...)
|
||||
@@ -1228,7 +1228,7 @@ end
|
||||
|
||||
function py_add_annotations(sp::Subplot{PyPlotBackend}, x, y, val)
|
||||
ax = sp.o
|
||||
ax."annotate"(val, xy = (x,y), zorder = 999, annotation_clip = false)
|
||||
ax."annotate"(val, xy = (x,y), zorder = 999)
|
||||
end
|
||||
|
||||
|
||||
@@ -1242,8 +1242,7 @@ function py_add_annotations(sp::Subplot{PyPlotBackend}, x, y, val::PlotText)
|
||||
verticalalignment = val.font.valign == :vcenter ? "center" : string(val.font.valign),
|
||||
rotation = val.font.rotation,
|
||||
size = py_thickness_scale(sp.plt, val.font.pointsize),
|
||||
zorder = 999,
|
||||
annotation_clip = false
|
||||
zorder = 999
|
||||
)
|
||||
end
|
||||
|
||||
@@ -1336,13 +1335,10 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
|
||||
frame = leg."get_frame"()
|
||||
frame."set_linewidth"(py_thickness_scale(plt, 1))
|
||||
leg."set_zorder"(1000)
|
||||
if sp[:legendtitle] !== nothing
|
||||
leg."set_title"(sp[:legendtitle])
|
||||
PyPlot.plt."setp"(leg."get_title"(), color = py_color(sp[:legendtitlefontcolor]), family = sp[:legendtitlefontfamily], fontsize = py_thickness_scale(plt, sp[:legendtitlefontsize]))
|
||||
end
|
||||
sp[:legendtitle] !== nothing && leg."set_title"(sp[:legendtitle])
|
||||
|
||||
for txt in leg."get_texts"()
|
||||
PyPlot.plt."setp"(txt, color = py_color(sp[:legendfontcolor]), family = sp[:legendfontfamily], fontsize = py_thickness_scale(plt, sp[:legendfontsize]))
|
||||
PyPlot.plt."setp"(txt, color = py_color(sp[:legendfontcolor]), family = sp[:legendfontfamily])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+2
-2
@@ -137,7 +137,7 @@ const _shape_keys = Symbol[
|
||||
:x,
|
||||
]
|
||||
|
||||
const _shapes = KW(
|
||||
const _shapes = Dict{Symbol,Shape}(
|
||||
:circle => makeshape(20),
|
||||
:rect => makeshape(4, offset=-0.25),
|
||||
:diamond => makeshape(4),
|
||||
@@ -794,7 +794,7 @@ end
|
||||
|
||||
@deprecate curve_points coords
|
||||
|
||||
coords(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, Base.range(range..., length=n))
|
||||
coords(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, range(range..., stop=n, length=50))
|
||||
|
||||
# build a BezierCurve which leaves point p vertically upwards and arrives point q vertically upwards.
|
||||
# may create a loop if necessary. Assumes the view is [0,1]
|
||||
|
||||
+5
-5
@@ -238,12 +238,12 @@ build with the method `text(string, attr...)`, which wraps font and color attrib
|
||||
""",
|
||||
[:(begin
|
||||
y = rand(10)
|
||||
plot(y, annotations = (3,y[3], Plots.text("this is #3",:left)), leg=false)
|
||||
annotate!([(5, y[5], Plots.text("this is #5",16,:red,:center)),
|
||||
(10, y[10], Plots.text("this is #10",:right,20,"courier"))])
|
||||
plot(y, annotations = (3,y[3],text("this is #3",:left)), leg=false)
|
||||
annotate!([(5, y[5], text("this is #5",16,:red,:center)),
|
||||
(10, y[10], text("this is #10",:right,20,"courier"))])
|
||||
scatter!(range(2, stop=8, length=6), rand(6), marker=(50,0.2,:orange),
|
||||
series_annotations = ["series","annotations","map","to","series",
|
||||
Plots.text("data",:green)])
|
||||
text("data",:green)])
|
||||
end)]
|
||||
),
|
||||
|
||||
@@ -461,7 +461,7 @@ _animation_examples = [2, 30]
|
||||
_backend_skips = Dict(
|
||||
:gr => [25, 30],
|
||||
:pyplot => [25, 30],
|
||||
:plotlyjs => [2, 21, 24, 25, 30, 31],
|
||||
:plotlyjs => [2, 21, 25, 30, 31],
|
||||
:pgfplots => [2, 5, 6, 10, 16, 20, 22, 23, 25, 28, 30],
|
||||
)
|
||||
|
||||
|
||||
+49
-49
@@ -107,6 +107,7 @@ function resolve_mixed(mix::MixedMeasures, sp::Subplot, letter::Symbol)
|
||||
if mix.len != 0mm
|
||||
f = (letter == :x ? width : height)
|
||||
totlen = f(plotarea(sp))
|
||||
@show totlen
|
||||
pct += mix.len / totlen
|
||||
end
|
||||
if pct != 0
|
||||
@@ -187,29 +188,29 @@ parent_bbox(layout::AbstractLayout) = bbox(parent(layout))
|
||||
# padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout)
|
||||
# padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout))
|
||||
|
||||
update_position!(layout::AbstractLayout) = nothing
|
||||
update_child_bboxes!(layout::AbstractLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) = nothing
|
||||
@noinline update_position!(layout::AbstractLayout) = nothing
|
||||
@noinline update_child_bboxes!(layout::AbstractLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) = nothing
|
||||
|
||||
left(layout::AbstractLayout) = left(bbox(layout))
|
||||
top(layout::AbstractLayout) = top(bbox(layout))
|
||||
right(layout::AbstractLayout) = right(bbox(layout))
|
||||
bottom(layout::AbstractLayout) = bottom(bbox(layout))
|
||||
width(layout::AbstractLayout) = width(bbox(layout))
|
||||
height(layout::AbstractLayout) = height(bbox(layout))
|
||||
@noinline left(layout::AbstractLayout) = left(bbox(layout))
|
||||
@noinline top(layout::AbstractLayout) = top(bbox(layout))
|
||||
@noinline right(layout::AbstractLayout) = right(bbox(layout))
|
||||
@noinline bottom(layout::AbstractLayout) = bottom(bbox(layout))
|
||||
@noinline width(layout::AbstractLayout) = width(bbox(layout))
|
||||
@noinline height(layout::AbstractLayout) = height(bbox(layout))
|
||||
|
||||
# pass these through to the bbox methods if there's no plotarea
|
||||
plotarea(layout::AbstractLayout) = bbox(layout)
|
||||
plotarea!(layout::AbstractLayout, bb::BoundingBox) = bbox!(layout, bb)
|
||||
@noinline plotarea(layout::AbstractLayout) = bbox(layout)
|
||||
@noinline plotarea!(layout::AbstractLayout, bb::BoundingBox) = bbox!(layout, bb)
|
||||
|
||||
attr(layout::AbstractLayout, k::Symbol) = layout.attr[k]
|
||||
attr(layout::AbstractLayout, k::Symbol, v) = get(layout.attr, k, v)
|
||||
attr!(layout::AbstractLayout, v, k::Symbol) = (layout.attr[k] = v)
|
||||
hasattr(layout::AbstractLayout, k::Symbol) = haskey(layout.attr, k)
|
||||
@noinline attr(layout::AbstractLayout, k::Symbol) = layout.attr[k]
|
||||
@noinline attr(layout::AbstractLayout, k::Symbol, v) = get(layout.attr, k, v)
|
||||
@noinline attr!(layout::AbstractLayout, v, k::Symbol) = (layout.attr[k] = v)
|
||||
@noinline hasattr(layout::AbstractLayout, k::Symbol) = haskey(layout.attr, k)
|
||||
|
||||
leftpad(layout::AbstractLayout) = 0mm
|
||||
toppad(layout::AbstractLayout) = 0mm
|
||||
rightpad(layout::AbstractLayout) = 0mm
|
||||
bottompad(layout::AbstractLayout) = 0mm
|
||||
@noinline leftpad(layout::AbstractLayout) = 0mm
|
||||
@noinline toppad(layout::AbstractLayout) = 0mm
|
||||
@noinline rightpad(layout::AbstractLayout) = 0mm
|
||||
@noinline bottompad(layout::AbstractLayout) = 0mm
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# RootLayout
|
||||
@@ -217,7 +218,6 @@ bottompad(layout::AbstractLayout) = 0mm
|
||||
# this is the parent of the top-level layout
|
||||
struct RootLayout <: AbstractLayout end
|
||||
|
||||
Base.show(io::IO, layout::RootLayout) = Base.show_default(io, layout)
|
||||
Base.parent(::RootLayout) = nothing
|
||||
parent_bbox(::RootLayout) = defaultbox
|
||||
bbox(::RootLayout) = defaultbox
|
||||
@@ -227,8 +227,8 @@ bbox(::RootLayout) = defaultbox
|
||||
|
||||
# contains blank space
|
||||
mutable struct EmptyLayout <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
bbox::BoundingBox
|
||||
parent
|
||||
bbox#::BoundingBox
|
||||
attr::KW # store label, width, and height for initialization
|
||||
# label # this is the label that the subplot will take (since we create a layout before initialization)
|
||||
end
|
||||
@@ -245,12 +245,12 @@ _update_min_padding!(layout::EmptyLayout) = nothing
|
||||
|
||||
# nested, gridded layout with optional size percentages
|
||||
mutable struct GridLayout <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
minpad::Tuple # leftpad, toppad, rightpad, bottompad
|
||||
bbox::BoundingBox
|
||||
grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion
|
||||
widths::Vector{Measure}
|
||||
heights::Vector{Measure}
|
||||
parent
|
||||
minpad::Tuple{AbsoluteLength,AbsoluteLength,AbsoluteLength,AbsoluteLength} # leftpad, toppad, rightpad, bottompad
|
||||
bbox#::BoundingBox
|
||||
grid::Matrix{Any} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion
|
||||
widths::Vector{Any}
|
||||
heights::Vector{Any}
|
||||
attr::KW
|
||||
end
|
||||
|
||||
@@ -268,14 +268,14 @@ function GridLayout(dims...;
|
||||
widths = zeros(dims[2]),
|
||||
heights = zeros(dims[1]),
|
||||
kw...)
|
||||
grid = Matrix{AbstractLayout}(undef, dims...)
|
||||
grid = Matrix{Any}(undef, dims...)
|
||||
layout = GridLayout(
|
||||
parent,
|
||||
(20mm, 5mm, 2mm, 10mm),
|
||||
defaultbox,
|
||||
grid,
|
||||
Measure[w*pct for w in widths],
|
||||
Measure[h*pct for h in heights],
|
||||
Any[w*pct for w in widths],
|
||||
Any[h*pct for h in heights],
|
||||
# convert(Vector{Float64}, widths),
|
||||
# convert(Vector{Float64}, heights),
|
||||
KW(kw))
|
||||
@@ -349,18 +349,18 @@ function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0
|
||||
# # create a matrix for each minimum padding direction
|
||||
# _update_min_padding!(layout)
|
||||
|
||||
minpad_left = map(leftpad, layout.grid)
|
||||
minpad_top = map(toppad, layout.grid)
|
||||
minpad_right = map(rightpad, layout.grid)
|
||||
minpad_bottom = map(bottompad, layout.grid)
|
||||
minpad_left::Matrix{AbsoluteLength} = map(leftpad, layout.grid)
|
||||
minpad_top::Matrix{AbsoluteLength} = map(toppad, layout.grid)
|
||||
minpad_right::Matrix{AbsoluteLength} = map(rightpad, layout.grid)
|
||||
minpad_bottom::Matrix{AbsoluteLength} = map(bottompad, layout.grid)
|
||||
|
||||
# get the max horizontal (left and right) padding over columns,
|
||||
# and max vertical (bottom and top) padding over rows
|
||||
# TODO: add extra padding here
|
||||
pad_left = maximum(minpad_left, dims = 1)
|
||||
pad_top = maximum(minpad_top, dims = 2)
|
||||
pad_right = maximum(minpad_right, dims = 1)
|
||||
pad_bottom = maximum(minpad_bottom, dims = 2)
|
||||
pad_left::Matrix{AbsoluteLength} = maximum(minpad_left, dims = 1)
|
||||
pad_top::Matrix{AbsoluteLength} = maximum(minpad_top, dims = 2)
|
||||
pad_right::Matrix{AbsoluteLength} = maximum(minpad_right, dims = 1)
|
||||
pad_bottom::Matrix{AbsoluteLength} = maximum(minpad_bottom, dims = 2)
|
||||
|
||||
# make sure the perimeter match the parent
|
||||
pad_left[1] = max(pad_left[1], minimum_perimeter[1])
|
||||
@@ -389,14 +389,14 @@ function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0
|
||||
child = layout[r,c]
|
||||
|
||||
# get the top-left corner of this child... the first one is top-left of the parent (i.e. layout)
|
||||
child_left = (c == 1 ? left(layout.bbox) : right(layout[r, c-1].bbox))
|
||||
child_top = (r == 1 ? top(layout.bbox) : bottom(layout[r-1, c].bbox))
|
||||
child_left::AbsoluteLength = (c == 1 ? left(layout.bbox) : right(layout[r, c-1].bbox))
|
||||
child_top::AbsoluteLength = (r == 1 ? top(layout.bbox) : bottom(layout[r-1, c].bbox))
|
||||
|
||||
# compute plot area
|
||||
plotarea_left = child_left + pad_left[c]
|
||||
plotarea_top = child_top + pad_top[r]
|
||||
plotarea_width = total_plotarea_horizontal * layout.widths[c]
|
||||
plotarea_height = total_plotarea_vertical * layout.heights[r]
|
||||
plotarea_left::AbsoluteLength = child_left + pad_left[c]
|
||||
plotarea_top::AbsoluteLength = child_top + pad_top[r]
|
||||
plotarea_width::AbsoluteLength = total_plotarea_horizontal * layout.widths[c]
|
||||
plotarea_height::AbsoluteLength = total_plotarea_vertical * layout.heights[r]
|
||||
plotarea!(child, BoundingBox(plotarea_left, plotarea_top, plotarea_width, plotarea_height))
|
||||
|
||||
# compute child bbox
|
||||
@@ -512,14 +512,14 @@ end
|
||||
|
||||
# # just a single subplot
|
||||
# function build_layout(sp::Subplot, n::Integer)
|
||||
# sp, Subplot[sp], SubplotMap(gensym() => sp)
|
||||
# sp, Subplot[sp], KW(gensym() => sp)
|
||||
# end
|
||||
|
||||
# n is the number of subplots... build a grid and initialize the inner subplots recursively
|
||||
function build_layout(layout::GridLayout, n::Integer)
|
||||
nr, nc = size(layout)
|
||||
subplots = Subplot[]
|
||||
spmap = SubplotMap()
|
||||
subplots = Any[]
|
||||
spmap = KW()
|
||||
i = 0
|
||||
for r=1:nr, c=1:nc
|
||||
l = layout[r,c]
|
||||
@@ -560,8 +560,8 @@ end
|
||||
# TODO... much of the logic overlaps with the method above... can we merge?
|
||||
function build_layout(layout::GridLayout, numsp::Integer, plts::AVec{Plot})
|
||||
nr, nc = size(layout)
|
||||
subplots = Subplot[]
|
||||
spmap = SubplotMap()
|
||||
subplots = Any[]
|
||||
spmap = KW()
|
||||
i = 0
|
||||
for r=1:nr, c=1:nc
|
||||
l = layout[r,c]
|
||||
|
||||
+16
-22
@@ -72,7 +72,6 @@ const _savemap = Dict(
|
||||
"eps" => eps,
|
||||
"tex" => tex,
|
||||
"html" => html,
|
||||
"tikz" => tex,
|
||||
)
|
||||
|
||||
function getExtension(fn::AbstractString)
|
||||
@@ -130,29 +129,29 @@ savefig(fn::AbstractString) = savefig(current(), fn)
|
||||
|
||||
Display a plot using the backends' gui window
|
||||
"""
|
||||
gui(plt::Plot = current()) = display(PlotsDisplay(), plt)
|
||||
@noinline gui(plt::Plot = current()) = display(PlotsDisplay(), plt)
|
||||
|
||||
# IJulia only... inline display
|
||||
function inline(plt::Plot = current())
|
||||
@noinline function inline(plt::Plot = current())
|
||||
isijulia() || error("inline() is IJulia-only")
|
||||
Main.IJulia.clear_output(true)
|
||||
display(Main.IJulia.InlineDisplay(), plt)
|
||||
end
|
||||
|
||||
function Base.display(::PlotsDisplay, plt::Plot)
|
||||
@noinline function Base.display(::PlotsDisplay, plt::Plot)
|
||||
prepare_output(plt)
|
||||
_display(plt)
|
||||
end
|
||||
|
||||
_do_plot_show(plt, showval::Bool) = showval && gui(plt)
|
||||
function _do_plot_show(plt, showval::Symbol)
|
||||
@noinline _do_plot_show(plt, showval::Bool) = showval && gui(plt)
|
||||
@noinline function _do_plot_show(plt, showval::Symbol)
|
||||
showval == :gui && gui(plt)
|
||||
showval in (:inline,:ijulia) && inline(plt)
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
const _best_html_output_type = KW(
|
||||
const _best_html_output_type = Dict{Symbol,Symbol}(
|
||||
:pyplot => :png,
|
||||
:unicodeplots => :txt,
|
||||
:plotlyjs => :html,
|
||||
@@ -160,7 +159,7 @@ const _best_html_output_type = KW(
|
||||
)
|
||||
|
||||
# a backup for html... passes to svg or png depending on the html_output_format arg
|
||||
function _show(io::IO, ::MIME"text/html", plt::Plot)
|
||||
@noinline function _show(io::IO, ::MIME"text/html", plt::Plot)
|
||||
output_type = Symbol(plt.attr[:html_output_format])
|
||||
if output_type == :auto
|
||||
output_type = get(_best_html_output_type, backend_name(plt.backend), :svg)
|
||||
@@ -179,11 +178,11 @@ function _show(io::IO, ::MIME"text/html", plt::Plot)
|
||||
end
|
||||
|
||||
# delegate showable to _show instead
|
||||
function Base.showable(m::M, plt::P) where {M<:MIME, P<:Plot}
|
||||
@noinline function Base.showable(m::M, plt::P) where {M<:MIME, P<:Plot}
|
||||
return hasmethod(_show, Tuple{IO, M, P})
|
||||
end
|
||||
|
||||
function _display(plt::Plot)
|
||||
@noinline function _display(plt::Plot)
|
||||
@warn("_display is not defined for this backend.")
|
||||
end
|
||||
|
||||
@@ -202,10 +201,8 @@ for mime in ("text/plain", "text/html", "image/png", "image/eps", "image/svg+xml
|
||||
end
|
||||
end
|
||||
|
||||
Base.show(io::IO, m::MIME"application/prs.juno.plotpane+html", plt::Plot) = showjuno(io, MIME("text/html"), plt)
|
||||
|
||||
# default text/plain for all backends
|
||||
_show(io::IO, ::MIME{Symbol("text/plain")}, plt::Plot) = show(io, plt)
|
||||
@noinline _show(io::IO, ::MIME{Symbol("text/plain")}, plt::Plot) = show(io, plt)
|
||||
|
||||
"Close all open gui windows of the current backend"
|
||||
closeall() = closeall(backend())
|
||||
@@ -231,17 +228,16 @@ closeall() = closeall(backend())
|
||||
# ---------------------------------------------------------
|
||||
# Atom PlotPane
|
||||
# ---------------------------------------------------------
|
||||
function showjuno(io::IO, m, plt)
|
||||
sz = collect(plt[:size])
|
||||
@noinline function showjuno(io::IO, m, plt)
|
||||
sz = plt[:size]
|
||||
dpi = plt[:dpi]
|
||||
thickness_scaling = plt[:thickness_scaling]
|
||||
|
||||
jsize = get(io, :juno_plotsize, [400, 500])
|
||||
jratio = get(io, :juno_dpi_ratio, 1)
|
||||
|
||||
scale = minimum(jsize[i] / sz[i] for i in 1:2)
|
||||
plt[:size] = [s * scale for s in sz]
|
||||
plt[:dpi] = jratio*Plots.DPI
|
||||
plt[:size] = (s * scale for s in sz)
|
||||
plt[:dpi] = Plots.DPI
|
||||
plt[:thickness_scaling] *= scale
|
||||
|
||||
prepare_output(plt)
|
||||
@@ -254,7 +250,7 @@ function showjuno(io::IO, m, plt)
|
||||
end
|
||||
end
|
||||
|
||||
function _showjuno(io::IO, m::MIME"image/svg+xml", plt)
|
||||
@noinline function _showjuno(io::IO, m::MIME"image/svg+xml", plt)
|
||||
if Symbol(plt.attr[:html_output_format]) ≠ :svg
|
||||
throw(MethodError(show, (typeof(m), typeof(plt))))
|
||||
else
|
||||
@@ -262,6 +258,4 @@ function _showjuno(io::IO, m::MIME"image/svg+xml", plt)
|
||||
end
|
||||
end
|
||||
|
||||
Base.showable(::MIME"application/prs.juno.plotpane+html", plt::Plot) = showable(MIME"text/html"(), plt)
|
||||
|
||||
_showjuno(io::IO, m, plt) = _show(io, m, plt)
|
||||
@noinline _showjuno(io::IO, m, plt) = _show(io, m, plt)
|
||||
|
||||
+15
-16
@@ -3,11 +3,11 @@
|
||||
# ------------------------------------------------------------------
|
||||
# preprocessing
|
||||
|
||||
function command_idx(kw_list::AVec{KW}, kw::KW)
|
||||
@noinline function command_idx(kw_list::AVec{KW}, kw::KW)
|
||||
Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1
|
||||
end
|
||||
|
||||
function _expand_seriestype_array(plotattributes::KW, args)
|
||||
@noinline function _expand_seriestype_array(plotattributes::KW, args)
|
||||
sts = get(plotattributes, :seriestype, :path)
|
||||
if typeof(sts) <: AbstractArray
|
||||
delete!(plotattributes, :seriestype)
|
||||
@@ -23,7 +23,7 @@ function _expand_seriestype_array(plotattributes::KW, args)
|
||||
end
|
||||
end
|
||||
|
||||
function _preprocess_args(plotattributes::KW, args, still_to_process::Vector{RecipeData})
|
||||
@noinline function _preprocess_args(plotattributes::KW, args, still_to_process::Vector{RecipeData})
|
||||
# the grouping mechanism is a recipe on a GroupBy object
|
||||
# we simply add the GroupBy object to the front of the args list to allow
|
||||
# the recipe to be applied
|
||||
@@ -57,7 +57,7 @@ end
|
||||
# user recipes
|
||||
|
||||
|
||||
function _process_userrecipes(plt::Plot, plotattributes::KW, args)
|
||||
@noinline function _process_userrecipes(plt::Plot, plotattributes::KW, args)
|
||||
still_to_process = RecipeData[]
|
||||
args = _preprocess_args(plotattributes, args, still_to_process)
|
||||
|
||||
@@ -90,7 +90,7 @@ function _process_userrecipes(plt::Plot, plotattributes::KW, args)
|
||||
kw_list
|
||||
end
|
||||
|
||||
function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeData)
|
||||
@noinline function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeData)
|
||||
# when the arg tuple is empty, that means there's nothing left to recursively
|
||||
# process... finish up and add to the kw_list
|
||||
kw = recipedata.plotattributes
|
||||
@@ -108,7 +108,7 @@ function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeD
|
||||
return
|
||||
end
|
||||
|
||||
function _preprocess_userrecipe(kw::KW)
|
||||
@noinline function _preprocess_userrecipe(kw::KW)
|
||||
_add_markershape(kw)
|
||||
|
||||
# if there was a grouping, filter the data here
|
||||
@@ -132,7 +132,7 @@ function _preprocess_userrecipe(kw::KW)
|
||||
return
|
||||
end
|
||||
|
||||
function _add_errorbar_kw(kw_list::Vector{KW}, kw::KW)
|
||||
@noinline function _add_errorbar_kw(kw_list::Vector{KW}, kw::KW)
|
||||
# handle error bars by creating new recipedata data... these will have
|
||||
# the same recipedata index as the recipedata they are copied from
|
||||
for esym in (:xerror, :yerror)
|
||||
@@ -147,7 +147,7 @@ function _add_errorbar_kw(kw_list::Vector{KW}, kw::KW)
|
||||
end
|
||||
end
|
||||
|
||||
function _add_smooth_kw(kw_list::Vector{KW}, kw::KW)
|
||||
@noinline function _add_smooth_kw(kw_list::Vector{KW}, kw::KW)
|
||||
# handle smoothing by adding a new series
|
||||
if get(kw, :smooth, false)
|
||||
x, y = kw[:x], kw[:y]
|
||||
@@ -203,7 +203,7 @@ end
|
||||
# ------------------------------------------------------------------
|
||||
# setup plot and subplot
|
||||
|
||||
function _plot_setup(plt::Plot, plotattributes::KW, kw_list::Vector{KW})
|
||||
function _plot_setup(plt::Plot{T}, plotattributes::KW, kw_list::Vector{KW}) where {T}
|
||||
# merge in anything meant for the Plot
|
||||
for kw in kw_list, (k,v) in kw
|
||||
haskey(_plot_defaults, k) && (plotattributes[k] = pop!(kw, k))
|
||||
@@ -252,18 +252,17 @@ function _plot_setup(plt::Plot, plotattributes::KW, kw_list::Vector{KW})
|
||||
plt[:inset_subplots] = nothing
|
||||
end
|
||||
|
||||
function _subplot_setup(plt::Plot, plotattributes::KW, kw_list::Vector{KW})
|
||||
function _subplot_setup(plt::Plot{T}, plotattributes::KW, kw_list::Vector{KW}) where T
|
||||
# we'll keep a map of subplot to an attribute override dict.
|
||||
# Subplot/Axis attributes set by a user/series recipe apply only to the
|
||||
# Subplot object which they belong to.
|
||||
# TODO: allow matrices to still apply to all subplots
|
||||
sp_attrs = Dict{Subplot,Any}()
|
||||
sp_attrs = Dict{Any,Any}()
|
||||
for kw in kw_list
|
||||
# get the Subplot object to which the series belongs.
|
||||
sps = get(kw, :subplot, :auto)
|
||||
sp = get_subplot(plt, _cycle(sps == :auto ? plt.subplots : plt.subplots[sps], command_idx(kw_list,kw)))
|
||||
kw[:subplot] = sp
|
||||
|
||||
# extract subplot/axis attributes from kw and add to sp_attr
|
||||
attr = KW()
|
||||
for (k,v) in collect(kw)
|
||||
@@ -303,7 +302,7 @@ end
|
||||
|
||||
# getting ready to add the series... last update to subplot from anything
|
||||
# that might have been added during series recipes
|
||||
function _prepare_subplot(plt::Plot{T}, plotattributes::KW) where T
|
||||
@noinline function _prepare_subplot(plt::Plot{T}, plotattributes::KW) where T
|
||||
st::Symbol = plotattributes[:seriestype]
|
||||
sp::Subplot{T} = plotattributes[:subplot]
|
||||
sp_idx = get_subplot_index(plt, sp)
|
||||
@@ -327,7 +326,7 @@ end
|
||||
# ------------------------------------------------------------------
|
||||
# series types
|
||||
|
||||
function _override_seriestype_check(plotattributes::KW, st::Symbol)
|
||||
@noinline function _override_seriestype_check(plotattributes::KW, st::Symbol)
|
||||
# do we want to override the series type?
|
||||
if !is3d(st) && !(st in (:contour,:contour3d))
|
||||
z = plotattributes[:z]
|
||||
@@ -339,7 +338,7 @@ function _override_seriestype_check(plotattributes::KW, st::Symbol)
|
||||
st
|
||||
end
|
||||
|
||||
function _prepare_annotations(sp::Subplot, plotattributes::KW)
|
||||
@noinline function _prepare_annotations(sp::Subplot, plotattributes::KW)
|
||||
# strip out series annotations (those which are based on series x/y coords)
|
||||
# and add them to the subplot attr
|
||||
sp_anns = annotations(sp[:annotations])
|
||||
@@ -372,7 +371,7 @@ function _expand_subplot_extrema(sp::Subplot, plotattributes::KW, st::Symbol)
|
||||
end
|
||||
end
|
||||
|
||||
function _add_the_series(plt, sp, plotattributes)
|
||||
@noinline function _add_the_series(plt, sp, plotattributes)
|
||||
warnOnUnsupported_args(plt.backend, plotattributes)
|
||||
warnOnUnsupported(plt.backend, plotattributes)
|
||||
series = Series(plotattributes)
|
||||
|
||||
+22
-15
@@ -4,19 +4,19 @@ mutable struct CurrentPlot
|
||||
end
|
||||
const CURRENT_PLOT = CurrentPlot(nothing)
|
||||
|
||||
isplotnull() = CURRENT_PLOT.nullableplot === nothing
|
||||
@noinline isplotnull() = CURRENT_PLOT.nullableplot === nothing
|
||||
|
||||
"""
|
||||
current()
|
||||
Returns the Plot object for the current plot
|
||||
"""
|
||||
function current()
|
||||
@noinline function current()
|
||||
if isplotnull()
|
||||
error("No current plot/subplot")
|
||||
end
|
||||
CURRENT_PLOT.nullableplot
|
||||
end
|
||||
current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = plot)
|
||||
@noinline current(plot::AbstractPlot) = (CURRENT_PLOT.nullableplot = plot)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@@ -25,9 +25,9 @@ Base.string(plt::Plot) = "Plot{$(plt.backend) n=$(plt.n)}"
|
||||
Base.print(io::IO, plt::Plot) = print(io, string(plt))
|
||||
Base.show(io::IO, plt::Plot) = print(io, string(plt))
|
||||
|
||||
getplot(plt::Plot) = plt
|
||||
getattr(plt::Plot, idx::Int = 1) = plt.attr
|
||||
convertSeriesIndex(plt::Plot, n::Int) = n
|
||||
@noinline getplot(plt::Plot) = plt
|
||||
@noinline getattr(plt::Plot, idx::Int = 1) = plt.attr
|
||||
@noinline convertSeriesIndex(plt::Plot, n::Int) = n
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@@ -50,11 +50,10 @@ function plot(args...; kw...)
|
||||
# this creates a new plot with args/kw and sets it to be the current plot
|
||||
plotattributes = KW(kw)
|
||||
preprocessArgs!(plotattributes)
|
||||
|
||||
# create an empty Plot then process
|
||||
plt = Plot()
|
||||
# plt.user_attr = plotattributes
|
||||
_plot!(plt, plotattributes, args)
|
||||
_plot!(plt, plotattributes, Any[args...])
|
||||
end
|
||||
|
||||
# build a new plot from existing plots
|
||||
@@ -155,7 +154,7 @@ function plot!(plt::Plot, args...; kw...)
|
||||
plotattributes = KW(kw)
|
||||
preprocessArgs!(plotattributes)
|
||||
# merge!(plt.user_attr, plotattributes)
|
||||
_plot!(plt, plotattributes, args)
|
||||
_plot!(plt, plotattributes, Any[args...])
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
@@ -163,9 +162,10 @@ 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::KW, args::Tuple)
|
||||
function _plot!(plt::Plot{T}, plotattributes::KW, args::Vector{Any}) where {T}
|
||||
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")
|
||||
@@ -175,6 +175,7 @@ function _plot!(plt::Plot, plotattributes::KW, args::Tuple)
|
||||
# "USER RECIPES"
|
||||
# --------------------------------
|
||||
|
||||
# 1 second
|
||||
kw_list = _process_userrecipes(plt, plotattributes, args)
|
||||
|
||||
# @info(1)
|
||||
@@ -189,6 +190,7 @@ function _plot!(plt::Plot, plotattributes::KW, args::Tuple)
|
||||
# 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)
|
||||
@@ -202,7 +204,11 @@ function _plot!(plt::Plot, plotattributes::KW, args::Tuple)
|
||||
# --------------------------------
|
||||
# Plot/Subplot/Layout setup
|
||||
# --------------------------------
|
||||
|
||||
# 2.5 seconds
|
||||
_plot_setup(plt, plotattributes, kw_list)
|
||||
|
||||
# 6 seconds
|
||||
_subplot_setup(plt, plotattributes, kw_list)
|
||||
|
||||
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!!
|
||||
@@ -216,7 +222,7 @@ function _plot!(plt::Plot, plotattributes::KW, args::Tuple)
|
||||
# map(DD, kw_list)
|
||||
|
||||
for kw in kw_list
|
||||
sp::Subplot = kw[:subplot]
|
||||
sp::Subplot{T} = kw[:subplot]
|
||||
# idx = get_subplot_index(plt, sp)
|
||||
|
||||
# # we update subplot args in case something like the color palatte is part of the recipe
|
||||
@@ -233,9 +239,9 @@ function _plot!(plt::Plot, plotattributes::KW, args::Tuple)
|
||||
# be able to support step, bar, and histogram plots (and any recipes that use those components).
|
||||
_process_seriesrecipe(plt, kw)
|
||||
end
|
||||
|
||||
# --------------------------------
|
||||
|
||||
# 7 seconds
|
||||
current(plt)
|
||||
|
||||
# do we want to force display?
|
||||
@@ -243,14 +249,14 @@ function _plot!(plt::Plot, plotattributes::KW, args::Tuple)
|
||||
# gui(plt)
|
||||
# end
|
||||
_do_plot_show(plt, plt[:show])
|
||||
|
||||
plt
|
||||
end
|
||||
|
||||
|
||||
# we're getting ready to display/output. prep for layout calcs, then update
|
||||
# the plot object after
|
||||
function prepare_output(plt::Plot)
|
||||
@noinline function prepare_output(plt::Plot)
|
||||
|
||||
_before_layout_calcs(plt)
|
||||
|
||||
w, h = plt.attr[:size]
|
||||
@@ -271,9 +277,10 @@ function prepare_output(plt::Plot)
|
||||
|
||||
# the backend callback, to reposition subplots, etc
|
||||
_update_plot_object(plt)
|
||||
|
||||
end
|
||||
|
||||
function backend_object(plt::Plot)
|
||||
@noinline function backend_object(plt::Plot)
|
||||
prepare_output(plt)
|
||||
plt.o
|
||||
end
|
||||
|
||||
+25
-45
@@ -7,9 +7,7 @@
|
||||
# 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}
|
||||
const DataPoint = Union{Number, AbstractString, Missing}
|
||||
const SeriesData = Union{AVec{<:DataPoint}, Function, Surface, Volume}
|
||||
|
||||
prepareSeriesData(x) = error("Cannot convert $(typeof(x)) to series data for plotting")
|
||||
@@ -17,52 +15,38 @@ prepareSeriesData(::Nothing) = nothing
|
||||
prepareSeriesData(s::SeriesData) = handlemissings(s)
|
||||
|
||||
handlemissings(v) = v
|
||||
handlemissings(v::AbstractArray{<:MaybeNumber}) = replace(v, missing => NaN)
|
||||
handlemissings(v::AbstractArray{<:MaybeString}) = replace(v, missing => "")
|
||||
handlemissings(v::AbstractArray{Union{T,Missing}}) where T <: Number = replace(v, missing => NaN)
|
||||
handlemissings(v::AbstractArray{Union{T,Missing}}) where T <: AbstractString = replace(v, missing => "")
|
||||
handlemissings(s::Surface) = Surface(handlemissings(s.surf))
|
||||
handlemissings(v::Volume) = Volume(handlemissings(v.v), v.x_extents, v.y_extents, v.z_extents)
|
||||
|
||||
# default: assume x represents a single series
|
||||
convertToAnyVector(x, plotattributes) = Any[prepareSeriesData(x)]
|
||||
convertToAnyVector(x) = Any[prepareSeriesData(x)]
|
||||
|
||||
# fixed number of blank series
|
||||
convertToAnyVector(n::Integer, plotattributes) = Any[zeros(0) for i in 1:n]
|
||||
convertToAnyVector(n::Integer) = Any[zeros(0) for i in 1:n]
|
||||
|
||||
# vector of data points is a single series
|
||||
convertToAnyVector(v::AVec{<:DataPoint}, plotattributes) = Any[prepareSeriesData(v)]
|
||||
convertToAnyVector(v::AVec{<:DataPoint}) = Any[prepareSeriesData(v)]
|
||||
|
||||
# list of things (maybe other vectors, functions, or something else)
|
||||
function convertToAnyVector(v::AVec, plotattributes)
|
||||
if all(x -> x isa MaybeNumber, v)
|
||||
convertToAnyVector(Vector{MaybeNumber}(v), plotattributes)
|
||||
elseif all(x -> x isa MaybeString, v)
|
||||
convertToAnyVector(Vector{MaybeString}(v), plotattributes)
|
||||
else
|
||||
vcat((convertToAnyVector(vi, plotattributes) for vi in v)...)
|
||||
end
|
||||
end
|
||||
convertToAnyVector(v::AVec) = vcat((convertToAnyVector(vi) for vi in v)...)
|
||||
|
||||
# Matrix is split into columns
|
||||
function convertToAnyVector(v::AMat{<:DataPoint}, plotattributes)
|
||||
if all3D(plotattributes)
|
||||
Any[prepareSeriesData(Surface(v))]
|
||||
else
|
||||
Any[prepareSeriesData(v[:, i]) for i in 1:size(v, 2)]
|
||||
end
|
||||
end
|
||||
convertToAnyVector(v::AMat{<:DataPoint}) = Any[prepareSeriesData(v[:,i]) for i in 1:size(v,2)]
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Fillranges & ribbons
|
||||
|
||||
|
||||
process_fillrange(range::Number, plotattributes) = [range]
|
||||
process_fillrange(range, plotattributes) = convertToAnyVector(range, plotattributes)
|
||||
process_fillrange(range::Number) = [range]
|
||||
process_fillrange(range) = convertToAnyVector(range)
|
||||
|
||||
process_ribbon(ribbon::Number, plotattributes) = [ribbon]
|
||||
process_ribbon(ribbon, plotattributes) = convertToAnyVector(ribbon, plotattributes)
|
||||
process_ribbon(ribbon::Number) = [ribbon]
|
||||
process_ribbon(ribbon) = convertToAnyVector(ribbon)
|
||||
# ribbon as a tuple: (lower_ribbons, upper_ribbons)
|
||||
process_ribbon(ribbon::Tuple{Any,Any}) = collect(zip(convertToAnyVector(ribbon[1], plotattributes),
|
||||
convertToAnyVector(ribbon[2], plotattributes)))
|
||||
process_ribbon(ribbon::Tuple{Any,Any}) = collect(zip(convertToAnyVector(ribbon[1]),
|
||||
convertToAnyVector(ribbon[2])))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
@@ -126,17 +110,17 @@ struct SliceIt end
|
||||
z = z.data
|
||||
end
|
||||
|
||||
xs = convertToAnyVector(x, plotattributes)
|
||||
ys = convertToAnyVector(y, plotattributes)
|
||||
zs = convertToAnyVector(z, plotattributes)
|
||||
xs = convertToAnyVector(x)
|
||||
ys = convertToAnyVector(y)
|
||||
zs = convertToAnyVector(z)
|
||||
|
||||
|
||||
fr = pop!(plotattributes, :fillrange, nothing)
|
||||
fillranges = process_fillrange(fr, plotattributes)
|
||||
fillranges = process_fillrange(fr)
|
||||
mf = length(fillranges)
|
||||
|
||||
rib = pop!(plotattributes, :ribbon, nothing)
|
||||
ribbons = process_ribbon(rib, plotattributes)
|
||||
ribbons = process_ribbon(rib)
|
||||
mr = length(ribbons)
|
||||
|
||||
# @show zs
|
||||
@@ -159,7 +143,7 @@ struct SliceIt end
|
||||
rib = ribbons[mod1(i,mr)]
|
||||
di[:ribbon] = isa(rib, Function) ? map(rib, di[:x]) : rib
|
||||
|
||||
push!(series_list, RecipeData(di, ()))
|
||||
push!(series_list, RecipeData(di, Any[]))
|
||||
end
|
||||
end
|
||||
nothing # don't add a series for the main block
|
||||
@@ -515,24 +499,20 @@ end
|
||||
# # 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)
|
||||
xs = _scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
|
||||
xs, f
|
||||
end
|
||||
@recipe function f(fs::AbstractArray{F}, xmin::Number, xmax::Number) where F<:Function
|
||||
xscale, yscale = [get(plotattributes, sym, :identity) for sym=(:xscale,:yscale)]
|
||||
xs = 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
|
||||
xs = Any[_scaled_adapted_grid(f, xscale, yscale, xmin, xmax) for f in fs]
|
||||
xs, fs
|
||||
end
|
||||
@recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, u::AVec) where {F<:Function,G<:Function} = mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u)
|
||||
@recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, umin::Number, umax::Number, n = 200) where {F<:Function,G<:Function} = fx, fy, range(umin, stop = umax, length = n)
|
||||
|
||||
function _scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
|
||||
(xf, xinv), (yf, yinv) = ((scalefunc(s),invscalefunc(s)) for s in (xscale,yscale))
|
||||
xs, ys = adapted_grid(yf∘f∘xinv, xf.((xmin, xmax)))
|
||||
xinv.(xs), yinv.(ys)
|
||||
xinv.(adapted_grid(yf∘f∘xinv, xf.((xmin, xmax))))
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
+12
-16
@@ -31,11 +31,11 @@ attr!(series::Series, v, k::Symbol) = (series.plotattributes[k] = v)
|
||||
|
||||
# a single subplot
|
||||
mutable struct Subplot{T<:AbstractBackend} <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
parent
|
||||
series_list::Vector{Series} # arguments for each series
|
||||
minpad::Tuple # leftpad, toppad, rightpad, bottompad
|
||||
bbox::BoundingBox # the canvas area which is available to this subplot
|
||||
plotarea::BoundingBox # the part where the data goes
|
||||
minpad::Tuple{AbsoluteLength,AbsoluteLength,AbsoluteLength,AbsoluteLength} # leftpad, toppad, rightpad, bottompad
|
||||
bbox#::BoundingBox # the canvas area which is available to this subplot
|
||||
plotarea#::BoundingBox # the part where the data goes
|
||||
attr::KW # args specific to this subplot
|
||||
o # can store backend-specific data... like a pyplot ax
|
||||
plt # the enclosing Plot object (can't give it a type because of no forward declarations)
|
||||
@@ -59,10 +59,6 @@ Extrema() = Extrema(Inf, -Inf)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
const SubplotMap = Dict{Any, Subplot}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
|
||||
mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
|
||||
backend::T # the backend type
|
||||
@@ -71,17 +67,17 @@ mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
|
||||
user_attr::KW # raw arg inputs (after aliases). these are used as the input dict in `_plot!`
|
||||
series_list::Vector{Series} # arguments for each series
|
||||
o # the backend's plot object
|
||||
subplots::Vector{Subplot}
|
||||
spmap::SubplotMap # provide any label as a map to a subplot
|
||||
layout::AbstractLayout
|
||||
inset_subplots::Vector{Subplot} # list of inset subplots
|
||||
subplots::Vector{Subplot{T}}
|
||||
spmap::KW # provide any label as a map to a subplot
|
||||
layout
|
||||
inset_subplots::Vector{Subplot{T}} # list of inset subplots
|
||||
init::Bool
|
||||
end
|
||||
|
||||
function Plot()
|
||||
Plot(backend(), 0, KW(), KW(), Series[], nothing,
|
||||
Subplot[], SubplotMap(), EmptyLayout(),
|
||||
Subplot[], false)
|
||||
function Plot(_backend = CURRENT_BACKEND)
|
||||
Plot(_backend.pkg, 0, KW(), KW(), Series[], nothing,
|
||||
Subplot{typeof(_backend.pkg)}[], KW(), EmptyLayout(),
|
||||
Subplot{typeof(_backend.pkg)}[], false)
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
+14
-21
@@ -115,6 +115,7 @@ function regressionXY(x, y)
|
||||
end
|
||||
|
||||
function replace_image_with_heatmap(z::Array{T}) where T<:Colorant
|
||||
@show T, size(z)
|
||||
n, m = size(z)
|
||||
# idx = 0
|
||||
colors = ColorGradient(vec(z))
|
||||
@@ -653,7 +654,7 @@ function has_attribute_segments(series::Series)
|
||||
end
|
||||
series[:seriestype] == :shape && return false
|
||||
# ... else we check relevant attributes if they have multiple inputs
|
||||
return any((typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1) for attr in [:seriescolor, :seriesalpha, :linecolor, :linealpha, :linewidth, :linestyle, :fillcolor, :fillalpha, :markercolor, :markeralpha, :markerstrokecolor, :markerstrokealpha]) || any(typeof(series[attr]) <: AbstractArray for attr in (:line_z, :fill_z, :marker_z))
|
||||
return any((typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1) for attr in [:seriescolor, :seriesalpha, :linecolor, :linealpha, :linewidth, :fillcolor, :fillalpha, :markercolor, :markeralpha, :markerstrokecolor, :markerstrokealpha]) || any(typeof(series[attr]) <: AbstractArray for attr in (:line_z, :fill_z, :marker_z))
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
@@ -1059,15 +1060,6 @@ legendfont(sp::Subplot) = font(
|
||||
sp[:legendfontcolor],
|
||||
)
|
||||
|
||||
legendtitlefont(sp::Subplot) = font(
|
||||
sp[:legendtitlefontfamily],
|
||||
sp[:legendtitlefontsize],
|
||||
sp[:legendtitlefontvalign],
|
||||
sp[:legendtitlefonthalign],
|
||||
sp[:legendtitlefontrotation],
|
||||
sp[:legendtitlefontcolor],
|
||||
)
|
||||
|
||||
tickfont(ax::Axis) = font(
|
||||
ax[:tickfontfamily],
|
||||
ax[:tickfontsize],
|
||||
@@ -1114,20 +1106,20 @@ function convert_sci_unicode(label::AbstractString)
|
||||
label
|
||||
end
|
||||
|
||||
function straightline_data(series, expansion_factor = 1)
|
||||
function straightline_data(series)
|
||||
sp = series[:subplot]
|
||||
xl, yl = isvertical(series) ? (xlims(sp), ylims(sp)) : (ylims(sp), xlims(sp))
|
||||
x, y = series[:x], series[:y]
|
||||
n = length(x)
|
||||
if n == 2
|
||||
return straightline_data(xl, yl, x, y, expansion_factor)
|
||||
return straightline_data(xl, yl, x, y)
|
||||
else
|
||||
k, r = divrem(n, 3)
|
||||
if r == 0
|
||||
xdata, ydata = fill(NaN, n), fill(NaN, n)
|
||||
for i in 1:k
|
||||
inds = (3 * i - 2):(3 * i - 1)
|
||||
xdata[inds], ydata[inds] = straightline_data(xl, yl, x[inds], y[inds], expansion_factor)
|
||||
xdata[inds], ydata[inds] = straightline_data(xl, yl, x[inds], y[inds])
|
||||
end
|
||||
return xdata, ydata
|
||||
else
|
||||
@@ -1136,7 +1128,7 @@ function straightline_data(series, expansion_factor = 1)
|
||||
end
|
||||
end
|
||||
|
||||
function straightline_data(xl, yl, x, y, expansion_factor = 1)
|
||||
function straightline_data(xl, yl, x, y)
|
||||
x_vals, y_vals = if y[1] == y[2]
|
||||
if x[1] == x[2]
|
||||
error("Two identical points cannot be used to describe a straight line.")
|
||||
@@ -1157,28 +1149,29 @@ function straightline_data(xl, yl, x, y, expansion_factor = 1)
|
||||
end
|
||||
# expand the data outside the axis limits, by a certain factor too improve
|
||||
# plotly(js) and interactive behaviour
|
||||
x_vals = x_vals .+ (x_vals[2] - x_vals[1]) .* expansion_factor .* [-1, 1]
|
||||
y_vals = y_vals .+ (y_vals[2] - y_vals[1]) .* expansion_factor .* [-1, 1]
|
||||
factor = 100
|
||||
x_vals = x_vals .+ (x_vals[2] - x_vals[1]) .* factor .* [-1, 1]
|
||||
y_vals = y_vals .+ (y_vals[2] - y_vals[1]) .* factor .* [-1, 1]
|
||||
return x_vals, y_vals
|
||||
end
|
||||
|
||||
function shape_data(series, expansion_factor = 1)
|
||||
function shape_data(series)
|
||||
sp = series[:subplot]
|
||||
xl, yl = isvertical(series) ? (xlims(sp), ylims(sp)) : (ylims(sp), xlims(sp))
|
||||
x, y = series[:x], series[:y]
|
||||
factor = 100
|
||||
for i in eachindex(x)
|
||||
if x[i] == -Inf
|
||||
x[i] = xl[1] - expansion_factor * (xl[2] - xl[1])
|
||||
x[i] = xl[1] - factor * (xl[2] - xl[1])
|
||||
elseif x[i] == Inf
|
||||
x[i] = xl[2] + expansion_factor * (xl[2] - xl[1])
|
||||
x[i] = xl[2] + factor * (xl[2] - xl[1])
|
||||
end
|
||||
end
|
||||
for i in eachindex(y)
|
||||
if y[i] == -Inf
|
||||
y[i] = yl[1] - expansion_factor * (yl[2] - yl[1])
|
||||
y[i] = yl[1] - factor * (yl[2] - yl[1])
|
||||
elseif y[i] == Inf
|
||||
y[i] = yl[2] + expansion_factor * (yl[2] - yl[1])
|
||||
y[i] = yl[2] + factor * (yl[2] - yl[1])
|
||||
end
|
||||
end
|
||||
return x, y
|
||||
|
||||
+65
-3
@@ -1,6 +1,34 @@
|
||||
import Plots._current_plots_version
|
||||
|
||||
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = !is_ci(), sigma = [1,1], tol = 1e-2)
|
||||
# Taken from MakieGallery
|
||||
"""
|
||||
Downloads the reference images from ReferenceImages for a specific version
|
||||
"""
|
||||
function download_reference(version = v"0.0.1")
|
||||
download_dir = abspath(@__DIR__, "reference_images")
|
||||
isdir(download_dir) || mkpath(download_dir)
|
||||
tarfile = joinpath(download_dir, "reference_images.zip")
|
||||
url = "https://github.com/JuliaPlots/PlotReferenceImages.jl/archive/v$(version).tar.gz"
|
||||
refpath = joinpath(download_dir, "PlotReferenceImages.jl-$(version)")
|
||||
if !isdir(refpath) # if not yet downloaded
|
||||
@info "downloading reference images for version $version"
|
||||
download(url, tarfile)
|
||||
BinaryProvider.unpack(tarfile, download_dir)
|
||||
# check again after download
|
||||
if !isdir(refpath)
|
||||
error("Something went wrong while downloading reference images. Plots can't be compared to references")
|
||||
else
|
||||
rm(tarfile, force = true)
|
||||
end
|
||||
else
|
||||
@info "using reference images for version $version (already downloaded)"
|
||||
end
|
||||
refpath
|
||||
end
|
||||
|
||||
const ref_image_dir = download_reference()
|
||||
|
||||
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], tol = 1e-2)
|
||||
Plots._debugMode.on = debug
|
||||
example = Plots._examples[idx]
|
||||
Plots.theme(:default)
|
||||
@@ -11,9 +39,36 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = !i
|
||||
# ensure consistent results
|
||||
Random.seed!(1234)
|
||||
|
||||
# reference image directory setup
|
||||
refdir = joinpath(ref_image_dir, "Plots", string(pkg))
|
||||
fn = "ref$idx.png"
|
||||
reffn = reference_file(pkg, idx, _current_plots_version)
|
||||
newfn = joinpath(reference_path(pkg, _current_plots_version), fn)
|
||||
|
||||
# firgure out version info
|
||||
vns = filter(x->x[1] != '.', readdir(refdir))
|
||||
versions = sort(VersionNumber.(vns), rev = true)
|
||||
versions = filter(v -> v <= _current_plots_version, versions)
|
||||
# @show refdir fn versions
|
||||
|
||||
newdir = joinpath(refdir, string(_current_plots_version))
|
||||
newfn = joinpath(newdir, fn)
|
||||
|
||||
# figure out which reference file we should compare to, by finding the highest versioned file
|
||||
reffn = nothing
|
||||
for v in versions
|
||||
tmpfn = joinpath(refdir, string(v), fn)
|
||||
if isfile(tmpfn)
|
||||
reffn = tmpfn
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# now we have the fn (if any)... do the comparison
|
||||
# @show reffn
|
||||
if reffn === nothing
|
||||
reffn = newfn
|
||||
end
|
||||
# @show reffn
|
||||
# return
|
||||
|
||||
# test function
|
||||
func = (fn, idx) -> begin
|
||||
@@ -23,6 +78,13 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = !i
|
||||
png(fn)
|
||||
end
|
||||
|
||||
# try
|
||||
# run(`mkdir -p $newdir`)
|
||||
# catch err
|
||||
# display(err)
|
||||
# end
|
||||
# # reffn = joinpath(refdir, "ref$idx.png")
|
||||
|
||||
# the test
|
||||
vtest = VisualTest(func, reffn, idx)
|
||||
test_images(vtest, popup=popup, sigma=sigma, tol=tol, newfn = newfn)
|
||||
|
||||
+21
-54
@@ -1,72 +1,40 @@
|
||||
import ImageMagick
|
||||
using VisualRegressionTests
|
||||
using Plots
|
||||
using Random
|
||||
using BinaryProvider
|
||||
using Test
|
||||
using FileIO
|
||||
using Gtk
|
||||
using LibGit2
|
||||
using GeometryTypes
|
||||
|
||||
reference_dir(args...) = joinpath(homedir(), ".julia", "dev", "PlotReferenceImages", args...)
|
||||
|
||||
function reference_file(backend, i, version)
|
||||
refdir = reference_dir("Plots", string(backend))
|
||||
fn = "ref$i.png"
|
||||
versions = sort(VersionNumber.(readdir(refdir)), rev = true)
|
||||
|
||||
reffn = joinpath(refdir, string(version), fn)
|
||||
for v in versions
|
||||
tmpfn = joinpath(refdir, string(v), fn)
|
||||
if isfile(tmpfn)
|
||||
reffn = tmpfn
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return reffn
|
||||
end
|
||||
|
||||
reference_path(backend, version) = reference_dir("Plots", string(backend), string(version))
|
||||
|
||||
if !isdir(reference_dir())
|
||||
mkpath(reference_dir())
|
||||
LibGit2.clone("https://github.com/JuliaPlots/PlotReferenceImages.jl.git", reference_dir())
|
||||
end
|
||||
|
||||
include("imgcomp.jl")
|
||||
# don't actually show the plots
|
||||
Random.seed!(1234)
|
||||
default(show=false, reuse=true)
|
||||
is_ci() = get(ENV, "CI", "false") == "true"
|
||||
img_tol = is_ci() ? 10e-2 : 10e-2
|
||||
img_tol = isinteractive() ? 1e-2 : 10e-2
|
||||
|
||||
@testset "Backends" begin
|
||||
@testset "GR" begin
|
||||
ENV["PLOTS_TEST"] = "true"
|
||||
ENV["GKSwstype"] = "100"
|
||||
@test gr() == Plots.GRBackend()
|
||||
@test backend() == Plots.GRBackend()
|
||||
|
||||
@testset "GR" begin
|
||||
ENV["PLOTS_TEST"] = "true"
|
||||
ENV["GKSwstype"] = "100"
|
||||
@test gr() == Plots.GRBackend()
|
||||
@test backend() == Plots.GRBackend()
|
||||
|
||||
@static if Sys.islinux()
|
||||
image_comparison_facts(:gr, tol=img_tol, skip = Plots._backend_skips[:gr])
|
||||
end
|
||||
@static if Sys.islinux()
|
||||
image_comparison_facts(:gr, tol=img_tol, skip = [25, 30])
|
||||
end
|
||||
end
|
||||
|
||||
@testset "UnicodePlots" begin
|
||||
@test unicodeplots() == Plots.UnicodePlotsBackend()
|
||||
@test backend() == Plots.UnicodePlotsBackend()
|
||||
|
||||
# lets just make sure it runs without error
|
||||
p = plot(rand(10))
|
||||
@test isa(p, Plots.Plot) == true
|
||||
@test isa(display(p), Nothing) == true
|
||||
p = bar(randn(10))
|
||||
@test isa(p, Plots.Plot) == true
|
||||
@test isa(display(p), Nothing) == true
|
||||
end
|
||||
@testset "UnicodePlots" begin
|
||||
@test unicodeplots() == Plots.UnicodePlotsBackend()
|
||||
@test backend() == Plots.UnicodePlotsBackend()
|
||||
|
||||
# lets just make sure it runs without error
|
||||
p = plot(rand(10))
|
||||
@test isa(p, Plots.Plot) == true
|
||||
@test isa(display(p), Nothing) == true
|
||||
p = bar(randn(10))
|
||||
@test isa(p, Plots.Plot) == true
|
||||
@test isa(display(p), Nothing) == true
|
||||
end
|
||||
|
||||
@testset "Axes" begin
|
||||
@@ -93,7 +61,6 @@ end
|
||||
for plt in plots
|
||||
display(plt)
|
||||
end
|
||||
@test_nowarn plot(x->x^2,0,2)
|
||||
end
|
||||
|
||||
@testset "EmptyAnim" begin
|
||||
@@ -130,6 +97,6 @@ end
|
||||
[(missing,missing)], [(missing,missing,missing),("a","b","c")])
|
||||
for z in zipped
|
||||
@test isequal(collect(zip(Plots.unzip(z)...)), z)
|
||||
@test isequal(collect(zip(Plots.unzip(GeometryTypes.Point.(z))...)), z)
|
||||
@test isequal(collect(zip(Plots.unzip(Point.(z))...)), z)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user