diff --git a/.gitignore b/.gitignore index 0c89a967..6e5cdbe6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ deps/build.log deps/deps.jl Manifest.toml dev/ +test/tmpplotsave.hdf5 diff --git a/.travis.yml b/.travis.yml index b8ae53eb..a4ca0ed6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ os: # - osx julia: - 1 - - 1.3 + - 1.4 - nightly matrix: @@ -32,7 +32,10 @@ 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.pin(PackageSpec(name="FixedPointNumbers", version="0.7")); Pkg.build(); Pkg.test(coverage=true)' + - $TESTCMD -e 'using Pkg; if VERSION == v"1.4"; Pkg.resolve(); end; Pkg.pin(PackageSpec(name="FixedPointNumbers", version="0.7")); Pkg.build(); Pkg.test(coverage=true)' diff --git a/Project.toml b/Project.toml index f03c1ed2..26d82018 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Plots" uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" author = ["Tom Breloff (@tbreloff)"] -version = "0.29.8" +version = "0.29.9" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" diff --git a/README.md b/README.md index 3c57fef1..ae3a3dd9 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ [![][pkgeval-img]][pkgeval-url] [![][gitter-img]][gitter-url] [![][docs-img]][docs-url] +[![Codecov](https://codecov.io/gh/JuliaPlots/Plots.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaPlots/Plots.jl) #### Created by Tom Breloff (@tbreloff) diff --git a/appveyor.yml b/appveyor.yml index 890f4960..7742efb2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ environment: matrix: # - julia_version: 0.7 - julia_version: 1 - - julia_version: 1.3 + - julia_version: 1.4 - julia_version: nightly platform: @@ -33,10 +33,10 @@ install: - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) build_script: - - C:\julia\bin\julia --project=C:\projects\plots-jl -e "using Pkg; Pkg.pin(PackageSpec(name="""FixedPointNumbers""", version="""0.7"""))" + - C:\julia\bin\julia --project=C:\projects\plots-jl -e "using Pkg; if VERSION == v"""1.4"""; Pkg.resolve(); end; Pkg.pin(PackageSpec(name="""FixedPointNumbers""", version="""0.7"""))" - echo "%JL_BUILD_SCRIPT%" - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" - + test_script: - echo "%JL_TEST_SCRIPT%" - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..79baa3c2 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,10 @@ +ignore: + - "src/backends/inspectdr.jl" + - "src/backends/orca.jl" + - "src/backends/pgfplots.jl" + - "src/backends/plotly.jl" + - "src/backends/plotlyjs.jl" + - "src/backends/pyplot.jl" + - "src/backends/web.jl" + - "src/fileio.jl" + - "src/ijulia.jl" diff --git a/src/args.jl b/src/args.jl index 82ca09d8..cebc5344 100644 --- a/src/args.jl +++ b/src/args.jl @@ -460,6 +460,29 @@ is_axis_attr_noletter(k) = haskey(_axis_defaults, k) RecipesBase.is_key_supported(k::Symbol) = is_attr_supported(k) +const _internal_args = + [:plot_object, :series_plotindex, :markershape_to_add, :letter, :idxfilter] +const _magic_axis_args = [:axis, :tickfont, :guidefont] +const _magic_subplot_args = [:titlefont, :legendfont, :legendtitlefont, ] +const _magic_series_args = [:line, :marker, :fill] + +function is_noletter_attribute(k) + is_axis_attr_noletter(k) && return true + k in _magic_axis_args && return true + return false +end + +function is_default_attribute(k) + k in _internal_args && return true + k in _all_args && return true + is_axis_attr(k) && return true + is_noletter_attribute(k) && return true + k in _magic_subplot_args && return true + k in _magic_series_args && return true + Symbol(chop(string(k); head = 1, tail = 0)) in _magic_axis_args && return true + return false +end + # ----------------------------------------------------------------------------- makeplural(s::Symbol) = Symbol(string(s,"s")) @@ -1017,6 +1040,16 @@ function preprocessArgs!(plotattributes::AKW) end end end + # handle axes guides + if haskey(plotattributes, :guide) + guide = pop!(plotattributes, :guide) + for letter in (:x, :y, :z) + guide_sym = Symbol(letter, :guide) + if !is_explicit(plotattributes, guide_sym) + plotattributes[guide_sym] = guide + end + end + end # handle line args for arg in wraptuple(pop_kw!(plotattributes, :line, ())) diff --git a/src/pipeline.jl b/src/pipeline.jl index 1e9cd991..ef846dd1 100644 --- a/src/pipeline.jl +++ b/src/pipeline.jl @@ -1,4 +1,26 @@ +# 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 +warn_on_recipe_aliases!(v::AbstractVector, recipe_type, args) = + foreach(x -> warn_on_recipe_aliases!(x, recipe_type, args), v) +warn_on_recipe_aliases!(rd::RecipeData, recipe_type, args) = + warn_on_recipe_aliases!(rd.plotattributes, recipe_type, args) +function signature_string(::Type{Val{:user}}, args...) + return string("(::", join(string.(typeof.(args)), ", ::"), ")") +end +signature_string(::Type{Val{:type}}, T) = "(::Type{$T}, ::$T)" +signature_string(::Type{Val{:plot}}, st) = "(::Type{Val{:$st}}, ::AbstractPlot)" +signature_string(::Type{Val{:series}}, st) = "(::Type{Val{:$st}}, x, y, z)" # ------------------------------------------------------------------ # preprocessing @@ -82,7 +104,11 @@ function _process_userrecipes(plt::Plot, plotattributes::AKW, args) if isempty(next_series.args) _process_userrecipe(plt, kw_list, next_series) else - rd_list = RecipesBase.apply_recipe(next_series.plotattributes, next_series.args...) + rd_list = RecipesBase.apply_recipe( + next_series.plotattributes, + next_series.args... + ) + warn_on_recipe_aliases!(rd_list, :user, next_series.args) prepend!(still_to_process,rd_list) end end @@ -184,6 +210,7 @@ function _process_plotrecipe(plt::Plot, kw::AKW, kw_list::Vector{KW}, still_to_p st = kw[:seriestype] st = kw[:seriestype] = get(_typeAliases, st, st) datalist = RecipesBase.apply_recipe(kw, Val{st}, plt) + warn_on_recipe_aliases!(datalist, :plot, st) for data in datalist preprocessArgs!(data.plotattributes) if data.plotattributes[:seriestype] == st @@ -408,7 +435,9 @@ function _process_seriesrecipe(plt::Plot, plotattributes::AKW) else # get a sub list of series for this seriestype - datalist = RecipesBase.apply_recipe(plotattributes, Val{st}, plotattributes[:x], plotattributes[:y], plotattributes[:z]) + x, y, z = plotattributes[:x], plotattributes[:y], plotattributes[:z] + datalist = RecipesBase.apply_recipe(plotattributes, Val{st}, x, y, z) + warn_on_recipe_aliases!(datalist, :series, st) # assuming there was no error, recursively apply the series recipes for data in datalist diff --git a/src/recipes.jl b/src/recipes.jl index dc94c1a9..79b4176d 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -1261,8 +1261,8 @@ end yflip := true aspect_ratio := 1 rs, cs, zs = findnz(z.surf) - xlim := ignorenan_extrema(cs) - ylim := ignorenan_extrema(rs) + xlims := ignorenan_extrema(cs) + ylims := ignorenan_extrema(rs) if plotattributes[:markershape] == :none markershape := :circle end diff --git a/src/series.jl b/src/series.jl index 2773b221..bbb49469 100644 --- a/src/series.jl +++ b/src/series.jl @@ -20,6 +20,7 @@ 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 @@ -171,9 +172,10 @@ end @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) - _handle_axis_args!(plotattributes) + _preprocess_axis_args!(plotattributes, letter) rdvec = RecipesBase.apply_recipe(plotattributes, typeof(v), v) - _handle_axis_args!(plotattributes, letter) + warn_on_recipe_aliases!(plotattributes, :type, typeof(v)) + _postprocess_axis_args!(plotattributes, letter) return rdvec[1].args[1] end @@ -181,15 +183,17 @@ end # 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) - _handle_axis_args!(plotattributes) + _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 - _handle_axis_args!(plotattributes, letter) + 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) @@ -197,7 +201,7 @@ function _apply_type_recipe(plotattributes, v::AbstractArray, letter) return v end end - _handle_axis_args!(plotattributes, letter) + _postprocess_axis_args!(plotattributes, letter) return w end @@ -218,29 +222,32 @@ end # end # don't do anything for ints or floats -_apply_type_recipe(plotattributes, v::AbstractArray{T}, letter) where {T<:Union{Integer,AbstractFloat}} = v +_apply_type_recipe(plotattributes, v::AbstractArray{<:DataPoint}, letter) = v -# axis args in type recipes should only be applied to the current axis -function _handle_axis_args!(plotattributes, letter) - if letter in (:x, :y, :z) - replaceAliases!(plotattributes, _keyAliases) - for (k, v) in plotattributes - if haskey(_axis_defaults, k) - pop!(plotattributes, k) - lk = Symbol(letter, k) +# axis args before type recipes should still be mapped to all axes +function _preprocess_axis_args!(plotattributes) + for (k, v) in plotattributes + if is_noletter_attribute(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 before type recipes should still be mapped to all axes -function _handle_axis_args!(plotattributes) - replaceAliases!(plotattributes, _keyAliases) - for (k, v) in plotattributes - if haskey(_axis_defaults, k) - pop!(plotattributes, k) - for letter in (:x, :y, :z) +# 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_noletter_attribute(k) + pop!(plotattributes, k) lk = Symbol(letter, k) haskey(plotattributes, lk) || (plotattributes[lk] = v) end diff --git a/src/types.jl b/src/types.jl index 4ee8e9e0..a866e59f 100644 --- a/src/types.jl +++ b/src/types.jl @@ -26,8 +26,9 @@ struct Attr <: AbstractDict{Symbol,Any} defaults::KW end -Base.getindex(attr::Attr, k) = haskey(attr.explicit,k) ? - attr.explicit[k] : attr.defaults[k] +function Base.getindex(attr::Attr, k) + return haskey(attr.explicit, k) ? attr.explicit[k] : attr.defaults[k] +end Base.haskey(attr::Attr, k) = haskey(attr.explicit,k) || haskey(attr.defaults,k) Base.get(attr::Attr, k, default) = haskey(attr, k) ? attr[k] : default function Base.get!(attr::Attr, k, default) @@ -41,7 +42,7 @@ end function Base.delete!(attr::Attr, k) haskey(attr.explicit, k) && delete!(attr.explicit, k) haskey(attr.defaults, k) && delete!(attr.defaults, k) -end +end Base.length(attr::Attr) = length(union(keys(attr.explicit), keys(attr.defaults))) function Base.iterate(attr::Attr) exp_keys = keys(attr.explicit) diff --git a/src/backends/template.jl b/templates/backends.jl similarity index 100% rename from src/backends/template.jl rename to templates/backends.jl