diff --git a/NEWS.md b/NEWS.md index 8113ae7b..8a7e19d7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,14 +11,23 @@ --- ## (current master) -## 0.12.2 +## 0.12.3 + +- new grid line style defaults +- `grid` is now an axis attribute and a magic argument: it is now possible to modify the grid line style, alpha and line width +- Enforce plot order in user recipes +- import `plot!` from RecipesBase +- GR no longer automatically handles _ and ^ in texts +- fix GR colorbar for scatter plots + +#### 0.12.2 - fix an issue with Juno/PlotlyJS compatibility on new installations - fix markers not showing up in seriesrecipes using :scatter - don't use pywrap in the pyplot backend - improve the bottom margin for the gr backend -## 0.12.1 +#### 0.12.1 - fix deprecation warnings - switch from FixedSizeArrays to StaticArrays.FixedSizeArrays diff --git a/README.md b/README.md index b88fe561..72a82007 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Plots [![Build Status](https://travis-ci.org/JuliaPlots/Plots.jl.svg?branch=master)](https://travis-ci.org/JuliaPlots/Plots.jl) +[![Build status](https://ci.appveyor.com/api/projects/status/github/tbreloff/plots.jl?branch=master&svg=true)](https://ci.appveyor.com/project/tbreloff/plots-jl) [![Join the chat at https://gitter.im/tbreloff/Plots.jl](https://badges.gitter.im/tbreloff/Plots.jl.svg)](https://gitter.im/tbreloff/Plots.jl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/REQUIRE b/REQUIRE index 6b96e23e..888aaf7d 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ -julia 0.6-pre +julia 0.6 RecipesBase 0.2.0 PlotUtils 0.4.1 diff --git a/appveyor.yml b/appveyor.yml index c0464e36..81d2e51f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,13 @@ environment: matrix: - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.5/julia-0.5-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" + - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe" + - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe" + +matrix: + allow_failures: + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" #check and address - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe" - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe" diff --git a/src/Plots.jl b/src/Plots.jl index e7c7fc66..c75a8e65 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -8,7 +8,7 @@ import StaticArrays using StaticArrays.FixedSizeArrays @reexport using RecipesBase -import RecipesBase: plot, animate +import RecipesBase: plot, plot!, animate using Base.Meta @reexport using PlotUtils @reexport using PlotThemes @@ -52,6 +52,8 @@ export yflip!, xaxis!, yaxis!, + xgrid!, + ygrid!, xlims, ylims, @@ -186,33 +188,65 @@ include("output.jl") @shorthands quiver @shorthands curves +"Plot a pie diagram" pie(args...; kw...) = plot(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing) pie!(args...; kw...) = plot!(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing) + +"Plot with seriestype :path3d" plot3d(args...; kw...) = plot(args...; kw..., seriestype = :path3d) plot3d!(args...; kw...) = plot!(args...; kw..., seriestype = :path3d) - +"Add title to an existing plot" title!(s::AbstractString; kw...) = plot!(; title = s, kw...) + +"Add xlabel to an existing plot" xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...) + +"Add ylabel to an existing plot" ylabel!(s::AbstractString; kw...) = plot!(; ylabel = s, kw...) + +"Set xlims for an existing plot" xlims!{T<:Real,S<:Real}(lims::Tuple{T,S}; kw...) = plot!(; xlims = lims, kw...) + +"Set ylims for an existing plot" ylims!{T<:Real,S<:Real}(lims::Tuple{T,S}; kw...) = plot!(; ylims = lims, kw...) + +"Set zlims for an existing plot" zlims!{T<:Real,S<:Real}(lims::Tuple{T,S}; kw...) = plot!(; zlims = lims, kw...) + xlims!(xmin::Real, xmax::Real; kw...) = plot!(; xlims = (xmin,xmax), kw...) ylims!(ymin::Real, ymax::Real; kw...) = plot!(; ylims = (ymin,ymax), kw...) zlims!(zmin::Real, zmax::Real; kw...) = plot!(; zlims = (zmin,zmax), kw...) + + +"Set xticks for an existing plot" xticks!{T<:Real}(v::AVec{T}; kw...) = plot!(; xticks = v, kw...) + +"Set yticks for an existing plot" yticks!{T<:Real}(v::AVec{T}; kw...) = plot!(; yticks = v, kw...) + xticks!{T<:Real,S<:AbstractString}( ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(; xticks = (ticks,labels), kw...) yticks!{T<:Real,S<:AbstractString}( ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(; yticks = (ticks,labels), kw...) + +"Add annotations to an existing plot" annotate!(anns...; kw...) = plot!(; annotation = anns, kw...) annotate!{T<:Tuple}(anns::AVec{T}; kw...) = plot!(; annotation = anns, kw...) + +"Flip the current plots' x axis" xflip!(flip::Bool = true; kw...) = plot!(; xflip = flip, kw...) + +"Flip the current plots' y axis" yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...) + +"Specify x axis attributes for an existing plot" xaxis!(args...; kw...) = plot!(; xaxis = args, kw...) + +"Specify x axis attributes for an existing plot" yaxis!(args...; kw...) = plot!(; yaxis = args, kw...) +xgrid!(args...; kw...) = plot!(; xgrid = args, kw...) +ygrid!(args...; kw...) = plot!(; ygrid = args, kw...) let PlotOrSubplot = Union{Plot, Subplot} title!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; title = s, kw...) @@ -230,6 +264,8 @@ let PlotOrSubplot = Union{Plot, Subplot} ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; xticks = (ticks,labels), kw...) yticks!{T<:Real,S<:AbstractString}(plt::PlotOrSubplot, ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; yticks = (ticks,labels), kw...) + xgrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xgrid = args, kw...) + ygrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; ygrid = args, kw...) annotate!(plt::PlotOrSubplot, anns...; kw...) = plot!(plt; annotation = anns, kw...) annotate!{T<:Tuple}(plt::PlotOrSubplot, anns::AVec{T}; kw...) = plot!(plt; annotation = anns, kw...) xflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; xflip = flip, kw...) diff --git a/src/animation.jl b/src/animation.jl index 55a9fe53..4afa01cf 100644 --- a/src/animation.jl +++ b/src/animation.jl @@ -1,4 +1,4 @@ - +"Represents an animation object" immutable Animation dir::String frames::Vector{String} @@ -9,6 +9,11 @@ function Animation() Animation(tmpdir, String[]) end +""" + frame(animation[, plot]) + +Add a plot (the current plot if not specified) to an existing animation +""" function frame{P<:AbstractPlot}(anim::Animation, plt::P=current()) i = length(anim.frames) + 1 filename = @sprintf("%06d.png", i) @@ -81,7 +86,7 @@ function buildanimation(animdir::AbstractString, fn::AbstractString; catch err warn("""Tried to create gif using convert (ImageMagick), but got error: $err ImageMagick can be installed by executing `Pkg.add("ImageMagick")`. - You may also need to install the imagemagick c++ library through your operating system. + You may also need to install the imagemagick c++ library through your operating system. Will try ffmpeg, but it's lower quality...)""") # low quality diff --git a/src/arg_desc.jl b/src/arg_desc.jl index f244ff99..8634eabb 100644 --- a/src/arg_desc.jl +++ b/src/arg_desc.jl @@ -21,7 +21,7 @@ const _arg_desc = KW( :markerstrokewidth => "Number. Width of the marker stroke (border. in pixels)", :markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:foreground_color_subplot`.", :markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.", -:bins => "Integer, NTuple{2,Integer}, AbstractVector or Symbol. Default is :auto. For histogram-types, defines the approximate number of bins to aim for, or the auto-binning algorithm to use (:sturges, :sqrt, :rice, :scott or :fd). For fine-grained control pass a Vector of break values, e.g. `linspace(extrema(x)..., 25)`", +:bins => "Integer, NTuple{2,Integer}, AbstractVector or Symbol. Default is :auto (the Freedman-Diaconis rule). For histogram-types, defines the approximate number of bins to aim for, or the auto-binning algorithm to use (:sturges, :sqrt, :rice, :scott or :fd). For fine-grained control pass a Vector of break values, e.g. `linspace(extrema(x)..., 25)`", :smooth => "Bool. Add a regression line?", :group => "AbstractVector. Data is split into a separate series, one for each unique value in `group`.", :x => "Various. Input data. First Dimension", @@ -40,7 +40,7 @@ const _arg_desc = KW( :ribbon => "Number or AbstractVector. Creates a fillrange around the data points.", :quiver => "AbstractVector or 2-Tuple of vectors. The directional vectors U,V which specify velocity/gradient vectors for a quiver plot.", :arrow => "nothing (no arrows), Bool (if true, default arrows), Arrow object, or arg(s) that could be style or head length/widths. Defines arrowheads that should be displayed at the end of path line segments (just before a NaN and the last non-NaN point). Used in quiverplot, streamplot, or similar.", -:normalize => "Bool or Symbol. Histogram normalization mode. Possible values are: false/:none (no normalization, default), true/:pdf (normalize to a PDF with integral of 1) and :density (only normalize in respect to bin sizes).", +:normalize => "Bool or Symbol. Histogram normalization mode. Possible values are: false/:none (no normalization, default), true/:pdf (normalize to a discrete Probability Density Function, where the total area of the bins is 1), :probability (bin heights sum to 1) and :density (the area of each bin, rather than the height, is equal to the counts - useful for uneven bin sizes).", :weights => "AbstractVector. Used in histogram types for weighted counts.", :contours => "Bool. Add contours to the side-grids of 3D plots? Used in surface/wireframe.", :match_dimensions => "Bool. For heatmap types... should the first dimension of a matrix (rows) correspond to the first dimension of the plot (x-axis)? The default is false, which matches the behavior of Matplotlib, Plotly, and others. Note: when passing a function for z, the function should still map `(x,y) -> z`.", @@ -76,7 +76,6 @@ const _arg_desc = KW( :background_color_inside => "Color Type or `:match` (matches `:background_color_subplot`). Background color inside the plot area (under the grid).", :foreground_color_subplot => "Color Type or `:match` (matches `:foreground_color`). Base foreground color of the subplot.", :foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.", -:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.", :foreground_color_title => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of subplot title.", :color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.", :legend => "Bool (show the legend?) or Symbol (legend position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` (note: only some may be supported in each backend)", @@ -84,7 +83,6 @@ const _arg_desc = KW( :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.", -:grid => "Bool. Show the grid lines?", :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 `apect_ratio` x-units.", @@ -95,6 +93,7 @@ const _arg_desc = KW( :bottom_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the bottom of the subplot.", :subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.", :colorbar_title => "String. Title of colorbar.", +:framestyle => "Symbol. Style of the axes frame. Choose from $(_allFramestyles)", # axis args :guide => "String. Axis guide (label).", @@ -111,5 +110,9 @@ const _arg_desc = KW( :foreground_color_text => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of tick labels.", :foreground_color_guide => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis guides (axis labels).", :mirror => "Bool. Switch the side of the tick labels (right or top).", - +:grid => "Bool, Symbol, String or `nothing`. Show the grid lines? `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:none`, `:off`", +:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.", +:gridalpha => "Number in [0,1]. The alpha/opacity override for the grid lines.", +:gridstyle => "Symbol. Style of the grid lines. Choose from $(_allStyles)", +:gridlinewidth => "Number. Width of the grid lines (in pixels)", ) diff --git a/src/args.jl b/src/args.jl index 223e7484..61c90133 100644 --- a/src/args.jl +++ b/src/args.jl @@ -80,9 +80,13 @@ const _typeAliases = Dict{Symbol,Symbol}( add_non_underscore_aliases!(_typeAliases) -like_histogram(seriestype::Symbol) = seriestype in (:histogram, :barhist, :barbins) -like_line(seriestype::Symbol) = seriestype in (:line, :path, :steppre, :steppost) -like_surface(seriestype::Symbol) = seriestype in (:contour, :contourf, :contour3d, :heatmap, :surface, :wireframe, :image) +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 is3d(seriestype::Symbol) = seriestype in _3dTypes is3d(series::Series) = is3d(series.d) @@ -163,6 +167,32 @@ const _scaleAliases = Dict{Symbol,Symbol}( :log => :log10, ) +const _allGridSyms = [:x, :y, :z, + :xy, :xz, :yx, :yz, :zx, :zy, + :xyz, :xzy, :yxz, :yzx, :zxy, :zyx, + :all, :both, :on, + :none, :off,] +const _allGridArgs = [_allGridSyms; string.(_allGridSyms); nothing] +hasgrid(arg::Void, letter) = false +hasgrid(arg::Bool, letter) = arg +function hasgrid(arg::Symbol, letter) + if arg in _allGridSyms + arg in (:all, :both, :on) || contains(string(arg), string(letter)) + else + warn("Unknown grid argument $arg; $(Symbol(letter, :grid)) was set to `true` instead.") + true + end +end +hasgrid(arg::AbstractString, letter) = hasgrid(Symbol(arg), letter) + +const _allFramestyles = [:box, :semi, :axes, :grid, :none] +const _framestyleAliases = Dict{Symbol, Symbol}( + :frame => :box, + :border => :box, + :on => :box, + :transparent => :semi, + :semitransparent => :semi, +) # ----------------------------------------------------------------------------- const _series_defaults = KW( @@ -172,7 +202,7 @@ const _series_defaults = KW( :seriestype => :path, :linestyle => :solid, :linewidth => :auto, - :linecolor => :match, + :linecolor => :auto, :linealpha => nothing, :fillrange => nothing, # ribbons, areas, etc :fillcolor => :match, @@ -248,7 +278,6 @@ const _subplot_defaults = KW( :background_color_inside => :match, # background inside grid :foreground_color_subplot => :match, # default for other fg colors... match takes plot default :foreground_color_legend => :match, # foreground of legend - :foreground_color_grid => :match, # grid color :foreground_color_title => :match, # title color :color_palette => :auto, :legend => :best, @@ -256,7 +285,6 @@ const _subplot_defaults = KW( :colorbar => :legend, :clims => :auto, :legendfont => font(8), - :grid => true, :annotations => [], # annotation tuples... list of (x,y,annotation) :projection => :none, # can also be :polar or :3d :aspect_ratio => :none, # choose from :none or :equal @@ -267,6 +295,7 @@ const _subplot_defaults = KW( :bottom_margin => :match, :subplot_index => -1, :colorbar_title => "", + :framestyle => :axes, ) const _axis_defaults = KW( @@ -286,6 +315,11 @@ const _axis_defaults = KW( :discrete_values => [], :formatter => :auto, :mirror => false, + :grid => true, + :foreground_color_grid => :match, # grid color + :gridalpha => 0.1, + :gridstyle => :solid, + :gridlinewidth => 0.5, ) const _suppress_warnings = Set{Symbol}([ @@ -403,7 +437,7 @@ add_aliases(:foreground_color_title, :fg_title, :fgtitle, :fgcolor_title, :fg_co add_aliases(:foreground_color_axis, :fg_axis, :fgaxis, :fgcolor_axis, :fg_color_axis, :foreground_axis, :foreground_colour_axis, :fgcolour_axis, :fg_colour_axis, :axiscolor) add_aliases(:foreground_color_border, :fg_border, :fgborder, :fgcolor_border, :fg_color_border, :foreground_border, - :foreground_colour_border, :fgcolour_border, :fg_colour_border, :bordercolor, :border) + :foreground_colour_border, :fgcolour_border, :fg_colour_border, :bordercolor) add_aliases(:foreground_color_text, :fg_text, :fgtext, :fgcolor_text, :fg_color_text, :foreground_text, :foreground_colour_text, :fgcolour_text, :fg_colour_text, :textcolor) add_aliases(:foreground_color_guide, :fg_guide, :fgguide, :fgcolor_guide, :fg_color_guide, :foreground_guide, @@ -415,6 +449,7 @@ add_aliases(:linealpha, :la, :lalpha, :lα, :lineopacity, :lopacity) add_aliases(:markeralpha, :ma, :malpha, :mα, :markeropacity, :mopacity) add_aliases(:markerstrokealpha, :msa, :msalpha, :msα, :markerstrokeopacity, :msopacity) add_aliases(:fillalpha, :fa, :falpha, :fα, :fillopacity, :fopacity) +add_aliases(:gridalpha, :ga, :galpha, :gα, :gridopacity, :gopacity) # series attributes add_aliases(:seriestype, :st, :t, :typ, :linetype, :lt) @@ -471,6 +506,9 @@ add_aliases(:html_output_format, :format, :fmt, :html_format) add_aliases(:orientation, :direction, :dir) add_aliases(:inset_subplots, :inset, :floating) add_aliases(:stride, :wirefame_stride, :surface_stride, :surf_str, :str) +add_aliases(:gridlinewidth, :gridwidth, :grid_linewidth, :grid_width, :gridlw, :grid_lw) +add_aliases(:gridstyle, :grid_style, :gridlinestyle, :grid_linestyle, :grid_ls, :gridls) +add_aliases(:framestyle, :frame_style, :frame, :axesstyle, :axes_style, :boxstyle, :box_style, :box, :borderstyle, :border_style, :border) # add all pluralized forms to the _keyAliases dict @@ -490,7 +528,6 @@ end `default(; kw...)` will set the current default value for each key/value pair `default(d, key)` returns the key from d if it exists, otherwise `default(key)` """ - function default(k::Symbol) k = get(_keyAliases, k, k) for defaults in _all_defaults @@ -647,6 +684,36 @@ function processFillArg(d::KW, arg) return end + +function processGridArg!(d::KW, arg, letter) + if arg in _allGridArgs || isa(arg, Bool) + d[Symbol(letter, :grid)] = hasgrid(arg, letter) + + elseif allStyles(arg) + d[Symbol(letter, :gridstyle)] = arg + + elseif typeof(arg) <: Stroke + arg.width == nothing || (d[Symbol(letter, :gridlinewidth)] = arg.width) + arg.color == nothing || (d[Symbol(letter, :foreground_color_grid)] = arg.color in (:auto, :match) ? :match : plot_color(arg.color)) + arg.alpha == nothing || (d[Symbol(letter, :gridalpha)] = arg.alpha) + arg.style == nothing || (d[Symbol(letter, :gridstyle)] = arg.style) + + # linealpha + elseif allAlphas(arg) + d[Symbol(letter, :gridalpha)] = arg + + # linewidth + elseif allReals(arg) + d[Symbol(letter, :gridlinewidth)] = arg + + # color +elseif !handleColors!(d, arg, Symbol(letter, :foreground_color_grid)) + warn("Skipped grid arg $arg.") + + end +end + + _replace_markershape(shape::Symbol) = get(_markerAliases, shape, shape) _replace_markershape(shapes::AVec) = map(_replace_markershape, shapes) _replace_markershape(shape) = shape @@ -668,6 +735,7 @@ function preprocessArgs!(d::KW) if haskey(d, :axis) && d[:axis] in (:none, nothing, false) d[:ticks] = nothing d[:foreground_color_border] = RGBA(0,0,0,0) + d[:foreground_color_axis] = RGBA(0,0,0,0) d[:grid] = false delete!(d, :axis) end @@ -690,6 +758,22 @@ function preprocessArgs!(d::KW) end end + # handle grid args common to all axes + args = pop!(d, :grid, ()) + for arg in wraptuple(args) + for letter in (:x, :y, :z) + processGridArg!(d, arg, letter) + end + end + # handle individual axes grid args + for letter in (:x, :y, :z) + gridsym = Symbol(letter, :grid) + args = pop!(d, gridsym, ()) + for arg in wraptuple(args) + processGridArg!(d, arg, letter) + end + end + # handle line args for arg in wraptuple(pop!(d, :line, ())) processLineArg(d, arg) @@ -754,6 +838,11 @@ function preprocessArgs!(d::KW) d[:colorbar] = convertLegendValue(d[:colorbar]) end + # framestyle + if haskey(d, :framestyle) && haskey(_framestyleAliases, d[:framestyle]) + d[:framestyle] = _framestyleAliases[d[:framestyle]] + end + # warnings for moved recipes st = get(d, :seriestype, :path) if st in (:boxplot, :violin, :density) && !isdefined(Main, :StatPlots) @@ -773,21 +862,29 @@ end # this is when given a vector-type of values to group by -function extractGroupArgs(v::AVec, args...) +function extractGroupArgs(v::AVec, args...; legendEntry = string) groupLabels = sort(collect(unique(v))) n = length(groupLabels) if n > 100 warn("You created n=$n groups... Is that intended?") end groupIds = Vector{Int}[filter(i -> v[i] == glab, 1:length(v)) for glab in groupLabels] - GroupBy(map(string, groupLabels), groupIds) + GroupBy(map(legendEntry, groupLabels), groupIds) end +legendEntryFromTuple(ns::Tuple) = string(("$n " for n in ns)...) + +# this is when given a tuple of vectors of values to group by +function extractGroupArgs(vs::Tuple, args...) + (vs == ()) && return GroupBy([""], [1:size(args[1],1)]) + v = collect(zip(vs...)) + extractGroupArgs(v, args...; legendEntry = legendEntryFromTuple) +end # expecting a mapping of "group label" to "group indices" function extractGroupArgs{T, V<:AVec{Int}}(idxmap::Dict{T,V}, args...) groupLabels = sortedkeys(idxmap) - groupIds = VecI[collect(idxmap[k]) for k in groupLabels] + groupIds = Vector{Int}[collect(idxmap[k]) for k in groupLabels] GroupBy(groupLabels, groupIds) end @@ -945,7 +1042,6 @@ const _match_map = KW( :background_color_legend => :background_color_subplot, :background_color_inside => :background_color_subplot, :foreground_color_legend => :foreground_color_subplot, - :foreground_color_grid => :foreground_color_subplot, :foreground_color_title => :foreground_color_subplot, :left_margin => :margin, :top_margin => :margin, @@ -959,6 +1055,7 @@ const _match_map2 = KW( :foreground_color_subplot => :foreground_color, :foreground_color_axis => :foreground_color_subplot, :foreground_color_border => :foreground_color_subplot, + :foreground_color_grid => :foreground_color_subplot, :foreground_color_guide => :foreground_color_subplot, :foreground_color_text => :foreground_color_subplot, ) @@ -1093,7 +1190,6 @@ function _update_subplot_colors(sp::Subplot) # foreground colors color_or_nothing!(sp.attr, :foreground_color_subplot) color_or_nothing!(sp.attr, :foreground_color_legend) - color_or_nothing!(sp.attr, :foreground_color_grid) color_or_nothing!(sp.attr, :foreground_color_title) return end @@ -1145,6 +1241,7 @@ function _update_axis_colors(axis::Axis) color_or_nothing!(axis.d, :foreground_color_border) color_or_nothing!(axis.d, :foreground_color_guide) color_or_nothing!(axis.d, :foreground_color_text) + color_or_nothing!(axis.d, :foreground_color_grid) return end @@ -1250,12 +1347,14 @@ function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int) # update other colors for s in (:line, :marker, :fill) csym, asym = Symbol(s,:color), Symbol(s,:alpha) - d[csym] = if d[csym] == :match + d[csym] = if d[csym] == :auto plot_color(if has_black_border_for_default(d[:seriestype]) && s == :line sp[:foreground_color_subplot] else d[:seriescolor] end, d[asym]) + elseif d[csym] == :match + plot_color(d[:seriescolor], d[asym]) else getSeriesRGBColor(d[csym], d[asym], sp, plotIndex) end diff --git a/src/axes.jl b/src/axes.jl index e53d3ccb..850b67cb 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -181,15 +181,27 @@ function optimal_ticks_and_labels(axis::Axis, ticks = nothing) end # get a list of well-laid-out ticks - scaled_ticks = if ticks == nothing - optimize_ticks( + if ticks == nothing + scaled_ticks = optimize_ticks( sf(amin), sf(amax); - k_min = 5, # minimum number of ticks + k_min = 4, # minimum number of ticks k_max = 8, # maximum number of ticks )[1] + elseif typeof(ticks) <: Int + scaled_ticks, viewmin, viewmax = optimize_ticks( + sf(amin), + sf(amax); + k_min = ticks, # minimum number of ticks + k_max = ticks, # maximum number of ticks + k_ideal = ticks, + # `strict_span = false` rewards cases where the span of the + # chosen ticks is not too much bigger than amin - amax: + strict_span = false, + ) + axis[:lims] = map(invscalefunc(scale), (viewmin, viewmax)) else - map(sf, filter(t -> amin <= t <= amax, ticks)) + scaled_ticks = map(sf, (filter(t -> amin <= t <= amax, ticks))) end unscaled_ticks = map(invscalefunc(scale), scaled_ticks) @@ -216,7 +228,7 @@ end # return (continuous_values, discrete_values) for the ticks on this axis function get_ticks(axis::Axis) - ticks = axis[:ticks] + ticks = _transform_ticks(axis[:ticks]) ticks in (nothing, false) && return nothing dvals = axis[:discrete_values] @@ -226,7 +238,7 @@ function get_ticks(axis::Axis) elseif ticks == :auto # compute optimal ticks and labels optimal_ticks_and_labels(axis) - elseif typeof(ticks) <: AVec + elseif typeof(ticks) <: Union{AVec, Int} # override ticks, but get the labels optimal_ticks_and_labels(axis, ticks) elseif typeof(ticks) <: NTuple{2, Any} @@ -246,6 +258,10 @@ function get_ticks(axis::Axis) end end +_transform_ticks(ticks) = ticks +_transform_ticks(ticks::AbstractArray{T}) where T <: Dates.TimeType = Dates.value.(ticks) +_transform_ticks(ticks::NTuple{2, Any}) = (_transform_ticks(ticks[1]), ticks[2]) + # ------------------------------------------------------------------------- @@ -490,38 +506,46 @@ function axis_drawing_info(sp::Subplot) ymin, ymax = axis_limits(yaxis) xticks = get_ticks(xaxis) yticks = get_ticks(yaxis) - spine_segs = Segments(2) - grid_segs = Segments(2) + xaxis_segs = Segments(2) + yaxis_segs = Segments(2) + xgrid_segs = Segments(2) + ygrid_segs = Segments(2) + xborder_segs = Segments(2) + yborder_segs = Segments(2) - if !(xaxis[:ticks] in (nothing, false)) - f = scalefunc(yaxis[:scale]) - invf = invscalefunc(yaxis[:scale]) - t1 = invf(f(ymin) + 0.015*(f(ymax)-f(ymin))) - t2 = invf(f(ymax) - 0.015*(f(ymax)-f(ymin))) + if !(sp[:framestyle] == :none) + # xaxis + sp[:framestyle] == :grid || push!(xaxis_segs, (xmin,ymin), (xmax,ymin)) # bottom spine / xaxis + sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin,ymax), (xmax,ymax)) # top spine + if !(xaxis[:ticks] in (nothing, false)) + f = scalefunc(yaxis[:scale]) + invf = invscalefunc(yaxis[:scale]) + t1 = invf(f(ymin) + 0.015*(f(ymax)-f(ymin))) + t2 = invf(f(ymax) - 0.015*(f(ymax)-f(ymin))) - push!(spine_segs, (xmin,ymin), (xmax,ymin)) # bottom spine - # push!(spine_segs, (xmin,ymax), (xmax,ymax)) # top spine - for xtick in xticks[1] - push!(spine_segs, (xtick, ymin), (xtick, t1)) # bottom tick - push!(grid_segs, (xtick, t1), (xtick, t2)) # vertical grid - # push!(spine_segs, (xtick, ymax), (xtick, t2)) # top tick + for xtick in xticks[1] + push!(xaxis_segs, (xtick, ymin), (xtick, t1)) # bottom tick + # sp[:draw_axes_border] && push!(xaxis_segs, (xtick, ymax), (xtick, t2)) # top tick + xaxis[:grid] && push!(xgrid_segs, (xtick, t1), (xtick, t2)) # vertical grid + end + end + + # yaxis + sp[:framestyle] == :grid || push!(yaxis_segs, (xmin,ymin), (xmin,ymax)) # left spine / yaxis + sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (xmax,ymin), (xmax,ymax)) # right spine + if !(yaxis[:ticks] in (nothing, false)) + f = scalefunc(xaxis[:scale]) + invf = invscalefunc(xaxis[:scale]) + t1 = invf(f(xmin) + 0.015*(f(xmax)-f(xmin))) + t2 = invf(f(xmax) - 0.015*(f(xmax)-f(xmin))) + + for ytick in yticks[1] + push!(yaxis_segs, (xmin, ytick), (t1, ytick)) # left tick + # sp[:draw_axes_border] && push!(yaxis_segs, (xmax, ytick), (t2, ytick)) # right tick + yaxis[:grid] && push!(ygrid_segs, (t1, ytick), (t2, ytick)) # horizontal grid + end end end - if !(yaxis[:ticks] in (nothing, false)) - f = scalefunc(xaxis[:scale]) - invf = invscalefunc(xaxis[:scale]) - t1 = invf(f(xmin) + 0.015*(f(xmax)-f(xmin))) - t2 = invf(f(xmax) - 0.015*(f(xmax)-f(xmin))) - - push!(spine_segs, (xmin,ymin), (xmin,ymax)) # left spine - # push!(spine_segs, (xmax,ymin), (xmax,ymax)) # right spine - for ytick in yticks[1] - push!(spine_segs, (xmin, ytick), (t1, ytick)) # left tick - push!(grid_segs, (t1, ytick), (t2, ytick)) # horizontal grid - # push!(spine_segs, (xmax, ytick), (t2, ytick)) # right tick - end - end - - xticks, yticks, spine_segs, grid_segs + xticks, yticks, xaxis_segs, yaxis_segs, xgrid_segs, ygrid_segs, xborder_segs, yborder_segs end diff --git a/src/backends.jl b/src/backends.jl index 19a9c6ea..b445807c 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -6,7 +6,10 @@ const _backendSymbol = Dict{DataType, Symbol}(NoBackend => :none) const _backends = Symbol[] const _initialized_backends = Set{Symbol}() +"Returns a list of supported backends" backends() = _backends + +"Returns the name of the current backend" backend_name() = CURRENT_BACKEND.sym _backend_instance(sym::Symbol) = haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym") diff --git a/src/backends/glvisualize.jl b/src/backends/glvisualize.jl index 3484131b..974b73b1 100644 --- a/src/backends/glvisualize.jl +++ b/src/backends/glvisualize.jl @@ -24,7 +24,8 @@ const _glvisualize_attr = merge_with_base_supported([ :window_title, :guide, :lims, :ticks, :scale, :flip, :rotation, :tickfont, :guidefont, :legendfont, - :grid, :legend, :colorbar, + :grid, :gridalpha, :gridstyle, :gridlinewidth, + :legend, :colorbar, :marker_z, :line_z, :levels, @@ -38,7 +39,8 @@ const _glvisualize_attr = merge_with_base_supported([ :clims, :inset_subplots, :dpi, - :hover + :hover, + :framestyle, ]) const _glvisualize_seriestype = [ :path, :shape, @@ -676,17 +678,29 @@ function text_model(font, pivot) end end function gl_draw_axes_2d(sp::Plots.Subplot{Plots.GLVisualizeBackend}, model, area) - xticks, yticks, spine_segs, grid_segs = Plots.axis_drawing_info(sp) + xticks, yticks, xspine_segs, yspine_segs, xgrid_segs, ygrid_segs, xborder_segs, yborder_segs = Plots.axis_drawing_info(sp) xaxis = sp[:xaxis]; yaxis = sp[:yaxis] - c = Colors.color(Plots.gl_color(sp[:foreground_color_grid])) + xgc = Colors.color(Plots.gl_color(xaxis[:foreground_color_grid])) + ygc = Colors.color(Plots.gl_color(yaxis[:foreground_color_grid])) axis_vis = [] - if sp[:grid] - grid = draw_grid_lines(sp, grid_segs, 1f0, :dot, model, RGBA(c, 0.3f0)) + if xaxis[:grid] + grid = draw_grid_lines(sp, xgrid_segs, xaxis[:gridlinewidth], xaxis[:gridstyle], model, RGBA(xgc, xaxis[:gridalpha])) push!(axis_vis, grid) end - if alpha(xaxis[:foreground_color_border]) > 0 - spine = draw_grid_lines(sp, spine_segs, 1f0, :solid, model, RGBA(c, 1.0f0)) + if yaxis[:grid] + grid = draw_grid_lines(sp, ygrid_segs, yaxis[:gridlinewidth], yaxis[:gridstyle], model, RGBA(ygc, yaxis[:gridalpha])) + push!(axis_vis, grid) + end + + xac = Colors.color(Plots.gl_color(xaxis[:foreground_color_axis])) + yac = Colors.color(Plots.gl_color(yaxis[:foreground_color_axis])) + if alpha(xaxis[:foreground_color_axis]) > 0 + spine = draw_grid_lines(sp, xspine_segs, 1f0, :solid, model, RGBA(xac, 1.0f0)) + push!(axis_vis, spine) + end + if alpha(yaxis[:foreground_color_axis]) > 0 + spine = draw_grid_lines(sp, yspine_segs, 1f0, :solid, model, RGBA(yac, 1.0f0)) push!(axis_vis, spine) end fcolor = Plots.gl_color(xaxis[:foreground_color_axis]) @@ -694,7 +708,7 @@ function gl_draw_axes_2d(sp::Plots.Subplot{Plots.GLVisualizeBackend}, model, are xlim = Plots.axis_limits(xaxis) ylim = Plots.axis_limits(yaxis) - if !(xaxis[:ticks] in (nothing, false, :none)) + if !(xaxis[:ticks] in (nothing, false, :none)) && !(sp[:framestyle] == :none) ticklabels = map(model) do m mirror = xaxis[:mirror] t, positions, offsets = draw_ticks(xaxis, xticks, true, ylim, m) @@ -714,6 +728,15 @@ function gl_draw_axes_2d(sp::Plots.Subplot{Plots.GLVisualizeBackend}, model, are push!(axis_vis, visualize(map(first, ticklabels), Style(:default), kw_args)) end + xbc = Colors.color(Plots.gl_color(xaxis[:foreground_color_border])) + ybc = Colors.color(Plots.gl_color(yaxis[:foreground_color_border])) + intensity = sp[:framestyle] == :semi ? 0.5f0 : 1.0f0 + if sp[:framestyle] in (:box, :semi) + xborder = draw_grid_lines(sp, xborder_segs, intensity, :solid, model, RGBA(xbc, intensity)) + yborder = draw_grid_lines(sp, yborder_segs, intensity, :solid, model, RGBA(ybc, intensity)) + push!(axis_vis, xborder, yborder) + end + area_w = GeometryTypes.widths(area) if sp[:title] != "" tf = sp[:titlefont]; color = gl_color(sp[:foreground_color_title]) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 80b52822..76faa6fd 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -20,7 +20,8 @@ const _gr_attr = merge_with_base_supported([ :title, :window_title, :guide, :lims, :ticks, :scale, :flip, :tickfont, :guidefont, :legendfont, - :grid, :legend, :legendtitle, :colorbar, + :grid, :gridalpha, :gridstyle, :gridlinewidth, + :legend, :legendtitle, :colorbar, :marker_z, :levels, :ribbon, :quiver, :orientation, @@ -31,6 +32,7 @@ const _gr_attr = merge_with_base_supported([ :inset_subplots, :bar_width, :arrow, + :framestyle, ]) const _gr_seriestype = [ :path, :scatter, @@ -325,7 +327,10 @@ function gr_draw_markers(series::Series, x, y, msize, mz) cfuncind(ci) GR.settransparency(_gr_gradient_alpha[ci-999]) end - gr_draw_marker(x[i], y[i], msi, shape) + # don't draw filled area if marker shape is 1D + if !(shape in (:hline, :vline, :+, :x)) + gr_draw_marker(x[i], y[i], msi, shape) + end end end end @@ -336,6 +341,7 @@ function gr_draw_markers(series::Series, x, y) GR.setfillintstyle(GR.INTSTYLE_SOLID) gr_draw_markers(series, x, y, series[:markersize], mz) if mz != nothing + GR.setscale(0) gr_colorbar(series[:subplot]) end end @@ -537,40 +543,91 @@ function gr_display(plt::Plot) end +function gr_set_xticks_font(sp) + flip = sp[:yaxis][:flip] + mirror = sp[:xaxis][:mirror] + gr_set_font(sp[:xaxis][:tickfont], + halign = (:left, :hcenter, :right)[sign(sp[:xaxis][:rotation]) + 2], + valign = (mirror ? :bottom : :top), + color = sp[:xaxis][:foreground_color_axis], + rotation = sp[:xaxis][:rotation]) + return flip, mirror +end + + +function gr_set_yticks_font(sp) + flip = sp[:xaxis][:flip] + mirror = sp[:yaxis][:mirror] + gr_set_font(sp[:yaxis][:tickfont], + halign = (mirror ? :left : :right), + valign = (:top, :vcenter, :bottom)[sign(sp[:yaxis][:rotation]) + 2], + color = sp[:yaxis][:foreground_color_axis], + rotation = sp[:yaxis][:rotation]) + return flip, mirror +end + +function gr_get_ticks_size(ticks, i) + GR.savestate() + GR.selntran(0) + l = 0.0 + for (cv, dv) in zip(ticks...) + tb = gr_inqtext(0, 0, string(dv))[i] + tb_min, tb_max = extrema(tb) + l = max(l, tb_max - tb_min) + end + GR.restorestate() + return l +end + function _update_min_padding!(sp::Subplot{GRBackend}) - leftpad = 10mm - toppad = 2mm - rightpad = 2mm - bottompad = 6mm + if !haskey(ENV, "GKSwstype") + if isijulia() || (isdefined(Main, :Juno) && Juno.isactive()) + ENV["GKSwstype"] = "svg" + end + end + # Add margin given by the user + leftpad = 2mm + sp[:left_margin] + toppad = 2mm + sp[:top_margin] + rightpad = 4mm + sp[:right_margin] + bottompad = 2mm + sp[:bottom_margin] + # Add margin for title if sp[:title] != "" toppad += 5mm end - if sp[:xaxis][:guide] != "" - xticks = axis_drawing_info(sp)[1] - if !(xticks in (nothing, false)) - gr_set_font(sp[:xaxis][:tickfont], - halign = (:left, :hcenter, :right)[sign(sp[:xaxis][:rotation]) + 2], - valign = :top, - color = sp[:xaxis][:foreground_color_axis], - rotation = sp[:xaxis][:rotation]) - h = 0 - for (cv, dv) in zip(xticks...) - tbx, tby = gr_inqtext(0, 0, string(dv)) - h = max(h, tby[2] - tby[1]) - end - bottompad += 1mm + gr_plot_size[2] * h * px + # Add margin for x and y ticks + xticks, yticks = axis_drawing_info(sp)[1:2] + if !(xticks in (nothing, false)) + flip, mirror = gr_set_xticks_font(sp) + l = gr_get_ticks_size(xticks, 2) + if mirror + toppad += 1mm + gr_plot_size[2] * l * px else - bottompad += 4mm + bottompad += 1mm + gr_plot_size[2] * l * px end end + if !(yticks in (nothing, false)) + flip, mirror = gr_set_yticks_font(sp) + l = gr_get_ticks_size(yticks, 1) + if mirror + rightpad += 1mm + gr_plot_size[1] * l * px + else + leftpad += 1mm + gr_plot_size[1] * l * px + end + end + # Add margin for x label + if sp[:xaxis][:guide] != "" + bottompad += 4mm + end + # Add margin for y label if sp[:yaxis][:guide] != "" leftpad += 4mm end sp.minpad = (leftpad, toppad, rightpad, bottompad) end - function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) + _update_min_padding!(sp) + # the viewports for this subplot viewport_subplot = gr_viewport_from_bbox(sp, bbox(sp), w, h, viewport_canvas) viewport_plotarea[:] = gr_viewport_from_bbox(sp, plotarea(sp), w, h, viewport_canvas) @@ -606,7 +663,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) # TODO: can these be generic flags? outside_ticks = false cmap = false - draw_axes = true + draw_axes = sp[:framestyle] != :none # axes_2d = true for series in series_list(sp) st = series[:seriestype] @@ -691,10 +748,9 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) ticksize = 0.01 * (viewport_plotarea[2] - viewport_plotarea[1]) # GR.setlinetype(GR.LINETYPE_DOTTED) - if sp[:grid] - GR.grid3d(xtick, 0, ztick, xmin, ymax, zmin, 2, 0, 2) - GR.grid3d(0, ytick, 0, xmin, ymax, zmin, 0, 2, 0) - end + xaxis[:grid] && GR.grid3d(xtick, 0, 0, xmin, ymax, zmin, 2, 0, 0) + yaxis[:grid] && GR.grid3d(0, ytick, 0, xmin, ymax, zmin, 0, 2, 0) + zaxis[:grid] && GR.grid3d(0, 0, ztick, xmin, ymax, zmin, 0, 0, 2) GR.axes3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2, -ticksize) GR.axes3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0, ticksize) @@ -709,34 +765,37 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) GR.setwindow(xmin, xmax, ymin, ymax) end - xticks, yticks, spine_segs, grid_segs = axis_drawing_info(sp) + xticks, yticks, xspine_segs, yspine_segs, xgrid_segs, ygrid_segs, xborder_segs, yborder_segs = axis_drawing_info(sp) # @show xticks yticks #spine_segs grid_segs # draw the grid lines - if sp[:grid] + if xaxis[:grid] # gr_set_linecolor(sp[:foreground_color_grid]) # GR.grid(xtick, ytick, 0, 0, majorx, majory) - gr_set_line(1, :dot, sp[:foreground_color_grid]) - GR.settransparency(0.5) - gr_polyline(coords(grid_segs)...) + gr_set_line(xaxis[:gridlinewidth], xaxis[:gridstyle], xaxis[:foreground_color_grid]) + GR.settransparency(xaxis[:gridalpha]) + gr_polyline(coords(xgrid_segs)...) + end + if yaxis[:grid] + gr_set_line(yaxis[:gridlinewidth], yaxis[:gridstyle], yaxis[:foreground_color_grid]) + GR.settransparency(yaxis[:gridalpha]) + gr_polyline(coords(ygrid_segs)...) end GR.settransparency(1.0) - # spine (border) and tick marks - gr_set_line(1, :solid, sp[:xaxis][:foreground_color_axis]) + # axis lines + gr_set_line(1, :solid, xaxis[:foreground_color_axis]) GR.setclip(0) - gr_polyline(coords(spine_segs)...) + gr_polyline(coords(xspine_segs)...) + gr_set_line(1, :solid, yaxis[:foreground_color_axis]) + GR.setclip(0) + gr_polyline(coords(yspine_segs)...) GR.setclip(1) - if !(xticks in (nothing, false)) + # tick marks + if !(xticks in (:none, nothing, false)) # x labels - flip = sp[:yaxis][:flip] - mirror = sp[:xaxis][:mirror] - gr_set_font(sp[:xaxis][:tickfont], - halign = (:left, :hcenter, :right)[sign(sp[:xaxis][:rotation]) + 2], - valign = (mirror ? :bottom : :top), - color = sp[:xaxis][:foreground_color_axis], - rotation = sp[:xaxis][:rotation]) + flip, mirror = gr_set_xticks_font(sp) for (cv, dv) in zip(xticks...) # use xor ($) to get the right y coords xi, yi = GR.wctondc(cv, xor(flip, mirror) ? ymax : ymin) @@ -745,15 +804,9 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) end end - if !(yticks in (nothing, false)) + if !(yticks in (:none, nothing, false)) # y labels - flip = sp[:xaxis][:flip] - mirror = sp[:yaxis][:mirror] - gr_set_font(sp[:yaxis][:tickfont], - halign = (mirror ? :left : :right), - valign = (:top, :vcenter, :bottom)[sign(sp[:yaxis][:rotation]) + 2], - color = sp[:yaxis][:foreground_color_axis], - rotation = sp[:yaxis][:rotation]) + flip, mirror = gr_set_yticks_font(sp) for (cv, dv) in zip(yticks...) # use xor ($) to get the right y coords xi, yi = GR.wctondc(xor(flip, mirror) ? xmax : xmin, cv) @@ -761,6 +814,17 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) gr_text(xi + (mirror ? 1 : -1) * 1e-2, yi, string(dv)) end end + + # border + intensity = sp[:framestyle] == :semi ? 0.5 : 1.0 + if sp[:framestyle] in (:box, :semi) + gr_set_line(intensity, :solid, xaxis[:foreground_color_border]) + GR.settransparency(intensity) + gr_polyline(coords(xborder_segs)...) + gr_set_line(intensity, :solid, yaxis[:foreground_color_border]) + GR.settransparency(intensity) + gr_polyline(coords(yborder_segs)...) + end end # end @@ -863,7 +927,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if series[:marker_z] != nothing zmin, zmax = extrema(series[:marker_z]) GR.setspace(zmin, zmax, 0, 90) - GR.setscale(0) end gr_draw_markers(series, x, y) end @@ -1088,7 +1151,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) st = series[:seriestype] gr_set_line(series[:linewidth], series[:linestyle], series[:linecolor]) #, series[:linealpha]) - if st == :shape || series[:fillrange] != nothing + if (st == :shape || series[:fillrange] != nothing) && series[:ribbon] == nothing gr_set_fill(series[:fillcolor]) #, series[:fillalpha]) l, r = xpos-0.07, xpos-0.01 b, t = ypos-0.4dy, ypos+0.4dy diff --git a/src/backends/inspectdr.jl b/src/backends/inspectdr.jl index d7ef859c..a8224634 100644 --- a/src/backends/inspectdr.jl +++ b/src/backends/inspectdr.jl @@ -18,7 +18,8 @@ Add in functionality to Plots.jl: const _inspectdr_attr = merge_with_base_supported([ :annotations, :background_color_legend, :background_color_inside, :background_color_outside, - :foreground_color_grid, :foreground_color_legend, :foreground_color_title, + # :foreground_color_grid, + :foreground_color_legend, :foreground_color_title, :foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text, :label, :linecolor, :linestyle, :linewidth, :linealpha, @@ -334,15 +335,18 @@ end # --------------------------------------------------------------------------- function _inspectdr_setupsubplot(sp::Subplot{InspectDRBackend}) - const gridon = InspectDR.GridRect(vmajor=true, hmajor=true) - const gridoff = InspectDR.GridRect() const plot = sp.o const strip = plot.strips[1] #Only 1 strip supported with Plots.jl - #No independent control of grid??? - strip.grid = sp[:grid]? gridon: gridoff - xaxis = sp[:xaxis]; yaxis = sp[:yaxis] + xgrid_show = xaxis[:grid] + ygrid_show = yaxis[:grid] + + strip.grid = InspectDR.GridRect( + vmajor=xgrid_show, # vminor=xgrid_show, + hmajor=ygrid_show, # hminor=ygrid_show, + ) + plot.xscale = _inspectdr_getscale(xaxis[:scale], false) strip.yscale = _inspectdr_getscale(yaxis[:scale], true) xmin, xmax = axis_limits(xaxis) diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index c44c10ae..30169147 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -98,7 +98,7 @@ const _pgf_series_extrastyle = KW( :xsticks => "xcomb", ) -# PGFPlots uses the anchors to define orientations for example to align left +# PGFPlots uses the anchors to define orientations for example to align left # one needs to use the right edge as anchor const _pgf_annotation_halign = KW( :center => "", @@ -121,7 +121,7 @@ function pgf_color(grad::ColorGradient) end # Generates a colormap for pgfplots based on a ColorGradient -function pgf_colormap(grad::ColorGradient) +function pgf_colormap(grad::ColorGradient) join(map(grad.colors) do c @sprintf("rgb=(%.8f,%.8f,%.8f)", red(c), green(c),blue(c)) end,", ") @@ -266,6 +266,11 @@ function pgf_axis(sp::Subplot, letter) push!(style, "$(letter)majorticks=false") end + # grid on or off + if axis[:grid] + push!(style, "$(letter)majorgrids = true") + end + # limits # TODO: support zlims if letter != :z @@ -324,7 +329,6 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) kw[:title] = "$(sp[:title])" end - sp[:grid] && push!(style, "grid = major") if sp[:aspect_ratio] in (1, :equal) kw[:axisEqual] = "true" end @@ -360,7 +364,7 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) kw[:colorbar] = "true" end # goto is needed to break out of col and series for - @goto colorbar_end + @goto colorbar_end end end end diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index 9f764f4c..1bd9f096 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -5,7 +5,7 @@ const _plotly_attr = merge_with_base_supported([ :annotations, :background_color_legend, :background_color_inside, :background_color_outside, :foreground_color_legend, :foreground_color_guide, - # :foreground_color_grid, :foreground_color_axis, + :foreground_color_grid, :foreground_color_axis, :foreground_color_text, :foreground_color_border, :foreground_color_title, :label, @@ -19,7 +19,8 @@ const _plotly_attr = merge_with_base_supported([ :window_title, :guide, :lims, :ticks, :scale, :flip, :rotation, :tickfont, :guidefont, :legendfont, - :grid, :legend, :colorbar, :colorbar_title, + :grid, :gridalpha, :gridlinewidth, + :legend, :colorbar, :colorbar_title, :marker_z, :fill_z, :levels, :ribbon, :quiver, :orientation, @@ -213,7 +214,9 @@ function plotly_axis(axis::Axis, sp::Subplot) letter = axis[:letter] ax = KW( :title => axis[:guide], - :showgrid => sp[:grid], + :showgrid => axis[:grid], + :gridcolor => rgba_string(plot_color(axis[:foreground_color_grid], axis[:gridalpha])), + :gridwidth => axis[:gridlinewidth], :zeroline => false, :ticks => "inside", ) @@ -229,8 +232,8 @@ function plotly_axis(axis::Axis, sp::Subplot) ax[:titlefont] = plotly_font(axis[:guidefont], axis[:foreground_color_guide]) ax[:type] = plotly_scale(axis[:scale]) ax[:tickfont] = plotly_font(axis[:tickfont], axis[:foreground_color_text]) - ax[:tickcolor] = rgba_string(axis[:foreground_color_border]) - ax[:linecolor] = rgba_string(axis[:foreground_color_border]) + ax[:tickcolor] = rgba_string(axis[:foreground_color_axis]) + ax[:linecolor] = rgba_string(axis[:foreground_color_axis]) # lims lims = axis[:lims] @@ -435,7 +438,8 @@ function plotly_series(plt::Plot, series::Series) isscatter = st in (:scatter, :scatter3d, :scattergl) hasmarker = isscatter || series[:markershape] != :none hasline = st in (:path, :path3d) - hasfillrange = st in (:path, :scatter, :scattergl) && isa(series[:fillrange], AbstractVector) + hasfillrange = st in (:path, :scatter, :scattergl) && + (isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple)) # for surface types, set the data if st in (:heatmap, :contour, :surface, :wireframe) @@ -459,7 +463,7 @@ function plotly_series(plt::Plot, series::Series) else hasline ? "lines" : "none" end - if series[:fillrange] == true || series[:fillrange] == 0 + if series[:fillrange] == true || series[:fillrange] == 0 || isa(series[:fillrange], Tuple) d_out[:fill] = "tozeroy" d_out[:fillcolor] = rgba_string(series[:fillcolor]) elseif isa(series[:fillrange], AbstractVector) @@ -584,11 +588,21 @@ function plotly_series(plt::Plot, series::Series) if hasfillrange # if hasfillrange is true, return two dictionaries (one for original # series, one for series being filled to) instead of one - d_out_fillrange = copy(d_out) - d_out_fillrange[:y] = series[:fillrange] + d_out_fillrange = deepcopy(d_out) d_out_fillrange[:showlegend] = false - delete!(d_out_fillrange, :fill) - delete!(d_out_fillrange, :fillcolor) + if isa(series[:fillrange], AbstractVector) + d_out_fillrange[:y] = series[:fillrange] + delete!(d_out_fillrange, :fill) + delete!(d_out_fillrange, :fillcolor) + else + # if fillrange is a tuple with upper and lower limit, d_out_fillrange + # is the series that will do the filling + d_out_fillrange[:x], d_out_fillrange[:y] = + concatenate_fillrange(series[:x], series[:fillrange]) + d_out_fillrange[:line][:width] = 0 + delete!(d_out, :fill) + delete!(d_out, :fillcolor) + end return [d_out_fillrange, d_out] else diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index ecac853a..2188191a 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -17,7 +17,8 @@ const _pyplot_attr = merge_with_base_supported([ :window_title, :guide, :lims, :ticks, :scale, :flip, :rotation, :tickfont, :guidefont, :legendfont, - :grid, :legend, :legendtitle, :colorbar, + :grid, :gridalpha, :gridstyle, :gridlinewidth, + :legend, :legendtitle, :colorbar, :marker_z, :line_z, :fill_z, :levels, :ribbon, :quiver, :arrow, @@ -32,6 +33,7 @@ const _pyplot_attr = merge_with_base_supported([ :dpi, :colorbar_title, :stride, + :framestyle, ]) const _pyplot_seriestype = [ :path, :steppre, :steppost, :shape, @@ -1053,7 +1055,8 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) end py_set_scale(ax, axis) py_set_lims(ax, axis) - py_set_ticks(ax, get_ticks(axis), letter) + ticks = sp[:framestyle] == :none ? nothing : get_ticks(axis) + py_set_ticks(ax, ticks, letter) ax[Symbol("set_", letter, "label")](axis[:guide]) if get(axis.d, :flip, false) ax[Symbol("invert_", letter, "axis")]() @@ -1065,9 +1068,13 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) lab[:set_family](axis[:tickfont].family) lab[:set_rotation](axis[:rotation]) end - if sp[:grid] - fgcolor = py_color(sp[:foreground_color_grid]) - pyaxis[:grid](true, color = fgcolor, linestyle = ":") + if axis[:grid] && !(ticks in (:none, nothing, false)) + fgcolor = py_color(axis[:foreground_color_grid]) + pyaxis[:grid](true, + color = fgcolor, + linestyle = py_linestyle(:line, axis[:gridstyle]), + linewidth = axis[:gridlinewidth], + alpha = axis[:gridalpha]) ax[:set_axisbelow](true) end py_set_axis_colors(ax, axis) @@ -1084,6 +1091,24 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) # this sets the bg color inside the grid ax[set_facecolor_sym](py_color(sp[:background_color_inside])) + + # framestyle + if !ispolar(sp) && !is3d(sp) + if sp[:framestyle] == :semi + intensity = 0.5 + ax[:spines]["right"][:set_alpha](intensity) + ax[:spines]["top"][:set_alpha](intensity) + ax[:spines]["right"][:set_linewidth](intensity) + ax[:spines]["top"][:set_linewidth](intensity) + elseif sp[:framestyle] == :axes + ax[:spines]["right"][:set_visible](false) + ax[:spines]["top"][:set_visible](false) + elseif sp[:framestyle] in (:grid, :none) + for (loc, spine) in ax[:spines] + spine[:set_visible](false) + end + end + end end py_drawfig(fig) end diff --git a/src/components.jl b/src/components.jl index 2962f184..eba135c2 100644 --- a/src/components.jl +++ b/src/components.jl @@ -22,6 +22,13 @@ immutable Shape # end # end end + +""" + Shape(x, y) + Shape(vertices) + +Construct a polygon to be plotted +""" Shape(verts::AVec) = Shape(unzip(verts)...) Shape(s::Shape) = deepcopy(s) @@ -32,6 +39,7 @@ vertices(shape::Shape) = collect(zip(shape.x, shape.y)) #deprecated @deprecate shape_coords coords +"return the vertex points from a Shape or Segments object" function coords(shape::Shape) shape.x, shape.y end @@ -156,6 +164,7 @@ Shape(k::Symbol) = deepcopy(_shapes[k]) # uses the centroid calculation from https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon +"return the centroid of a Shape" function center(shape::Shape) x, y = coords(shape) n = length(x) @@ -189,6 +198,7 @@ function scale(shape::Shape, x::Real, y::Real = x, c = center(shape)) scale!(shapecopy, x, y, c) end +"translate a Shape in space" function translate!(shape::Shape, x::Real, y::Real = x) sx, sy = coords(shape) for i=1:length(sx) @@ -227,6 +237,7 @@ function rotate!(shape::Shape, Θ::Real, c = center(shape)) shape end +"rotate an object in space" function rotate(shape::Shape, Θ::Real, c = center(shape)) shapecopy = deepcopy(shape) rotate!(shapecopy, Θ, c) @@ -331,6 +342,11 @@ immutable PlotText end PlotText(str) = PlotText(string(str), font()) +""" + text(string, args...) + +Create a PlotText object wrapping a string with font info, for plot annotations +""" text(t::PlotText) = t text(str::AbstractString, f::Font) = PlotText(str, f) function text(str, args...) @@ -350,6 +366,11 @@ immutable Stroke style end +""" + stroke(args...; alpha = nothing) + +Define the properties of the stroke used in plotting lines +""" function stroke(args...; alpha = nothing) width = 1 color = :black @@ -597,6 +618,12 @@ immutable Arrow headwidth::Float64 end +""" + arrow(args...) + +Define arrowheads to apply to lines - args are `style` (`:open` or `:closed`), +`side` (`:head`, `:tail` or `:both`), `headlength` and `headwidth` +""" function arrow(args...) style = :simple side = :head @@ -652,7 +679,7 @@ immutable Formatted{T} end # ----------------------------------------------------------------------- - +"create a BezierCurve for plotting" type BezierCurve{T <: FixedSizeArrays.Vec} control_points::Vector{T} end diff --git a/src/examples.jl b/src/examples.jl index a9b86960..4393ab60 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -155,7 +155,7 @@ PlotExample("Subplots", """, [:(begin l = @layout([a{0.1h}; b [c;d e]]) - plot(randn(100,5), layout=l, t=[:line :histogram :scatter :steppre :bar], leg=false, ticks=nothing, border=false) + plot(randn(100,5), layout=l, t=[:line :histogram :scatter :steppre :bar], leg=false, ticks=nothing, border=:none) end)] ), @@ -290,7 +290,7 @@ PlotExample("Layouts, margins, label rotation, title location", [:(begin plot(rand(100,6),layout=@layout([a b; c]),title=["A" "B" "C"], title_location=:left, left_margin=[20mm 0mm], - bottom_margin=50px, xrotation=60) + bottom_margin=10px, xrotation=60) end)] ), @@ -321,7 +321,7 @@ PlotExample("Animation with subplots", ), PlotExample("Spy", - "For a matrix `mat` with unique nonzeros `spy(mat)` returns a colorless plot. If `mat` has various different nonzero values, a colorbar is added. The colorbar can be disabled with `legend = nothing`. As always, the marker shape and size can be changed with `spy(mat, markersize = 3, markershape = :star)`", + "For a matrix `mat` with unique nonzeros `spy(mat)` returns a colorless plot. If `mat` has various different nonzero values, a colorbar is added. The colorbar can be disabled with `legend = nothing`. As always, the marker shape and size can be changed with `spy(mat, markersize = 3, markershape = :star)`.", [:(begin a = spdiagm((ones(50), ones(49), ones(49), ones(40), ones(40)),(0, 1, -1, 10, -10)) b = spdiagm((1:50, 1:49, 1:49, 1:40, 1:40),(0, 1, -1, 10, -10)) @@ -329,6 +329,26 @@ PlotExample("Spy", end)] ), +PlotExample("Magic grid argument", + "The grid lines can be modified individually for each axis with the magic `grid` argument.", + [:(begin + x = rand(10) + p1 = plot(x, title = "Default looks") + p2 = plot(x, grid = (:y, :olivedrab, :dot, 1, 0.9), title = "Modified y grid") + p3 = plot(deepcopy(p2), title = "Add x grid") + xgrid!(p3, :on, :cadetblue, 2, :dashdot, 0.4) + plot(p1, p2, p3, layout = (1, 3), label = "", fillrange = 0, fillalpha = 0.3) + end)] +), + +PlotExample("Framestyle", + "The style of the frame/axes of a (sub)plot can be changed with the `framestyle` attribute. The default framestyle is `:axes`.", + [:(begin + histogram(fill(randn(1000), 5), framestyle = [:box :semi :axes :grid :none], + title = [":box" ":semi" ":axes" ":grid" ":none"], color = RowVector(1:5), layout = 5, label = "") + end)] +), + ] # --------------------------------------------------------------------------------- @@ -348,6 +368,13 @@ function test_examples(pkgname::Symbol, idx::Int; debug = false, disp = true) end # generate all plots and create a dict mapping idx --> plt +""" +test_examples(pkgname[, idx]; debug = false, disp = true, sleep = nothing, + skip = [], only = nothing + +Run the `idx` test example for a given backend, or all examples if `idx` +is not specified. +""" function test_examples(pkgname::Symbol; debug = false, disp = true, sleep = nothing, skip = [], only = nothing) Plots._debugMode.on = debug diff --git a/src/layouts.jl b/src/layouts.jl index 71f8b7a1..9e90a2f1 100644 --- a/src/layouts.jl +++ b/src/layouts.jl @@ -133,7 +133,12 @@ make_measure_hor(m::Measure) = m make_measure_vert(n::Number) = n * h make_measure_vert(m::Measure) = m +""" + bbox(x, y, w, h [,originargs...]) + bbox(layout) +Create a bounding box for plotting +""" function bbox(x, y, w, h, oarg1::Symbol, originargs::Symbol...) oargs = vcat(oarg1, originargs...) orighor = :left @@ -253,6 +258,13 @@ type GridLayout <: AbstractLayout attr::KW end +""" + grid(args...; kw...) + +Create a grid layout for subplots. `args` specify the dimensions, e.g. +`grid(3,2, widths = (0.6,04))` creates a grid with three rows and two +columns of different width. +""" grid(args...; kw...) = GridLayout(args...; kw...) function GridLayout(dims...; diff --git a/src/output.jl b/src/output.jl index 5c036fe7..41cc9d0c 100644 --- a/src/output.jl +++ b/src/output.jl @@ -97,6 +97,13 @@ function addExtension(fn::AbstractString, ext::AbstractString) end end +""" + savefig([plot,] filename) + +Save a Plot (the current plot if `plot` is not passed) to file. The file +type is inferred from the file extension. All backends support png and pdf +file types, some also support svg, ps, eps, html and tex. +""" function savefig(plt::Plot, fn::AbstractString) # get the extension @@ -119,7 +126,11 @@ savefig(fn::AbstractString) = savefig(current(), fn) # --------------------------------------------------------- +""" + gui([plot]) +Display a plot using the backends' gui window +""" gui(plt::Plot = current()) = display(PlotsDisplay(), plt) # IJulia only... inline display @@ -198,6 +209,7 @@ for mime in keys(_mimeformats) end end +"Close all open gui windows of the current backend" closeall() = closeall(backend()) @@ -266,6 +278,7 @@ using Requires show(io, MIME("text/html"), plt) end + ENV["MPLBACKEND"] = "Agg" set_ijulia_output("text/html") end end diff --git a/src/pipeline.jl b/src/pipeline.jl index 83eff845..e0041b77 100644 --- a/src/pipeline.jl +++ b/src/pipeline.jl @@ -60,29 +60,26 @@ function _process_userrecipes(plt::Plot, d::KW, args) args = _preprocess_args(d, args, still_to_process) # for plotting recipes, swap out the args and update the parameter dictionary - # we are keeping a queue of series that still need to be processed. + # 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, and the rest go into the queue - # for processing. + # 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 pass it through apply_recipe - # to generate a list of RecipeData objects (data + attributes) + # 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 = shift!(still_to_process) - rd_list = RecipesBase.apply_recipe(next_series.d, next_series.args...) - for recipedata in rd_list - # recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes - if !(typeof(recipedata) <: RecipeData) - error("Inputs couldn't be processed... expected RecipeData but got: $recipedata") - end - - if isempty(recipedata.args) - _process_userrecipe(plt, kw_list, recipedata) - else - # args are non-empty, so there's still processing to do... add it back to the queue - push!(still_to_process, recipedata) - end + # 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.d, next_series.args...) + prepend!(still_to_process,rd_list) end end @@ -213,7 +210,7 @@ function _plot_setup(plt::Plot, d::KW, kw_list::Vector{KW}) # TODO: init subplots here _update_plot_args(plt, d) if !plt.init - plt.o = _create_backend_figure(plt) + plt.o = Base.invokelatest(_create_backend_figure, plt) # create the layout and subplots from the inputs plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr) diff --git a/src/plot.jl b/src/plot.jl index 3ab41701..3e6dd9a3 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -6,6 +6,10 @@ const CURRENT_PLOT = CurrentPlot(Nullable{AbstractPlot}()) isplotnull() = isnull(CURRENT_PLOT.nullableplot) +""" + current() +Returns the Plot object for the current plot +""" function current() if isplotnull() error("No current plot/subplot") diff --git a/src/plotattr.jl b/src/plotattr.jl index cc8d053b..7313b2ce 100644 --- a/src/plotattr.jl +++ b/src/plotattr.jl @@ -14,6 +14,12 @@ function lookup_aliases(attrtype, attribute) error("There is no attribute named $attribute in $attrtype") end +""" + plotattr([attr]) + +Look up the properties of a Plots attribute, or specify an attribute type. Call `plotattr()` for options. +The information is the same as that given on https://juliaplots.github.io/attributes/. +""" function plotattr() println("Specify an attribute type to get a list of supported attributes. Options are $(attrtypes())") end diff --git a/src/recipes.jl b/src/recipes.jl index f5cb9b73..d4d1b201 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -510,8 +510,10 @@ function _auto_binning_nbins{N}(vs::NTuple{N,AbstractVector}, dim::Integer; mode v = vs[dim] if mode == :auto - 30 - elseif mode == :sqrt # Square-root choice + mode = :fd + end + + if mode == :sqrt # Square-root choice _cl(sqrt(n)) elseif mode == :sturges # Sturges' formula _cl(log2(n)) + 1 @@ -550,7 +552,7 @@ end @recipe function f(::Type{Val{:histogram}}, x, y, z) - seriestype := :barhist + seriestype := length(y) > 1e6 ? :stephist : :barhist () end @deps histogram barhist @@ -847,6 +849,7 @@ end # TODO: move OHLC to PlotRecipes finance.jl +"Represent Open High Low Close data (used in finance)" type OHLC{T<:Real} open::T high::T diff --git a/src/series.jl b/src/series.jl index ca520168..f3499e22 100644 --- a/src/series.jl +++ b/src/series.jl @@ -509,12 +509,25 @@ end # nothing # end +splittable_kw(key, val, lengthGroup) = false +splittable_kw(key, val::AbstractArray, lengthGroup) = (key != :group) && size(val,1) == lengthGroup +splittable_kw(key, val::Tuple, lengthGroup) = all(splittable_kw.(key, val, lengthGroup)) + +split_kw(key, val::AbstractArray, indices) = val[indices, fill(Colon(), ndims(val)-1)...] +split_kw(key, val::Tuple, indices) = Tuple(split_kw(key, v, indices) for v in val) + # split the group into 1 series per group, and set the label and idxfilter for each @recipe function f(groupby::GroupBy, args...) + lengthGroup = maximum(union(groupby.groupIds...)) for (i,glab) in enumerate(groupby.groupLabels) @series begin label --> string(glab) idxfilter --> groupby.groupIds[i] + for (key,val) in d + if splittable_kw(key, val, lengthGroup) + :($key) := split_kw(key, val, groupby.groupIds[i]) + end + end args end end diff --git a/src/subplots.jl b/src/subplots.jl index ccd1478c..5629125f 100644 --- a/src/subplots.jl +++ b/src/subplots.jl @@ -13,6 +13,11 @@ function Subplot{T<:AbstractBackend}(::T; parent = RootLayout()) ) end +""" + plotarea(subplot) + +Return the bounding box of a subplot +""" plotarea(sp::Subplot) = sp.plotarea plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox) diff --git a/src/themes.jl b/src/themes.jl index e7a39d02..840078dd 100644 --- a/src/themes.jl +++ b/src/themes.jl @@ -1,4 +1,8 @@ +""" + theme(s::Symbol) +Specify the colour theme for plots. +""" function theme(s::Symbol; kw...) # reset? if s == :none || s == :default diff --git a/src/utils.jl b/src/utils.jl index d8aced3d..8c5986fc 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -137,7 +137,7 @@ function imageHack(d::KW) end # --------------------------------------------------------------- - +"Build line segments for plotting" type Segments{T} pts::Vector{T} end @@ -185,6 +185,7 @@ type SegmentsIterator args::Tuple n::Int end + function iter_segments(args...) tup = Plots.wraptuple(args) n = maximum(map(length, tup)) @@ -495,14 +496,42 @@ function make_fillrange_from_ribbon(kw::KW) rib1, rib2 = -first(rib), last(rib) # kw[:ribbon] = nothing kw[:fillrange] = make_fillrange_side(y, rib1), make_fillrange_side(y, rib2) + (get(kw, :fillalpha, nothing) == nothing) && (kw[:fillalpha] = 0.5) +end + +#turn tuple of fillranges to one path +function concatenate_fillrange(x,y::Tuple) + rib1, rib2 = first(y), last(y) + yline = vcat(rib1,(rib2)[end:-1:1]) + xline = vcat(x,x[end:-1:1]) + return xline, yline end function get_sp_lims(sp::Subplot, letter::Symbol) axis_limits(sp[Symbol(letter, :axis)]) end + +""" + xlims([plt]) + +Returns the x axis limits of the current plot or subplot +""" xlims(sp::Subplot) = get_sp_lims(sp, :x) + +""" + ylims([plt]) + +Returns the y axis limits of the current plot or subplot +""" ylims(sp::Subplot) = get_sp_lims(sp, :y) + +""" + zlims([plt]) + +Returns the z axis limits of the current plot or subplot +""" zlims(sp::Subplot) = get_sp_lims(sp, :z) + xlims(plt::Plot, sp_idx::Int = 1) = xlims(plt[sp_idx]) ylims(plt::Plot, sp_idx::Int = 1) = ylims(plt[sp_idx]) zlims(plt::Plot, sp_idx::Int = 1) = zlims(plt[sp_idx]) @@ -536,7 +565,7 @@ allFunctions(arg) = trueOrAllTrue(a -> isa(a, Function), arg) """ Allows temporary setting of backend and defaults for Plots. Settings apply only for the `do` block. Example: ``` -with(:gadfly, size=(400,400), type=:histogram) do +with(:gr, size=(400,400), type=:histogram) do plot(rand(10)) plot(rand(10)) end diff --git a/test/imgcomp.jl b/test/imgcomp.jl index 7d6ae303..0a59e0df 100644 --- a/test/imgcomp.jl +++ b/test/imgcomp.jl @@ -23,7 +23,7 @@ default(size=(500,300)) # TODO: use julia's Condition type and the wait() and notify() functions to initialize a Window, then wait() on a condition that # is referenced in a button press callback (the button clicked callback will call notify() on that condition) -const _current_plots_version = v"0.12.1" +const _current_plots_version = v"0.12.3" function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2) @@ -42,7 +42,8 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = is fn = "ref$idx.png" # firgure out version info - versions = sort(VersionNumber.(readdir(refdir)), rev = true) + 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 diff --git a/test/runtests.jl b/test/runtests.jl index 69b4d393..da7fcb05 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,6 +8,7 @@ default(show=false, reuse=true) img_eps = isinteractive() ? 1e-2 : 10e-2 @testset "GR" begin + ENV["GKSwstype"] = "100" @test gr() == Plots.GRBackend() @test backend() == Plots.GRBackend()