diff --git a/.travis.yml b/.travis.yml index d3b61c32..7288bdf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,10 @@ addons: - xauth - xvfb +cache: + directories: + - $HOME/.julia/artifacts + sudo: required before_install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pwd ; fi diff --git a/Project.toml b/Project.toml index 46aacdfd..d21b9273 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.3" +version = "0.29.6" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" @@ -35,7 +35,7 @@ Contour = "0.5" FFMPEG = "0.2, 0.3" FixedPointNumbers = "0.6, 0.7, 0.8" GR = "0.46, 0.47" -GeometryTypes = "0.7" +GeometryTypes = "0.7, 0.8" JSON = "0.21" Measures = "0.3" NaNMath = "0.3" diff --git a/appveyor.yml b/appveyor.yml index cd0c8862..02074c59 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,6 +20,9 @@ branches: - master - /release-.*/ +cache: + - '%USERPROFILE%\.julia\artifacts' + notifications: - provider: Email on_build_success: false diff --git a/src/args.jl b/src/args.jl index 8c6b1f0a..82ca09d8 100644 --- a/src/args.jl +++ b/src/args.jl @@ -345,7 +345,7 @@ const _subplot_defaults = KW( :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 + :aspect_ratio => :auto, # choose from :none or :equal :margin => 1mm, :left_margin => :match, :top_margin => :match, diff --git a/src/backends.jl b/src/backends.jl index 143a929e..abfa98b1 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -673,37 +673,86 @@ const _inspectdr_scale = [:identity, :ln, :log2, :log10] const _pgfplotsx_attr = merge_with_base_supported([ :annotations, - :background_color_legend, :background_color_inside, :background_color_outside, - :foreground_color_legend, :foreground_color_grid, :foreground_color_axis, - :foreground_color_text, :foreground_color_border, + :background_color_legend, + :background_color_inside, + :background_color_outside, + :foreground_color_legend, + :foreground_color_grid, + :foreground_color_axis, + :foreground_color_text, + :foreground_color_border, :label, - :seriescolor, :seriesalpha, - :linecolor, :linestyle, :linewidth, :linealpha, - :markershape, :markercolor, :markersize, :markeralpha, - :markerstrokewidth, :markerstrokecolor, :markerstrokealpha, - :fillrange, :fillcolor, :fillalpha, + :seriescolor, + :seriesalpha, + :linecolor, + :linestyle, + :linewidth, + :linealpha, + :markershape, + :markercolor, + :markersize, + :markeralpha, + :markerstrokewidth, + :markerstrokecolor, + :markerstrokealpha, + :fillrange, + :fillcolor, + :fillalpha, :bins, :layout, - :title, :window_title, - :guide, :lims, :ticks, :scale, :flip, + :title, + :window_title, + :guide, + :lims, + :ticks, + :scale, + :flip, :match_dimensions, - :titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign, - :titlefontrotation, :titlefontcolor, - :legendfontfamily, :legendfontsize, :legendfonthalign, :legendfontvalign, - :legendfontrotation, :legendfontcolor, - :tickfontfamily, :tickfontsize, :tickfonthalign, :tickfontvalign, - :tickfontrotation, :tickfontcolor, - :guidefontfamily, :guidefontsize, :guidefonthalign, :guidefontvalign, - :guidefontrotation, :guidefontcolor, - :grid, :gridalpha, :gridstyle, :gridlinewidth, - :legend, :legendtitle, :colorbar, :colorbar_title, :colorbar_entry, - :fill_z, :line_z, :marker_z, :levels, - :ribbon, :quiver, + :titlefontfamily, + :titlefontsize, + :titlefonthalign, + :titlefontvalign, + :titlefontrotation, + :titlefontcolor, + :legendfontfamily, + :legendfontsize, + :legendfonthalign, + :legendfontvalign, + :legendfontrotation, + :legendfontcolor, + :tickfontfamily, + :tickfontsize, + :tickfonthalign, + :tickfontvalign, + :tickfontrotation, + :tickfontcolor, + :guidefontfamily, + :guidefontsize, + :guidefonthalign, + :guidefontvalign, + :guidefontrotation, + :guidefontcolor, + :grid, + :gridalpha, + :gridstyle, + :gridlinewidth, + :legend, + :legendtitle, + :colorbar, + :colorbar_title, + :colorbar_entry, + :fill_z, + :line_z, + :marker_z, + :levels, + :ribbon, + :quiver, :orientation, :overwrite_figure, :polar, :aspect_ratio, - :normalize, :weights, + :normalize, + :weights, :inset_subplots, :bar_width, :arrow, @@ -712,13 +761,42 @@ const _pgfplotsx_attr = merge_with_base_supported([ :camera, :contour_labels, ]) -const _pgfplotsx_seriestype = - [:path, :scatter, :straightline, - :path3d, :scatter3d, :surface, :wireframe, - :heatmap, :contour, :contour3d, - :shape, - :steppre, :stepmid, :steppost, :ysticks, :xsticks] +const _pgfplotsx_seriestype = [ + :path, + :scatter, + :straightline, + :path3d, + :scatter3d, + :surface, + :wireframe, + :heatmap, + :contour, + :contour3d, + :shape, + :steppre, + :stepmid, + :steppost, + :ysticks, + :xsticks, +] const _pgfplotsx_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] -const _pgfplotsx_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :ltriangle, :rtriangle, :cross, :xcross, :star5, :pentagon, :hline, :vline, Shape] +const _pgfplotsx_marker = [ + :none, + :auto, + :circle, + :rect, + :diamond, + :utriangle, + :dtriangle, + :ltriangle, + :rtriangle, + :cross, + :xcross, + :star5, + :pentagon, + :hline, + :vline, + Shape, +] const _pgfplotsx_scale = [:identity, :ln, :log2, :log10] is_marker_supported(::PGFPlotsXBackend, shape::Shape) = true diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 2f59e09d..5a8bf07d 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -305,15 +305,27 @@ end # --------------------------------------------------------- # draw ONE Shape -function gr_draw_marker(xi, yi, msize, shape::Shape) +function gr_draw_marker(series, xi, yi, clims, i, msize, shape::Shape) sx, sy = coords(shape) # convert to ndc coords (percentages of window) GR.selntran(0) w, h = gr_plot_size f = msize / (w + h) xi, yi = GR.wctondc(xi, yi) - GR.fillarea(xi .+ sx .* f, - yi .+ sy .* f) + xs = xi .+ sx .* f + ys = yi .+ sy .* f + + # draw the interior + mc = get_markercolor(series, clims, i) + gr_set_fill(mc) + gr_set_transparency(mc, get_markeralpha(series, i)) + GR.fillarea(xs, ys) + + # draw the shapes + msc = get_markerstrokecolor(series, i) + gr_set_line(get_markerstrokewidth(series, i), :solid, msc) + gr_set_transparency(msc, get_markerstrokealpha(series, i)) + GR.polyline(xs, ys) GR.selntran(1) end @@ -323,7 +335,11 @@ function nominal_size() end # draw ONE symbol marker -function gr_draw_marker(xi, yi, msize::Number, shape::Symbol) +function gr_draw_marker(series, xi, yi, clims, i, msize::Number, shape::Symbol) + GR.setborderwidth(series[:markerstrokewidth]); + gr_set_bordercolor(get_markerstrokecolor(series, i)); + gr_set_markercolor(get_markercolor(series, clims, i)); + gr_set_transparency(get_markeralpha(series, i)) GR.setmarkertype(gr_markertype[shape]) GR.setmarkersize(0.3msize / nominal_size()) GR.polymarker([xi], [yi]) @@ -341,13 +357,7 @@ function gr_draw_markers(series::Series, x, y, clims, msize = series[:markersize for i=eachindex(x) msi = _cycle(msize, i) shape = _cycle(shapes, i) -i - GR.setborderwidth(series[:markerstrokewidth]); - gr_set_bordercolor(get_markerstrokecolor(series, i)); - gr_set_markercolor(get_markercolor(series, clims, i)); - gr_set_transparency(get_markeralpha(series, i)) - - gr_draw_marker(x[i], y[i], msi, shape) + gr_draw_marker(series, x[i], y[i], clims, i, msi, shape) end end end @@ -971,7 +981,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) data_lims = gr_xy_axislims(sp) xy_lims = data_lims - ratio = sp[:aspect_ratio] + ratio = get_aspect_ratio(sp) if ratio != :none if ratio == :equal ratio = 1 @@ -1050,11 +1060,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if st == :pie draw_axes = false end - if st == :heatmap + if st in (:heatmap, :image) outside_ticks = true - for ax in (sp[:xaxis], sp[:yaxis]) - v = series[ax[:letter]] - end x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) xy_lims = x[1], x[end], y[1], y[end] expand_extrema!(sp[:xaxis], x) @@ -1776,8 +1783,10 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) elseif st == :image z = transpose_z(series, series[:z].surf, true)' + x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(z)) w, h = size(z) - xmin, xmax = ignorenan_extrema(series[:x]); ymin, ymax = ignorenan_extrema(series[:y]) + xmin, xmax = ignorenan_extrema(x) + ymin, ymax = ignorenan_extrema(y) rgba = gr_color.(z) GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba) end diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index 7205b850..276ad8dd 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -470,7 +470,7 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) push!(style, string("title style = {font = ", pgf_font(sp[:titlefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", sp[:titlefontrotation], "}")) end - if sp[:aspect_ratio] in (1, :equal) + if get_aspect_ratio(sp) in (1, :equal) kw[:axisEqual] = "true" end diff --git a/src/backends/pgfplotsx.jl b/src/backends/pgfplotsx.jl index 3b1180e3..a75b0408 100644 --- a/src/backends/pgfplotsx.jl +++ b/src/backends/pgfplotsx.jl @@ -51,7 +51,7 @@ function pgfx_preamble(pgfx_plot::Plot{PGFPlotsXBackend}) old_flag = pgfx_plot.attr[:tex_output_standalone] pgfx_plot.attr[:tex_output_standalone] = true fulltext = String(repr("application/x-tex", pgfx_plot)) - preamble = fulltext[1:first(findfirst("\\begin{document}", fulltext))-1] + preamble = fulltext[1:(first(findfirst("\\begin{document}", fulltext)) - 1)] pgfx_plot.attr[:tex_output_standalone] = old_flag preamble end @@ -78,7 +78,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) if !pgfx_plot.is_created the_plot = PGFPlotsX.TikzPicture(PGFPlotsX.Options()) bgc = plt.attr[:background_color_outside] == :match ? - plt.attr[:background_color] : plt.attr[:background_color_outside] + plt.attr[:background_color] : plt.attr[:background_color_outside] if bgc isa Colors.Colorant cstr = plot_color(bgc) a = alpha(cstr) @@ -98,7 +98,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) sp_width = width(bb) sp_height = height(bb) dx, dy = bb.x0 - # TODO: does this hold at every scale? + # TODO: does this hold at every scale? if sp[:legend] in (:outertopright, nothing) dx *= 1.2 end @@ -129,7 +129,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) ) => nothing, "fill" => cstr, "fill opacity" => a, - "text opacity" => alpha(plot_color(sp[:legendfontcolor])), + "text opacity" => + alpha(plot_color(sp[:legendfontcolor])), "font" => pgfx_font( sp[:legendfontsize], pgfx_thickness_scaling(sp), @@ -157,11 +158,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) else push!( axis_opt, - "legend pos" => get( - _pgfplotsx_legend_pos, - sp[:legend], - "outer north east", - ), + "legend pos" => + get(_pgfplotsx_legend_pos, sp[:legend], "outer north east"), ) end for letter in (:x, :y, :z) @@ -185,8 +183,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) PGFPlotsX.push_preamble!( pgfx_plot.the_plot, """\\pgfplotsset{ - colormap={plots$(sp.attr[:subplot_index])}{$(pgfx_colormap(series.plotattributes[col]))}, -}""", + colormap={plots$(sp.attr[:subplot_index])}{$(pgfx_colormap(series.plotattributes[col]))}, + }""", ) push!( axis_opt, @@ -213,8 +211,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) push!(axis_opt, "view" => (azim, elev)) end axisf = if sp[:projection] == :polar - # push!(axis_opt, "xmin" => 90) - # push!(axis_opt, "xmax" => 450) + # push!(axis_opt, "xmin" => 90) + # push!(axis_opt, "xmax" => 450) PGFPlotsX.PolarAxis else PGFPlotsX.Axis @@ -229,20 +227,21 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) sf = series[:fillrange] series_opt = PGFPlotsX.Options( "color" => single_color(opt[:linecolor]), - "name path" => string(series_id) + "name path" => string(series_id), ) if is3d(series) || st == :heatmap series_func = PGFPlotsX.Plot3 else series_func = PGFPlotsX.Plot end - if sf !== nothing && !isfilledcontour(series) && series[:ribbon] === nothing + if sf !== nothing && + !isfilledcontour(series) && series[:ribbon] === nothing push!(series_opt, "area legend" => nothing) end if st == :heatmap push!(axis.options, "view" => "{0}{90}") end - # treat segments + # treat segments segments = if st in (:wireframe, :heatmap, :contour, :surface, :contour3d) iter_segments(surface_to_vecs( @@ -255,6 +254,9 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) end for (i, rng) in enumerate(segments) segment_opt = PGFPlotsX.Options() + if opt[:label] == "" + push!(segment_opt, "forget plot" => nothing) + end segment_opt = merge(segment_opt, pgfx_linestyle(opt, i)) if opt[:markershape] != :none marker = opt[:markershape] @@ -264,7 +266,10 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) scale_factor = 0.025 mark_size = opt[:markersize] * scale_factor path = join( - ["($(x[i] * mark_size), $(y[i] * mark_size))" for i in eachindex(x)], + [ + "($(x[i] * mark_size), $(y[i] * mark_size))" + for i in eachindex(x) + ], " -- ", ) c = get_markercolor(series, i) @@ -285,38 +290,45 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) segment_opt = merge(segment_opt, pgfx_fillstyle(opt, i)) end # add fillrange - if sf !== nothing && !isfilledcontour(series) && series[:ribbon] === nothing + if sf !== nothing && + !isfilledcontour(series) && series[:ribbon] === nothing if sf isa Number || sf isa AVec - pgfx_fillrange_series!( axis, series, series_func, i, _cycle(sf, rng), rng) + pgfx_fillrange_series!( + axis, + series, + series_func, + i, + _cycle(sf, rng), + rng, + ) end - if i == 1 && opt[:label] != "" && sp[:legend] != :none && should_add_to_legend(series) + if i == 1 && sp[:legend] != :none && pgfx_should_add_to_legend(series) pgfx_filllegend!(series_opt, opt) end end - # series - # - coordinates = pgfx_series_coordinates!( - sp, - series, - segment_opt, - opt, - rng, - ) - segment_plot = series_func( - merge(series_opt, segment_opt), - coordinates, - ) + # series + # + coordinates = + pgfx_series_coordinates!(sp, series, segment_opt, opt, rng) + segment_plot = + series_func(merge(series_opt, segment_opt), coordinates) push!(axis, segment_plot) # fill between functions if sf isa Tuple && series[:ribbon] === nothing sf1, sf2 = sf @assert sf1 == series_index "First index of the tuple has to match the current series index." - push!(axis, series_func( - merge(pgfx_fillstyle(opt, series_index), PGFPlotsX.Options("forget plot" => nothing)), - "fill between [of=$series_id and $(_pgfplotsx_series_ids[Symbol(string(sf2))])]" - )) + push!( + axis, + series_func( + merge( + pgfx_fillstyle(opt, series_index), + PGFPlotsX.Options("forget plot" => nothing), + ), + "fill between [of=$series_id and $(_pgfplotsx_series_ids[Symbol(string(sf2))])]", + ), + ) end - # add ribbons? + # add ribbons? ribbon = series[:ribbon] if ribbon !== nothing pgfx_add_ribbons!( @@ -327,18 +339,16 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) series_index, ) end - # add to legend? - if i == 1 && - opt[:label] != "" && - sp[:legend] != :none && should_add_to_legend(series) + # add to legend? + if i == 1 && sp[:legend] != :none && pgfx_should_add_to_legend(series) leg_opt = PGFPlotsX.Options() if ribbon !== nothing - pgfx_filllegend!(axis.contents[end-3].options, opt) + pgfx_filllegend!(axis.contents[end - 3].options, opt) end legend = PGFPlotsX.LegendEntry(leg_opt, opt[:label], false) push!(axis, legend) end - # add series annotations + # add series annotations anns = series[:series_annotations] for (xi, yi, str, fnt) in EachAnn(anns, series[:x], series[:y]) pgfx_add_annotation!( @@ -352,7 +362,11 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) end # add subplot annotations for ann in sp[:annotations] - pgfx_add_annotation!(axis, locate_annotation(sp, ann...)..., pgfx_thickness_scaling(sp)) + pgfx_add_annotation!( + axis, + locate_annotation(sp, ann...)..., + pgfx_thickness_scaling(sp), + ) end end # for series push!(the_plot, axis) @@ -495,7 +509,7 @@ function pgfx_series_coordinates!( ) push!( segment_opt, - "contour prepared" => PGFPlotsX.Options("labels" => opt[:contour_labels],), + "contour prepared" => PGFPlotsX.Options("labels" => opt[:contour_labels]), ) return PGFPlotsX.Table(Contour.contours(args..., opt[:levels])) end @@ -508,7 +522,7 @@ function pgfx_series_coordinates!( xs, ys, zs = collect(args) push!( segment_opt, - "contour filled" => PGFPlotsX.Options("labels" => opt[:contour_labels],), + "contour filled" => PGFPlotsX.Options("labels" => opt[:contour_labels]), "point meta" => "explicit", "shader" => "flat", ) @@ -519,10 +533,10 @@ function pgfx_series_coordinates!( end cs = join( - [join(["($x, $y) [$(zs[j, i])]" for (j, x) in enumerate(xs)], " ") for ( - i, - y, - ) in enumerate(ys)], + [ + join(["($x, $y) [$(zs[j, i])]" for (j, x) in enumerate(xs)], " ") + for (i, y) in enumerate(ys) + ], "\n\n", ) """ @@ -575,11 +589,8 @@ const _pgfx_framestyle_defaults = Dict(:semi => :box) # we use the anchors to define orientations for example to align left # one needs to use the right edge as anchor -const _pgfx_annotation_halign = KW( - :center => "", - :left => "right", - :right => "left", -) +const _pgfx_annotation_halign = + KW(:center => "", :left => "right", :right => "left") ## -------------------------------------------------------------------------------------- # Generates a colormap for pgfplots based on a ColorGradient pgfx_arrow(::Nothing) = "every arrow/.append style={-}" @@ -607,12 +618,9 @@ function pgfx_filllegend!(series_opt, opt) io = IOBuffer() PGFPlotsX.print_tex(io, pgfx_fillstyle(opt)) style = strip(String(take!(io)), ['[', ']', ' ']) - push!( - series_opt, - "legend image code/.code" => """{ -\\draw[$style] (0cm,-0.1cm) rectangle (0.6cm,0.1cm); -}""", - ) + push!(series_opt, "legend image code/.code" => """{ + \\draw[$style] (0cm,-0.1cm) rectangle (0.6cm,0.1cm); + }""") end function pgfx_colormap(grad::ColorGradient) @@ -631,9 +639,7 @@ function pgfx_framestyle(style::Symbol) return style else default_style = get(_pgfx_framestyle_defaults, style, :axes) - @warn( - "Framestyle :$style is not (yet) supported by the PGFPlotsX backend. :$default_style was cosen instead.", - ) + @warn( "Framestyle :$style is not (yet) supported by the PGFPlotsX backend. :$default_style was cosen instead.",) default_style end end @@ -675,6 +681,25 @@ function pgfx_font(fontsize, thickness_scaling = 1, font = "\\selectfont") return string("{\\fontsize{", fs, " pt}{", 1.3fs, " pt}", font, "}") end +function pgfx_should_add_to_legend(series::Series) + series.plotattributes[:primary] && series.plotattributes[:label] != "" && + !( + series.plotattributes[:seriestype] in ( + :hexbin, + :bins2d, + :histogram2d, + :hline, + :vline, + :contour, + :contourf, + :contour3d, + :heatmap, + :pie, + :image, + ) + ) +end + function pgfx_marker(plotattributes, i = 1) shape = _cycle(plotattributes[:markershape], i) cstr = plot_color( @@ -687,19 +712,22 @@ function pgfx_marker(plotattributes, i = 1) get_markerstrokealpha(plotattributes, i), ) a_stroke = alpha(cstr_stroke) - mark_size = pgfx_thickness_scaling(plotattributes) * 0.5 * - _cycle(plotattributes[:markersize], i) + mark_size = + pgfx_thickness_scaling(plotattributes) * + 0.5 * + _cycle(plotattributes[:markersize], i) return PGFPlotsX.Options( - "mark" => shape isa Shape ? "PlotsShape$i" : - get(_pgfplotsx_markers, shape, "*"), + "mark" => + shape isa Shape ? "PlotsShape$i" : get(_pgfplotsx_markers, shape, "*"), "mark size" => "$mark_size pt", "mark options" => PGFPlotsX.Options( "color" => cstr_stroke, "draw opacity" => a_stroke, "fill" => cstr, "fill opacity" => a, - "line width" => pgfx_thickness_scaling(plotattributes) * - _cycle(plotattributes[:markerstrokewidth], i), + "line width" => + pgfx_thickness_scaling(plotattributes) * + _cycle(plotattributes[:markerstrokewidth], i), "rotate" => if shape == :dtriangle 180 elseif shape == :rtriangle @@ -725,15 +753,15 @@ function pgfx_add_annotation!(o, x, y, val, thickness_scaling = 1) push!( o, [ - "\\node", - PGFPlotsX.Options( - get(_pgfx_annotation_halign, val.font.halign, "") => nothing, - "color" => cstr, - "draw opacity" => convert(Float16, a), - "rotate" => val.font.rotation, - "font" => pgfx_font(val.font.pointsize, thickness_scaling), - ), - " at (axis cs:$x, $y) {$(val.str)};", + "\\node", + PGFPlotsX.Options( + get(_pgfx_annotation_halign, val.font.halign, "") => nothing, + "color" => cstr, + "draw opacity" => convert(Float16, a), + "rotate" => val.font.rotation, + "font" => pgfx_font(val.font.pointsize, thickness_scaling), + ), + " at (axis cs:$x, $y) {$(val.str)};", ], ) end @@ -803,7 +831,7 @@ function pgfx_fillrange_series!(axis, series, series_func, i, fillrange, rng) push!(fillrange_opt, "forget plot" => nothing) opt = series.plotattributes args = is3d(series) ? (opt[:x][rng], opt[:y][rng], opt[:z][rng]) : - (opt[:x][rng], opt[:y][rng]) + (opt[:x][rng], opt[:y][rng]) push!( axis, PGFPlotsX.PlotInc(fillrange_opt, pgfx_fillrange_args(fillrange, args...)), @@ -854,7 +882,8 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter) opt, string(letter, "label style") => PGFPlotsX.Options( labelpos => nothing, - "font" => pgfx_font(axis[:guidefontsize], pgfx_thickness_scaling(sp)), + "font" => + pgfx_font(axis[:guidefontsize], pgfx_thickness_scaling(sp)), "color" => cstr, "draw opacity" => α, "rotate" => axis[:guidefontrotation], @@ -868,10 +897,8 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter) scale = axis[:scale] if scale in (:log2, :ln, :log10) push!(opt, string(letter, :mode) => "log") - scale == :ln || push!( - opt, - "log basis $letter" => "$(scale == :log2 ? 2 : 10)", - ) + scale == :ln || + push!(opt, "log basis $letter" => "$(scale == :log2 ? 2 : 10)") end # ticks on or off @@ -888,14 +915,15 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter) # limits lims = ispolar(sp) && letter == :x ? rad2deg.(axis_limits(sp, :x)) : - axis_limits(sp, letter) + axis_limits(sp, letter) push!(opt, string(letter, :min) => lims[1], string(letter, :max) => lims[2]) if !(axis[:ticks] in (nothing, false, :none, :native)) && framestyle != :none ticks = get_ticks(sp, axis) #pgf plot ignores ticks with angle below 90 when xmin = 90 so shift values - tick_values = ispolar(sp) && letter == :x ? - [rad2deg.(ticks[1])[3:end]..., 360, 405] : ticks[1] + tick_values = + ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] : + ticks[1] push!( opt, string(letter, "tick") => string("{", join(tick_values, ","), "}"), @@ -911,44 +939,37 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter) end push!( opt, - string(letter, "ticklabels") => string( - "{\$", - join(tick_labels, "\$,\$"), - "\$}", - ), + string(letter, "ticklabels") => + string("{\$", join(tick_labels, "\$,\$"), "\$}"), ) elseif axis[:showaxis] - tick_labels = ispolar(sp) && letter == :x ? - [ticks[2][3:end]..., "0", "45"] : ticks[2] + tick_labels = + ispolar(sp) && letter == :x ? [ticks[2][3:end]..., "0", "45"] : + ticks[2] if axis[:formatter] in (:scientific, :auto) tick_labels = string.("\$", convert_sci_unicode.(tick_labels), "\$") tick_labels = replace.(tick_labels, Ref("×" => "\\times")) end push!( opt, - string(letter, "ticklabels") => string( - "{", - join(tick_labels, ","), - "}", - ), + string(letter, "ticklabels") => + string("{", join(tick_labels, ","), "}"), ) else push!(opt, string(letter, "ticklabels") => "{}") end push!( opt, - string(letter, "tick align") => (axis[:tick_direction] == :out ? - "outside" : "inside"), + string(letter, "tick align") => + (axis[:tick_direction] == :out ? "outside" : "inside"), ) cstr = plot_color(axis[:tickfontcolor]) α = alpha(cstr) push!( opt, string(letter, "ticklabel style") => PGFPlotsX.Options( - "font" => pgfx_font( - axis[:tickfontsize], - pgfx_thickness_scaling(sp), - ), + "font" => + pgfx_font(axis[:tickfontsize], pgfx_thickness_scaling(sp)), "color" => cstr, "draw opacity" => α, "rotate" => axis[:tickfontrotation], @@ -967,7 +988,9 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter) # framestyle if framestyle in (:axes, :origin) - axispos = framestyle == :axes ? "left" : "middle" + axispos = axis[:mirror] ? "right" : + framestyle == :axes ? "left" : "middle" + if axis[:draw_arrow] push!(opt, string("axis ", letter, " line") => axispos) else diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index 47f03771..26a27f75 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -108,7 +108,7 @@ function shrink_by(lo, sz, ratio) end function plotly_apply_aspect_ratio(sp::Subplot, plotarea, pcts) - aspect_ratio = sp[:aspect_ratio] + aspect_ratio = get_aspect_ratio(sp) if aspect_ratio != :none if aspect_ratio == :equal aspect_ratio = 1.0 diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 19137b13..dc584e0d 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -1155,7 +1155,7 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) end # aspect ratio - aratio = sp[:aspect_ratio] + aratio = get_aspect_ratio(sp) if aratio != :none ax."set_aspect"(isa(aratio, Symbol) ? string(aratio) : aratio, anchor = "C") end diff --git a/src/examples.jl b/src/examples.jl index a00a61d9..72e2ee9f 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -2,503 +2,879 @@ Holds all data needed for a documentation example... header, description, and plotting expression (Expr) """ mutable struct PlotExample - header::AbstractString - desc::AbstractString - exprs::Vector{Expr} + header::AbstractString + desc::AbstractString + exprs::Vector{Expr} end # the _examples we'll run for each const _examples = PlotExample[ + PlotExample( + "Lines", + "A simple line plot of the columns.", + [:( + begin + plot(Plots.fakedata(50, 5), w = 3) + end + )], + ), + PlotExample( + "Functions, adding data, and animations", + """ + Plot multiple functions. You can also put the function first, or use the form `plot(f, + xmin, xmax)` where f is a Function or AbstractVector{Function}.\n\nGet series data: + `x, y = plt[i]`. Set series data: `plt[i] = (x,y)`. Add to the series with + `push!`/`append!`.\n\nEasily build animations. (`convert` or `ffmpeg` must be available + to generate the animation.) Use command `gif(anim, filename, fps=15)` to save the + animation. + """, + [:( + begin + p = plot([sin, cos], zeros(0), leg = false) + anim = Animation() + for x in range(0, stop = 10π, length = 100) + push!(p, x, Float64[sin(x), cos(x)]) + frame(anim) + end + end + )], + ), + PlotExample( + "Parametric plots", + "Plot function pair (x(u), y(u)).", + [ + :( + begin + plot( + sin, + x -> sin(2x), + 0, + 2π, + line = 4, + leg = false, + fill = (0, :orange), + ) + end + ), + ], + ), + PlotExample( + "Colors", + """ + Access predefined palettes (or build your own with the `colorscheme` method). + Line/marker colors are auto-generated from the plot's palette, unless overridden. Set + the `z` argument to turn on series gradients. + """, + [ + :( + begin + y = rand(100) + plot( + 0:10:100, + rand(11, 4), + lab = "lines", + w = 3, + palette = :grays, + fill = 0, + α = 0.6, + ) + scatter!( + y, + zcolor = abs.(y .- 0.5), + m = (:heat, 0.8, Plots.stroke(1, :green)), + ms = 10 * abs.(y .- 0.5) .+ 4, + lab = "grad", + ) + end + ), + ], + ), + PlotExample( + "Global", + """ + Change the guides/background/limits/ticks. Convenience args `xaxis` and `yaxis` allow + you to pass a tuple or value which will be mapped to the relevant args automatically. + The `xaxis` below will be replaced with `xlabel` and `xlims` args automatically during + the preprocessing step. You can also use shorthand functions: `title!`, `xaxis!`, + `yaxis!`, `xlabel!`, `ylabel!`, `xlims!`, `ylims!`, `xticks!`, `yticks!` + """, + [ + :( + begin + using Statistics + y = rand(20, 3) + plot( + y, + xaxis = ("XLABEL", (-5, 30), 0:2:20, :flip), + background_color = RGB(0.2, 0.2, 0.2), + leg = false, + ) + hline!( + mean(y, dims = 1) + rand(1, 3), + line = (4, :dash, 0.6, [:lightgreen :green :darkgreen]), + ) + vline!([5, 10]) + title!("TITLE") + yaxis!("YLABEL", :log10) + end + ), + ], + ), -PlotExample("Lines", - "A simple line plot of the columns.", - [:(begin - plot(Plots.fakedata(50,5), w=3) - end)] -), + # PlotExample("Two-axis", + # "Use the `axis` arguments.", + # [ + # :(plot(Vector[randn(100), randn(100)*100], axis = [:l :r], ylabel="LEFT", yrightlabel="RIGHT", xlabel="X", title="TITLE")) + # ]), -PlotExample("Functions, adding data, and animations", -""" -Plot multiple functions. You can also put the function first, or use the form `plot(f, -xmin, xmax)` where f is a Function or AbstractVector{Function}.\n\nGet series data: -`x, y = plt[i]`. Set series data: `plt[i] = (x,y)`. Add to the series with -`push!`/`append!`.\n\nEasily build animations. (`convert` or `ffmpeg` must be available -to generate the animation.) Use command `gif(anim, filename, fps=15)` to save the -animation. -""", - [:(begin - p = plot([sin,cos], zeros(0), leg=false) - anim = Animation() - for x in range(0, stop=10π, length=100) - push!(p, x, Float64[sin(x), cos(x)]) - frame(anim) + PlotExample( + "Images", + "Plot an image. y-axis is set to flipped", + [ + :( + begin + import FileIO + path = + download("http://juliaplots.org/PlotReferenceImages.jl/Plots/pyplot/0.7.0/ref1.png") + img = FileIO.load(path) + plot(img) + end + ), + ], + ), + PlotExample( + "Arguments", + """ + Plot multiple series with different numbers of points. Mix arguments that apply to all + series (marker/markersize) with arguments unique to each series (colors). Special + arguments `line`, `marker`, and `fill` will automatically figure out what arguments to + set (for example, we are setting the `linestyle`, `linewidth`, and `color` arguments with + `line`.) Note that we pass a matrix of colors, and this applies the colors to each + series. + """, + [ + :( + begin + ys = Vector[rand(10), rand(20)] + plot( + ys, + color = [:black :orange], + line = (:dot, 4), + marker = ([:hex :d], 12, 0.8, Plots.stroke(3, :gray)), + ) + end + ), + ], + ), + PlotExample( + "Build plot in pieces", + "Start with a base plot...", + [:( + begin + plot(rand(100) / 3, reg = true, fill = (0, :green)) + end + )], + ), + PlotExample( + "", + "and add to it later.", + [:( + begin + scatter!(rand(100), markersize = 6, c = :orange) + end + )], + ), + PlotExample( + "Histogram2D", + "", + [:( + begin + histogram2d(randn(10000), randn(10000), nbins = 20) + end + )], + ), + PlotExample( + "Line types", + "", + [ + :( + begin + linetypes = [:path :steppre :steppost :sticks :scatter] + n = length(linetypes) + x = Vector[sort(rand(20)) for i = 1:n] + y = rand(20, n) + plot( + x, + y, + line = (linetypes, 3), + lab = map(string, linetypes), + ms = 15, + ) + end + ), + ], + ), + PlotExample( + "Line styles", + "", + [ + :( + begin + styles = filter( + s -> s in Plots.supported_styles(), + [:solid, :dash, :dot, :dashdot, :dashdotdot], + ) + styles = reshape(styles, 1, length(styles)) # Julia 0.6 unfortunately gives an error when transposing symbol vectors + n = length(styles) + y = cumsum(randn(20, n), dims = 1) + plot( + y, + line = (5, styles), + label = map(string, styles), + legendtitle = "linestyle", + ) + end + ), + ], + ), + PlotExample( + "Marker types", + "", + [ + :( + begin + markers = filter( + m -> m in Plots.supported_markers(), + Plots._shape_keys, + ) + markers = reshape(markers, 1, length(markers)) + n = length(markers) + x = range(0, stop = 10, length = n + 2)[2:(end - 1)] + y = repeat(reshape(reverse(x), 1, :), n, 1) + scatter( + x, + y, + m = (8, :auto), + lab = map(string, markers), + bg = :linen, + xlim = (0, 10), + ylim = (0, 10), + ) + end + ), + ], + ), + PlotExample( + "Bar", + "`x` is the midpoint of the bar. (todo: allow passing of edges instead of midpoints)", + [:( + begin + bar(randn(99)) + end + )], + ), + PlotExample( + "Histogram", + "", + [ + :( + begin + histogram( + randn(1000), + bins = :scott, + weights = repeat(1:5, outer = 200), + ) + end + ), + ], + ), + PlotExample( + "Subplots", + """ + Use the `layout` keyword, and optionally the convenient `@layout` macro to generate + arbitrarily complex subplot layouts. + """, + [ + :( + 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 = :none, + ) + end + ), + ], + ), + PlotExample( + "Adding to subplots", + """ + Note here the automatic grid layout, as well as the order in which new series are added + to the plots. + """, + [ + :( + begin + plot( + Plots.fakedata(100, 10), + layout = 4, + palette = [:grays :blues :heat :lightrainbow], + bg_inside = [:orange :pink :darkblue :black], + ) + end + ), + ], + ), + PlotExample("", "", [:( + begin + using Random + Random.seed!(111) + plot!(Plots.fakedata(100, 10)) end - end)] -), + )]), + PlotExample( + "Open/High/Low/Close", + """ + Create an OHLC chart. Pass in a list of (open,high,low,close) tuples as your `y` + argument. This uses recipes to first convert the tuples to OHLC objects, and + subsequently create a :path series with the appropriate line segments. + """, + [ + :( + begin + n = 20 + hgt = rand(n) .+ 1 + bot = randn(n) + openpct = rand(n) + closepct = rand(n) + y = OHLC[ + ( + openpct[i] * hgt[i] + bot[i], + bot[i] + hgt[i], + bot[i], + closepct[i] * hgt[i] + bot[i], + ) + for i = 1:n + ] + ohlc(y) + end + ), + ], + ), + PlotExample( + "Annotations", + """ + The `annotations` keyword is used for text annotations in data-coordinates. Pass in a + tuple (x,y,text) or a vector of annotations. `annotate!(ann)` is shorthand for `plot!(; + annotation=ann)`. Series annotations are used for annotating individual data points. + They require only the annotation... x/y values are computed. A `PlotText` object can be + build with the method `text(string, attr...)`, which wraps font and color attributes. + """, + [ + :( + 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"), + ), + ]) + 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), + ], + ) + end + ), + ], + ), + PlotExample( + "Custom Markers", + """A `Plots.Shape` is a light wrapper around vertices of a polygon. For supported + backends, pass arbitrary polygons as the marker shapes. Note: The center is (0,0) and + the size is expected to be rougly the area of the unit circle. + """, + [ + :( + begin + verts = [ + (-1.0, 1.0), + (-1.28, 0.6), + (-0.2, -1.4), + (0.2, -1.4), + (1.28, 0.6), + (1.0, 1.0), + (-1.0, 1.0), + (-0.2, -0.6), + (0.0, -0.2), + (-0.4, 0.6), + (1.28, 0.6), + (0.2, -1.4), + (-0.2, -1.4), + (0.6, 0.2), + (-0.2, 0.2), + (0.0, -0.2), + (0.2, 0.2), + (-0.2, -0.6), + ] + x = 0.1:0.2:0.9 + y = 0.7 * rand(5) .+ 0.15 + plot( + x, + y, + line = (3, :dash, :lightblue), + marker = (Shape(verts), 30, RGBA(0, 0, 0, 0.2)), + bg = :pink, + fg = :darkblue, + xlim = (0, 1), + ylim = (0, 1), + leg = false, + ) + end + ), + ], + ), + PlotExample( + "Contours", + """ + Any value for fill works here. We first build a filled contour from a function, then an + unfilled contour from a matrix. + """, + [:( + begin + x = 1:0.5:20 + y = 1:0.5:10 + f(x, y) = (3x + y^2) * abs(sin(x) + cos(y)) + X = repeat(reshape(x, 1, :), length(y), 1) + Y = repeat(y, 1, length(x)) + Z = map(f, X, Y) + p1 = contour(x, y, f, fill = true) + p2 = contour(x, y, Z) + plot(p1, p2) + end + )], + ), + PlotExample( + "Pie", + "", + [:( + begin + x = ["Nerds", "Hackers", "Scientists"] + y = [0.4, 0.35, 0.25] + pie(x, y, title = "The Julia Community", l = 0.5) + end + )], + ), + PlotExample( + "3D", + "", + [ + :( + begin + n = 100 + ts = range(0, stop = 8π, length = n) + x = ts .* map(cos, ts) + y = 0.1ts .* map(sin, ts) + z = 1:n + plot( + x, + y, + z, + zcolor = reverse(z), + m = (10, 0.8, :blues, Plots.stroke(0)), + leg = false, + cbar = true, + w = 5, + ) + plot!(zeros(n), zeros(n), 1:n, w = 10) + end + ), + ], + ), + PlotExample( + "DataFrames", + "Plot using DataFrame column symbols.", + [ + :(using StatsPlots), # can't be inside begin block because @df gets expanded first + :( + begin + import RDatasets + iris = RDatasets.dataset("datasets", "iris") + @df iris scatter( + :SepalLength, + :SepalWidth, + group = :Species, + title = "My awesome plot", + xlabel = "Length", + ylabel = "Width", + marker = (0.5, [:cross :hex :star7], 12), + bg = RGB(0.2, 0.2, 0.2), + ) + end + ), + ], + ), + PlotExample( + "Groups and Subplots", + "", + [ + :( + begin + group = rand(map(i -> "group $i", 1:4), 100) + plot( + rand(100), + layout = @layout([a b; c]), + group = group, + linetype = [:bar :scatter :steppre], + linecolor = :match, + ) + end + ), + ], + ), + PlotExample( + "Polar Plots", + "", + [:( + begin + Θ = range(0, stop = 1.5π, length = 100) + r = abs.(0.1 * randn(100) + sin.(3Θ)) + plot(Θ, r, proj = :polar, m = 2) + end + )], + ), + PlotExample( + "Heatmap, categorical axes, and aspect_ratio", + "", + [:( + begin + xs = [string("x", i) for i = 1:10] + ys = [string("y", i) for i = 1:4] + z = float((1:4) * reshape(1:10, 1, :)) + heatmap(xs, ys, z, aspect_ratio = 1) + end + )], + ), + PlotExample( + "Layouts, margins, label rotation, title location", + "", + [ + :( + begin + using Plots.PlotMeasures # for Measures, e.g. mm and px + plot( + rand(100, 6), + layout = @layout([a b; c]), + title = ["A" "B" "C"], + title_location = :left, + left_margin = [20mm 0mm], + bottom_margin = 10px, + xrotation = 60, + ) + end + ), + ], + ), + PlotExample( + "Boxplot and Violin series recipes", + "", + [ + :(using StatsPlots), # can't be inside begin block because @df gets expanded first + :( + begin + import RDatasets + singers = RDatasets.dataset("lattice", "singer") + @df singers violin( + :VoicePart, + :Height, + line = 0, + fill = (0.2, :blue), + ) + @df singers boxplot!( + :VoicePart, + :Height, + line = (2, :black), + fill = (0.3, :orange), + ) + end + ), + ], + ), + PlotExample( + "Animation with subplots", + "The `layout` macro can be used to create an animation with subplots.", + [ + :( + begin + l = @layout([[a; b] c]) + p = plot( + plot([sin, cos], 1, leg = false), + scatter([atan, cos], 1, leg = false), + plot(log, 1, xlims = (1, 10π), ylims = (0, 5), leg = false), + layout = l, + ) -PlotExample("Parametric plots", - "Plot function pair (x(u), y(u)).", - [:(begin - plot(sin, x->sin(2x), 0, 2π, line=4, leg=false, fill=(0,:orange)) - end)] -), - -PlotExample("Colors", -""" -Access predefined palettes (or build your own with the `colorscheme` method). -Line/marker colors are auto-generated from the plot's palette, unless overridden. Set -the `z` argument to turn on series gradients. -""", - [:(begin -y = rand(100) -plot(0:10:100,rand(11,4),lab="lines",w=3,palette=:grays,fill=0, α=0.6) -scatter!(y, zcolor=abs.(y.-0.5), m=(:heat,0.8,Plots.stroke(1,:green)), ms=10*abs.(y.-0.5).+4, - lab="grad") - end)] -), - -PlotExample("Global", -""" -Change the guides/background/limits/ticks. Convenience args `xaxis` and `yaxis` allow -you to pass a tuple or value which will be mapped to the relevant args automatically. -The `xaxis` below will be replaced with `xlabel` and `xlims` args automatically during -the preprocessing step. You can also use shorthand functions: `title!`, `xaxis!`, -`yaxis!`, `xlabel!`, `ylabel!`, `xlims!`, `ylims!`, `xticks!`, `yticks!` -""", - [:(begin -using Statistics -y = rand(20,3) -plot(y, xaxis=("XLABEL",(-5,30),0:2:20,:flip), background_color = RGB(0.2,0.2,0.2), - leg=false) -hline!(mean(y, dims = 1)+rand(1,3), line=(4,:dash,0.6,[:lightgreen :green :darkgreen])) -vline!([5,10]) -title!("TITLE") -yaxis!("YLABEL", :log10) - end)] -), - -# PlotExample("Two-axis", -# "Use the `axis` arguments.", -# [ -# :(plot(Vector[randn(100), randn(100)*100], axis = [:l :r], ylabel="LEFT", yrightlabel="RIGHT", xlabel="X", title="TITLE")) -# ]), - -PlotExample("Images", - "Plot an image. y-axis is set to flipped", - [:(begin - import FileIO - path = download("http://juliaplots.org/PlotReferenceImages.jl/Plots/pyplot/0.7.0/ref1.png") - img = FileIO.load(path) - plot(img) - end)] -), - -PlotExample("Arguments", -""" -Plot multiple series with different numbers of points. Mix arguments that apply to all -series (marker/markersize) with arguments unique to each series (colors). Special -arguments `line`, `marker`, and `fill` will automatically figure out what arguments to -set (for example, we are setting the `linestyle`, `linewidth`, and `color` arguments with -`line`.) Note that we pass a matrix of colors, and this applies the colors to each -series. -""", - [:(begin - ys = Vector[rand(10), rand(20)] - plot(ys, color=[:black :orange], line=(:dot,4), marker=([:hex :d],12,0.8,Plots.stroke(3,:gray))) - end)] -), - -PlotExample("Build plot in pieces", - "Start with a base plot...", - [:(begin - plot(rand(100)/3, reg=true, fill=(0,:green)) - end)] -), - -PlotExample("", - "and add to it later.", - [:(begin - scatter!(rand(100), markersize=6, c=:orange) - end)] -), - -PlotExample("Histogram2D", - "", - [:(begin - histogram2d(randn(10000), randn(10000), nbins=20) - end)] -), - -PlotExample("Line types", - "", - [:(begin - linetypes = [:path :steppre :steppost :sticks :scatter] - n = length(linetypes) - x = Vector[sort(rand(20)) for i in 1:n] - y = rand(20,n) - plot(x, y, line=(linetypes,3), lab=map(string,linetypes), ms=15) - end)] -), - -PlotExample("Line styles", - "", - [:(begin -styles = filter(s -> s in Plots.supported_styles(), - [:solid, :dash, :dot, :dashdot, :dashdotdot]) -styles = reshape(styles, 1, length(styles)) # Julia 0.6 unfortunately gives an error when transposing symbol vectors -n = length(styles) -y = cumsum(randn(20,n), dims = 1) -plot(y, line = (5, styles), label = map(string,styles), legendtitle = "linestyle") - end)] -), - -PlotExample("Marker types", - "", - [:(begin - markers = filter(m -> m in Plots.supported_markers(), Plots._shape_keys) - markers = reshape(markers, 1, length(markers)) - n = length(markers) - x = range(0, stop=10, length=n+2)[2:end-1] - y = repeat(reshape(reverse(x),1,:), n, 1) - scatter(x, y, m=(8,:auto), lab=map(string,markers), bg=:linen, xlim=(0,10), ylim=(0,10)) - end)] -), - -PlotExample("Bar", - "`x` is the midpoint of the bar. (todo: allow passing of edges instead of midpoints)", - [:(begin - bar(randn(99)) - end)] -), - -PlotExample("Histogram", - "", - [:(begin - histogram(randn(1000), bins = :scott, weights = repeat(1:5, outer = 200)) - end)] -), - -PlotExample("Subplots", -""" -Use the `layout` keyword, and optionally the convenient `@layout` macro to generate -arbitrarily complex subplot layouts. -""", - [:(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=:none) - end)] -), - -PlotExample("Adding to subplots", -""" -Note here the automatic grid layout, as well as the order in which new series are added -to the plots. -""", - [:(begin -plot(Plots.fakedata(100,10), layout=4, palette=[:grays :blues :heat :lightrainbow], - bg_inside=[:orange :pink :darkblue :black]) - end)] -), - -PlotExample("", - "", - [:(begin - using Random - Random.seed!(111) - plot!(Plots.fakedata(100,10)) - end)] -), - -PlotExample("Open/High/Low/Close", -""" -Create an OHLC chart. Pass in a list of (open,high,low,close) tuples as your `y` -argument. This uses recipes to first convert the tuples to OHLC objects, and -subsequently create a :path series with the appropriate line segments. -""", - [:(begin -n=20 -hgt=rand(n).+1 -bot=randn(n) -openpct=rand(n) -closepct=rand(n) -y = OHLC[(openpct[i]*hgt[i]+bot[i], bot[i]+hgt[i], bot[i], - closepct[i]*hgt[i]+bot[i]) for i in 1:n] -ohlc(y) - end)] -), - -PlotExample("Annotations", -""" -The `annotations` keyword is used for text annotations in data-coordinates. Pass in a -tuple (x,y,text) or a vector of annotations. `annotate!(ann)` is shorthand for `plot!(; -annotation=ann)`. Series annotations are used for annotating individual data points. -They require only the annotation... x/y values are computed. A `PlotText` object can be -build with the method `text(string, attr...)`, which wraps font and color attributes. -""", - [:(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"))]) -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)]) - end)] -), - -PlotExample("Custom Markers", -"""A `Plots.Shape` is a light wrapper around vertices of a polygon. For supported -backends, pass arbitrary polygons as the marker shapes. Note: The center is (0,0) and -the size is expected to be rougly the area of the unit circle. -""", - [:(begin -verts = [(-1.0,1.0),(-1.28,0.6),(-0.2,-1.4),(0.2,-1.4),(1.28,0.6),(1.0,1.0), - (-1.0,1.0),(-0.2,-0.6),(0.0,-0.2),(-0.4,0.6),(1.28,0.6),(0.2,-1.4), - (-0.2,-1.4),(0.6,0.2),(-0.2,0.2),(0.0,-0.2),(0.2,0.2),(-0.2,-0.6)] -x = 0.1:0.2:0.9 -y = 0.7rand(5).+0.15 -plot(x, y, line = (3,:dash,:lightblue), marker = (Shape(verts),30,RGBA(0,0,0,0.2)), - bg=:pink, fg=:darkblue, xlim = (0,1), ylim=(0,1), leg=false) - end)] -), - -PlotExample("Contours", -""" -Any value for fill works here. We first build a filled contour from a function, then an -unfilled contour from a matrix. -""", - [:(begin - x = 1:0.5:20 - y = 1:0.5:10 - f(x,y) = (3x+y^2)*abs(sin(x)+cos(y)) - X = repeat(reshape(x,1,:), length(y), 1) - Y = repeat(y, 1, length(x)) - Z = map(f, X, Y) - p1 = contour(x, y, f, fill=true) - p2 = contour(x, y, Z) - plot(p1, p2) - end)] -), - -PlotExample("Pie", - "", - [:(begin - x = ["Nerds", "Hackers", "Scientists"] - y = [0.4, 0.35, 0.25] - pie(x, y, title="The Julia Community", l=0.5) - end)] -), - -PlotExample("3D", - "", - [:(begin - n = 100 - ts = range(0, stop=8π, length=n) - x = ts .* map(cos,ts) - y = 0.1ts .* map(sin,ts) - z = 1:n - plot(x, y, z, zcolor=reverse(z), m=(10,0.8,:blues,Plots.stroke(0)), leg=false, cbar=true, w=5) - plot!(zeros(n),zeros(n),1:n, w=10) - end)] -), - -PlotExample("DataFrames", - "Plot using DataFrame column symbols.", - [:(using StatsPlots), # can't be inside begin block because @df gets expanded first - :(begin - import RDatasets - iris = RDatasets.dataset("datasets", "iris") - @df iris scatter(:SepalLength, :SepalWidth, group=:Species, - title = "My awesome plot", xlabel = "Length", ylabel = "Width", - marker = (0.5, [:cross :hex :star7], 12), bg=RGB(.2,.2,.2)) - end)] -), - -PlotExample("Groups and Subplots", - "", - [:(begin - group = rand(map(i->"group $i",1:4),100) - plot(rand(100), layout=@layout([a b;c]), group=group, - linetype=[:bar :scatter :steppre], linecolor = :match) - end)] -), - -PlotExample("Polar Plots", - "", - [:(begin - Θ = range(0, stop=1.5π, length=100) - r = abs.(0.1randn(100)+sin.(3Θ)) - plot(Θ, r, proj=:polar, m=2) - end)] -), - -PlotExample("Heatmap, categorical axes, and aspect_ratio", - "", - [:(begin - xs = [string("x",i) for i=1:10] - ys = [string("y",i) for i=1:4] - z = float((1:4)*reshape(1:10,1,:)) - heatmap(xs, ys, z, aspect_ratio=1) - end)] -), - -PlotExample("Layouts, margins, label rotation, title location", - "", - [:(begin - using Plots.PlotMeasures # for Measures, e.g. mm and px - plot(rand(100,6),layout=@layout([a b; c]),title=["A" "B" "C"], - title_location=:left, left_margin=[20mm 0mm], - bottom_margin=10px, xrotation=60) - end)] -), - -PlotExample("Boxplot and Violin series recipes", - "", - [:(using StatsPlots), # can't be inside begin block because @df gets expanded first - :(begin - import RDatasets - singers = RDatasets.dataset("lattice", "singer") - @df singers violin(:VoicePart, :Height, line = 0, fill = (0.2, :blue)) - @df singers boxplot!(:VoicePart, :Height, line = (2,:black), fill = (0.3, :orange)) - end)] -), - -PlotExample("Animation with subplots", - "The `layout` macro can be used to create an animation with subplots.", - [:(begin - l = @layout([[a; b] c]) - p = plot(plot([sin,cos],1,leg=false), - scatter([atan,cos],1,leg=false), - plot(log,1,xlims=(1,10π),ylims=(0,5),leg=false),layout=l) - - anim = Animation() - for x = range(1, stop=10π, length=100) - plot(push!(p,x,Float64[sin(x),cos(x),atan(x),cos(x),log(x)])) - frame(anim) - end - end)] -), - -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`. -""", - [:(begin - using SparseArrays - a = spdiagm(0 => ones(50), 1 => ones(49), -1 => ones(49), 10 => ones(40), -10 => ones(40)) - b = spdiagm(0 => 1:50, 1 => 1:49, -1 => 1:49, 10 => 1:40, -10 => 1:40) - plot(spy(a), spy(b), title = ["Unique nonzeros" "Different nonzeros"]) - 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 - scatter(fill(randn(10), 6), fill(randn(10), 6), - framestyle = [:box :semi :origin :zerolines :grid :none], - title = [":box" ":semi" ":origin" ":zerolines" ":grid" ":none"], - color = permutedims(1:6), layout = 6, label = "", markerstrokewidth = 0, - ticks = -2:2) - end)] -), - -PlotExample("Lines and markers with varying colors", -""" -You can use the `line_z` and `marker_z` properties to associate a color with -each line segment or marker in the plot. -""", - [:(begin - t = range(0, stop=1, length=100) - θ = 6π .* t - x = t .* cos.(θ) - y = t .* sin.(θ) - p1 = plot(x, y, line_z=t, linewidth=3, legend=false) - p2 = scatter(x, y, marker_z=+, color=:bluesreds, legend=false) - plot(p1, p2) - end)] -), - -PlotExample("Portfolio Composition maps", -""" -see: http://stackoverflow.com/a/37732384/5075246 -""", - [:(begin - using Random - Random.seed!(111) - tickers = ["IBM", "Google", "Apple", "Intel"] - N = 10 - D = length(tickers) - weights = rand(N,D) - weights ./= sum(weights, dims = 2) - returns = sort!((1:N) + D*randn(N)) - - portfoliocomposition(weights, returns, labels = permutedims(tickers)) - end)] -), - -PlotExample("Ribbons", - """ - Ribbons can be added to lines via the `ribbon` keyword; - you can pass a tuple of arrays (upper and lower bounds), - a single Array (for symmetric ribbons), a Function, or a number. - """, - [:(begin - plot( - plot(0:10; ribbon = (LinRange(0, 2, 10), LinRange(0, 1, 10))), - plot(0:10; ribbon = 0:0.5:5), - plot(0:10; ribbon = sqrt), - plot(0:10; ribbon = 1), - ) - end)] -), - -PlotExample("Histogram2D (complex values)", - "", - [:(begin - n = 10_000 - x = exp.(0.1randn(n) .+ randn(n).*(im)) - histogram2d(x, nbins=(20,40), show_empty_bins=true, - normed=true, aspect_ratio=1) - end)] -), - -PlotExample("Unconnected lines using `missing` or `NaN`", -""" -Missing values and non-finite values, including `NaN`, are not plotted. -Instead, lines are separated into segments at these values. -""", - [:(begin - x,y = [1,2,2,1,1], [1,2,1,2,1] - plot( - plot([rand(5); NaN; rand(5); NaN; rand(5)]), - plot([1,missing,2,3], marker=true), - plot([x; NaN; x.+2], [y; NaN; y.+1], arrow=2), - plot([1, 2+3im, Inf, 4im, 3, -Inf*im, 0, 3+3im], marker=true), - legend=false - ) - end)] -), + anim = Animation() + for x in range(1, stop = 10π, length = 100) + plot(push!( + p, + x, + Float64[sin(x), cos(x), atan(x), cos(x), log(x)], + )) + frame(anim) + end + end + ), + ], + ), + 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`. + """, + [ + :( + begin + using SparseArrays + a = spdiagm( + 0 => ones(50), + 1 => ones(49), + -1 => ones(49), + 10 => ones(40), + -10 => ones(40), + ) + b = spdiagm( + 0 => 1:50, + 1 => 1:49, + -1 => 1:49, + 10 => 1:40, + -10 => 1:40, + ) + plot( + spy(a), + spy(b), + title = ["Unique nonzeros" "Different nonzeros"], + ) + 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 + scatter( + fill(randn(10), 6), + fill(randn(10), 6), + framestyle = [:box :semi :origin :zerolines :grid :none], + title = [":box" ":semi" ":origin" ":zerolines" ":grid" ":none"], + color = permutedims(1:6), + layout = 6, + label = "", + markerstrokewidth = 0, + ticks = -2:2, + ) + end + ), + ], + ), + PlotExample( + "Lines and markers with varying colors", + """ + You can use the `line_z` and `marker_z` properties to associate a color with + each line segment or marker in the plot. + """, + [ + :( + begin + t = range(0, stop = 1, length = 100) + θ = 6π .* t + x = t .* cos.(θ) + y = t .* sin.(θ) + p1 = plot(x, y, line_z = t, linewidth = 3, legend = false) + p2 = scatter( + x, + y, + marker_z = +, + color = :bluesreds, + legend = false, + ) + plot(p1, p2) + end + ), + ], + ), + PlotExample( + "Portfolio Composition maps", + """ + see: http://stackoverflow.com/a/37732384/5075246 + """, + [ + :( + begin + using Random + Random.seed!(111) + tickers = ["IBM", "Google", "Apple", "Intel"] + N = 10 + D = length(tickers) + weights = rand(N, D) + weights ./= sum(weights, dims = 2) + returns = sort!((1:N) + D * randn(N)) + portfoliocomposition( + weights, + returns, + labels = permutedims(tickers), + ) + end + ), + ], + ), + PlotExample( + "Ribbons", + """ + Ribbons can be added to lines via the `ribbon` keyword; + you can pass a tuple of arrays (upper and lower bounds), + a single Array (for symmetric ribbons), a Function, or a number. + """, + [ + :( + begin + plot( + plot( + 0:10; + ribbon = (LinRange(0, 2, 10), LinRange(0, 1, 10)), + ), + plot(0:10; ribbon = 0:0.5:5), + plot(0:10; ribbon = sqrt), + plot(0:10; ribbon = 1), + ) + end + ), + ], + ), + PlotExample( + "Histogram2D (complex values)", + "", + [ + :( + begin + n = 10_000 + x = exp.(0.1 * randn(n) .+ randn(n) .* (im)) + histogram2d( + x, + nbins = (20, 40), + show_empty_bins = true, + normed = true, + aspect_ratio = 1, + ) + end + ), + ], + ), + PlotExample( + "Unconnected lines using `missing` or `NaN`", + """ + Missing values and non-finite values, including `NaN`, are not plotted. + Instead, lines are separated into segments at these values. + """, + [ + :( + begin + x, y = [1, 2, 2, 1, 1], [1, 2, 1, 2, 1] + plot( + plot([rand(5); NaN; rand(5); NaN; rand(5)]), + plot([1, missing, 2, 3], marker = true), + plot([x; NaN; x .+ 2], [y; NaN; y .+ 1], arrow = 2), + plot( + [1, 2 + 3im, Inf, 4im, 3, -Inf * im, 0, 3 + 3im], + marker = true, + ), + legend = false, + ) + end + ), + ], + ), + PlotExample( + "Lens", + "A lens lets you easily magnify a region of a plot. x and y coordinates refer to the to be magnified region and the via the `inset` keyword the subplot index and the bounding box (in relative coordinates) of the inset plot with the magnified plot can be specified. Additional attributes count for the inset plot.", + [ + quote + begin + plot( + [(0, 0), (0, 0.9), (1, 0.9), (2, 1), (3, 0.9), (80, 0)], + legend = :outertopright, + ) + plot!([(0, 0), (0, 0.9), (2, 0.9), (3, 1), (4, 0.9), (80, 0)]) + plot!([(0, 0), (0, 0.9), (3, 0.9), (4, 1), (5, 0.9), (80, 0)]) + plot!([(0, 0), (0, 0.9), (4, 0.9), (5, 1), (6, 0.9), (80, 0)]) + lens!( + [1, 6], + [0.9, 1.1], + inset = (1, bbox(0.5, 0.0, 0.4, 0.4)), + ) + end + end, + ], + ), ] # Some constants for PlotDocs and PlotReferenceImages @@ -517,21 +893,21 @@ _backend_skips = Dict( # make and display one plot function test_examples(pkgname::Symbol, idx::Int; debug = false, disp = true) - Plots._debugMode.on = debug - @info("Testing plot: $pkgname:$idx:$(_examples[idx].header)") - backend(pkgname) - backend() + Plots._debugMode.on = debug + @info("Testing plot: $pkgname:$idx:$(_examples[idx].header)") + backend(pkgname) + backend() - # prevent leaking variables (esp. functions) directly into Plots namespace - m = Module(:PlotExampleModule) - Base.eval(m, :(using Plots)) - map(exprs -> Base.eval(m, exprs), _examples[idx].exprs) + # prevent leaking variables (esp. functions) directly into Plots namespace + m = Module(:PlotExampleModule) + Base.eval(m, :(using Plots)) + map(exprs -> Base.eval(m, exprs), _examples[idx].exprs) - plt = current() - if disp - gui(plt) - end - plt + plt = current() + if disp + gui(plt) + end + plt end # generate all plots and create a dict mapping idx --> plt @@ -542,23 +918,29 @@ test_examples(pkgname[, idx]; debug = false, disp = true, sleep = 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 - plts = Dict() - for i in eachindex(_examples) - only !== nothing && !(i in only) && continue - i in skip && continue - try - plt = test_examples(pkgname, i, debug=debug, disp=disp) - plts[i] = plt - catch ex - # TODO: put error info into markdown? - @warn("Example $pkgname:$i:$(_examples[i].header) failed with: $ex") +function test_examples( + pkgname::Symbol; + debug = false, + disp = true, + sleep = nothing, + skip = [], + only = nothing, +) + Plots._debugMode.on = debug + plts = Dict() + for i in eachindex(_examples) + only !== nothing && !(i in only) && continue + i in skip && continue + try + plt = test_examples(pkgname, i, debug = debug, disp = disp) + plts[i] = plt + catch ex + # TODO: put error info into markdown? + @warn("Example $pkgname:$i:$(_examples[i].header) failed with: $ex") + end + if sleep !== nothing + Base.sleep(sleep) + end end - if sleep !== nothing - Base.sleep(sleep) - end - end - plts + plts end diff --git a/src/recipes.jl b/src/recipes.jl index 89343fd5..dc94c1a9 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -44,7 +44,7 @@ end # ---------------------------------------------------------------------------------- -num_series(x::AMat) = size(x,2) +num_series(x::AMat) = size(x, 2) num_series(x) = 1 RecipesBase.apply_recipe(plotattributes::AKW, ::Type{T}, plt::AbstractPlot) where {T} = throw(MethodError(T, "Unmatched plot recipe: $T")) @@ -55,13 +55,26 @@ RecipesBase.apply_recipe(plotattributes::AKW, ::Type{T}, plt::AbstractPlot) wher # for seriestype `line`, need to sort by x values const POTENTIAL_VECTOR_ARGUMENTS = [ - :seriescolor, :seriesalpha, - :linecolor, :linealpha, :linewidth, :linestyle, :line_z, - :fillcolor, :fillalpha, :fill_z, - :markercolor, :markeralpha, :markershape, :marker_z, - :markerstrokecolor, :markerstrokealpha, - :yerror, :yerror, - :series_annotations, :fillrange + :seriescolor, + :seriesalpha, + :linecolor, + :linealpha, + :linewidth, + :linestyle, + :line_z, + :fillcolor, + :fillalpha, + :fill_z, + :markercolor, + :markeralpha, + :markershape, + :marker_z, + :markerstrokecolor, + :markerstrokealpha, + :yerror, + :yerror, + :series_annotations, + :fillrange, ] @recipe function f(::Type{Val{:line}}, x, y, z) @@ -99,7 +112,7 @@ end @recipe function f(::Type{Val{:hline}}, x, y, z) n = length(y) newx = repeat(Float64[-1, 1, NaN], n) - newy = vec(Float64[yi for i=1:3,yi=y]) + newy = vec(Float64[yi for i = 1:3, yi in y]) x := newx y := newy seriestype := :straightline @@ -109,7 +122,7 @@ end @recipe function f(::Type{Val{:vline}}, x, y, z) n = length(y) - newx = vec(Float64[yi for i=1:3,yi=y]) + newx = vec(Float64[yi for i = 1:3, yi in y]) newy = repeat(Float64[-1, 1, NaN], n) x := newx y := newy @@ -121,7 +134,7 @@ end @recipe function f(::Type{Val{:hspan}}, x, y, z) n = div(length(y), 2) newx = repeat([-Inf, Inf, Inf, -Inf, NaN], outer = n) - newy = vcat([[y[2i-1], y[2i-1], y[2i], y[2i], NaN] for i in 1:n]...) + newy = vcat([[y[2i - 1], y[2i - 1], y[2i], y[2i], NaN] for i = 1:n]...) linewidth --> 0 x := newx y := newy @@ -132,7 +145,7 @@ end @recipe function f(::Type{Val{:vspan}}, x, y, z) n = div(length(y), 2) - newx = vcat([[y[2i-1], y[2i-1], y[2i], y[2i], NaN] for i in 1:n]...) + newx = vcat([[y[2i - 1], y[2i - 1], y[2i], y[2i], NaN] for i = 1:n]...) newy = repeat([-Inf, Inf, Inf, -Inf, NaN], outer = n) linewidth --> 0 x := newx @@ -156,7 +169,7 @@ end primary := false () end -() + () end @deps scatterpath path scatter @@ -169,7 +182,7 @@ function make_steps(x::AbstractArray, st) n = length(x) n == 0 && return zeros(0) newx = zeros(2n - 1) - for i in 1:n + for i = 1:n idx = 2i - 1 newx[idx] = x[i] if i > 1 @@ -249,10 +262,10 @@ end end end newx, newy = zeros(3n), zeros(3n) - for i=1:n - rng = 3i-2:3i + for i = 1:n + rng = (3i - 2):(3i) newx[rng] = [x[i], x[i], NaN] - newy[rng] = [_cycle(fr,i), y[i], NaN] + newy[rng] = [_cycle(fr, i), y[i], NaN] end x := newx y := newy @@ -282,16 +295,16 @@ end # get the value of the curve point at position t function bezier_value(pts::AVec, t::Real) val = 0.0 - n = length(pts)-1 - for (i,p) in enumerate(pts) - val += p * binomial(n, i-1) * (1-t)^(n-i+1) * t^(i-1) + n = length(pts) - 1 + for (i, p) in enumerate(pts) + val += p * binomial(n, i - 1) * (1 - t)^(n - i + 1) * t^(i - 1) end val end # create segmented bezier curves in place of line segments @recipe function f(::Type{Val{:curves}}, x, y, z; npoints = 30) - args = z !== nothing ? (x,y,z) : (x,y) + args = z !== nothing ? (x, y, z) : (x, y) newx, newy = zeros(0), zeros(0) fr = plotattributes[:fillrange] newfr = fr !== nothing ? zeros(0) : nothing @@ -304,13 +317,13 @@ end for rng in iter_segments(args...) length(rng) < 2 && continue ts = range(0, stop = 1, length = npoints) - nanappend!(newx, map(t -> bezier_value(_cycle(x,rng), t), ts)) - nanappend!(newy, map(t -> bezier_value(_cycle(y,rng), t), ts)) + nanappend!(newx, map(t -> bezier_value(_cycle(x, rng), t), ts)) + nanappend!(newy, map(t -> bezier_value(_cycle(y, rng), t), ts)) if z !== nothing - nanappend!(newz, map(t -> bezier_value(_cycle(z,rng), t), ts)) + nanappend!(newz, map(t -> bezier_value(_cycle(z, rng), t), ts)) end if fr !== nothing - nanappend!(newfr, map(t -> bezier_value(_cycle(fr,rng), t), ts)) + nanappend!(newfr, map(t -> bezier_value(_cycle(fr, rng), t), ts)) end # if lz !== nothing # lzrng = _cycle(lz, rng) # the line_z's for this segment @@ -343,14 +356,15 @@ end # create a bar plot as a filled step function @recipe function f(::Type{Val{:bar}}, x, y, z) - procx, procy, xscale, yscale, baseline = _preprocess_barlike(plotattributes, x, y) + procx, procy, xscale, yscale, baseline = + _preprocess_barlike(plotattributes, x, y) nx, ny = length(procx), length(procy) axis = plotattributes[:subplot][isvertical(plotattributes) ? :xaxis : :yaxis] - cv = [discrete_value!(axis, xi)[1] for xi=procx] + cv = [discrete_value!(axis, xi)[1] for xi in procx] procx = if nx == ny cv elseif nx == ny + 1 - 0.5diff(cv) + cv[1:end-1] + 0.5 * diff(cv) + cv[1:(end - 1)] else error("bar recipe: x must be same length as y (centers), or one more than y (edges).\n\t\tlength(x)=$(length(x)), length(y)=$(length(y))") end @@ -359,12 +373,12 @@ end bw = plotattributes[:bar_width] hw = if bw === nothing if nx > 1 - 0.5*_bar_width*ignorenan_minimum(filter(x->x>0, diff(procx))) + 0.5 * _bar_width * ignorenan_minimum(filter(x -> x > 0, diff(procx))) else 0.5 * _bar_width end else - Float64[0.5_cycle(bw,i) for i=eachindex(procx)] + Float64[0.5 * _cycle(bw, i) for i in eachindex(procx)] end # make fillto a vector... default fills to 0 @@ -378,13 +392,20 @@ end # create the bar shapes by adding x/y segments xseg, yseg = Segments(), Segments() - for i=1:ny + for i = 1:ny yi = procy[i] if !isnan(yi) center = procx[i] - hwi = _cycle(hw,i) - fi = _cycle(fillto,i) - push!(xseg, center-hwi, center-hwi, center+hwi, center+hwi, center-hwi) + hwi = _cycle(hw, i) + fi = _cycle(fillto, i) + push!( + xseg, + center - hwi, + center - hwi, + center + hwi, + center + hwi, + center - hwi, + ) push!(yseg, yi, fi, fi, yi, yi) end end @@ -415,8 +436,8 @@ end m, n = size(z.surf) x_pts, y_pts = fill(NaN, 6 * m * n), fill(NaN, 6 * m * n) fz = zeros(m * n) - for i in 1:m # y - for j in 1:n # x + for i = 1:m # y + for j = 1:n # x k = (j - 1) * m + i inds = (6 * (k - 1) + 1):(6 * k - 1) x_pts[inds] .= [xe[j], xe[j + 1], xe[j + 1], xe[j], xe[j]] @@ -440,13 +461,17 @@ end # --------------------------------------------------------------------------- # Histograms -_bin_centers(v::AVec) = (v[1:end-1] + v[2:end]) / 2 +_bin_centers(v::AVec) = (v[1:(end - 1)] + v[2:end]) / 2 _is_positive(x) = (x > 0) && !(x ≈ 0) _positive_else_nan(::Type{T}, x::Real) where {T} = _is_positive(x) ? T(x) : T(NaN) -function _scale_adjusted_values(::Type{T}, V::AbstractVector, scale::Symbol) where T<:AbstractFloat +function _scale_adjusted_values( + ::Type{T}, + V::AbstractVector, + scale::Symbol, +) where {T<:AbstractFloat} if scale in _logScales [_positive_else_nan(T, x) for x in V] else @@ -455,7 +480,7 @@ function _scale_adjusted_values(::Type{T}, V::AbstractVector, scale::Symbol) whe end -function _binbarlike_baseline(min_value::T, scale::Symbol) where T<:Real +function _binbarlike_baseline(min_value::T, scale::Symbol) where {T<:Real} if (scale in _logScales) !isnan(min_value) ? min_value / T(_logScaleBases[scale]^log10(2)) : T(1E-3) else @@ -464,7 +489,11 @@ function _binbarlike_baseline(min_value::T, scale::Symbol) where T<:Real end -function _preprocess_binbarlike_weights(::Type{T}, w, wscale::Symbol) where T<:AbstractFloat +function _preprocess_binbarlike_weights( + ::Type{T}, + w, + wscale::Symbol, +) where {T<:AbstractFloat} w_adj = _scale_adjusted_values(T, w, wscale) w_min = ignorenan_minimum(w_adj) w_max = ignorenan_maximum(w_adj) @@ -490,7 +519,8 @@ end @recipe function f(::Type{Val{:barbins}}, x, y, z) - edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, x, y) + edge, weights, xscale, yscale, baseline = + _preprocess_binlike(plotattributes, x, y) if (plotattributes[:bar_width] === nothing) bar_width := diff(edge) end @@ -503,8 +533,9 @@ end @recipe function f(::Type{Val{:scatterbins}}, x, y, z) - edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, x, y) - xerror := diff(edge)/2 + edge, weights, xscale, yscale, baseline = + _preprocess_binlike(plotattributes, x, y) + xerror := diff(edge) / 2 x := _bin_centers(edge) y := weights seriestype := :scatter @@ -513,7 +544,13 @@ end @deps scatterbins scatter -function _stepbins_path(edge, weights, baseline::Real, xscale::Symbol, yscale::Symbol) +function _stepbins_path( + edge, + weights, + baseline::Real, + xscale::Symbol, + yscale::Symbol, +) log_scale_x = xscale in _logScales log_scale_y = yscale in _logScales @@ -538,7 +575,7 @@ function _stepbins_path(edge, weights, baseline::Real, xscale::Symbol, yscale::S w, it_state_w = it_tuple_w if (log_scale_x && a ≈ 0) - a = oftype(a, b/_logScaleBases[xscale]^3) + a = oftype(a, b / _logScaleBases[xscale]^3) end if isnan(w) @@ -575,9 +612,11 @@ end @recipe function f(::Type{Val{:stepbins}}, x, y, z) - axis = plotattributes[:subplot][Plots.isvertical(plotattributes) ? :xaxis : :yaxis] + axis = + plotattributes[:subplot][Plots.isvertical(plotattributes) ? :xaxis : :yaxis] - edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, x, y) + edge, weights, xscale, yscale, baseline = + _preprocess_binlike(plotattributes, x, y) xpts, ypts = _stepbins_path(edge, weights, baseline, xscale, yscale) if !isvertical(plotattributes) @@ -607,9 +646,19 @@ end end Plots.@deps stepbins path -wand_edges(x...) = (@warn("Load the StatsPlots package in order to use :wand bins. Defaulting to :auto", once = true); :auto) +wand_edges(x...) = ( + @warn( + "Load the StatsPlots package in order to use :wand bins. Defaulting to :auto", + once = true + ); + :auto +) -function _auto_binning_nbins(vs::NTuple{N,AbstractVector}, dim::Integer; mode::Symbol = :auto) where N +function _auto_binning_nbins( + vs::NTuple{N,AbstractVector}, + dim::Integer; + mode::Symbol = :auto, +) where {N} max_bins = 10_000 _cl(x) = min(ceil(Int, max(x, one(x))), max_bins) _iqr(v) = (q = quantile(v, 0.75) - quantile(v, 0.25); q > 0 ? q : oftype(q, 1)) @@ -618,8 +667,13 @@ function _auto_binning_nbins(vs::NTuple{N,AbstractVector}, dim::Integer; mode::S n_samples = length(LinearIndices(first(vs))) # The nd estimator is the key to most automatic binning methods, and is modified for twodimensional histograms to include correlation - nd = n_samples^(1/(2+N)) - nd = N == 2 ? min(n_samples^(1/(2+N)), nd / (1-cor(first(vs), last(vs))^2)^(3//8)) : nd # the >2-dimensional case does not have a nice solution to correlations + nd = n_samples^(1 / (2 + N)) + nd = N == 2 ? + min( + n_samples^(1 / (2 + N)), + nd / (1 - cor(first(vs), last(vs))^2)^(3 // 8), + ) : + nd # the >2-dimensional case does not have a nice solution to correlations v = vs[dim] @@ -644,32 +698,52 @@ function _auto_binning_nbins(vs::NTuple{N,AbstractVector}, dim::Integer; mode::S end end -_hist_edge(vs::NTuple{N,AbstractVector}, dim::Integer, binning::Integer) where {N} = StatsBase.histrange(vs[dim], binning, :left) -_hist_edge(vs::NTuple{N,AbstractVector}, dim::Integer, binning::Symbol) where {N} = _hist_edge(vs, dim, _auto_binning_nbins(vs, dim, mode = binning)) -_hist_edge(vs::NTuple{N,AbstractVector}, dim::Integer, binning::AbstractVector) where {N} = binning +_hist_edge(vs::NTuple{N,AbstractVector}, dim::Integer, binning::Integer) where {N} = + StatsBase.histrange(vs[dim], binning, :left) +_hist_edge(vs::NTuple{N,AbstractVector}, dim::Integer, binning::Symbol) where {N} = + _hist_edge(vs, dim, _auto_binning_nbins(vs, dim, mode = binning)) +_hist_edge( + vs::NTuple{N,AbstractVector}, + dim::Integer, + binning::AbstractVector, +) where {N} = binning -_hist_edges(vs::NTuple{N,AbstractVector}, binning::NTuple{N, Any}) where {N} = +_hist_edges(vs::NTuple{N,AbstractVector}, binning::NTuple{N,Any}) where {N} = map(dim -> _hist_edge(vs, dim, binning[dim]), (1:N...,)) -_hist_edges(vs::NTuple{N,AbstractVector}, binning::Union{Integer, Symbol, AbstractVector}) where {N} = - map(dim -> _hist_edge(vs, dim, binning), (1:N...,)) +_hist_edges( + vs::NTuple{N,AbstractVector}, + binning::Union{Integer,Symbol,AbstractVector}, +) where {N} = map(dim -> _hist_edge(vs, dim, binning), (1:N...,)) _hist_norm_mode(mode::Symbol) = mode _hist_norm_mode(mode::Bool) = mode ? :pdf : :none _filternans(vs::NTuple{1,AbstractVector}) = filter!.(isfinite, vs) -function _filternans(vs::NTuple{N,AbstractVector}) where N - _invertedindex(v, not) = [j for (i,j) in enumerate(v) if !(i ∈ not)] +function _filternans(vs::NTuple{N,AbstractVector}) where {N} + _invertedindex(v, not) = [j for (i, j) in enumerate(v) if !(i ∈ not)] nots = union(Set.(findall.(!isfinite, vs))...) _invertedindex.(vs, Ref(nots)) end -function _make_hist(vs::NTuple{N,AbstractVector}, binning; normed = false, weights = nothing) where N +function _make_hist( + vs::NTuple{N,AbstractVector}, + binning; + normed = false, + weights = nothing, +) where {N} localvs = _filternans(vs) edges = _hist_edges(localvs, binning) - h = float( weights === nothing ? - StatsBase.fit(StatsBase.Histogram, localvs, edges, closed = :left) : - StatsBase.fit(StatsBase.Histogram, localvs, StatsBase.Weights(weights), edges, closed = :left) + h = float( + weights === nothing ? + StatsBase.fit(StatsBase.Histogram, localvs, edges, closed = :left) : + StatsBase.fit( + StatsBase.Histogram, + localvs, + StatsBase.Weights(weights), + edges, + closed = :left, + ), ) normalize!(h, mode = _hist_norm_mode(normed)) end @@ -682,7 +756,12 @@ end @deps histogram barhist @recipe function f(::Type{Val{:barhist}}, x, y, z) - h = _make_hist((y,), plotattributes[:bins], normed = plotattributes[:normalize], weights = plotattributes[:weights]) + h = _make_hist( + (y,), + plotattributes[:bins], + normed = plotattributes[:normalize], + weights = plotattributes[:weights], + ) x := h.edges[1] y := h.weights seriestype := :barbins @@ -691,7 +770,12 @@ end @deps barhist barbins @recipe function f(::Type{Val{:stephist}}, x, y, z) - h = _make_hist((y,), plotattributes[:bins], normed = plotattributes[:normalize], weights = plotattributes[:weights]) + h = _make_hist( + (y,), + plotattributes[:bins], + normed = plotattributes[:normalize], + weights = plotattributes[:weights], + ) x := h.edges[1] y := h.weights seriestype := :stepbins @@ -700,7 +784,12 @@ end @deps stephist stepbins @recipe function f(::Type{Val{:scatterhist}}, x, y, z) - h = _make_hist((y,), plotattributes[:bins], normed = plotattributes[:normalize], weights = plotattributes[:weights]) + h = _make_hist( + (y,), + plotattributes[:bins], + normed = plotattributes[:normalize], + weights = plotattributes[:weights], + ) x := h.edges[1] y := h.weights seriestype := :scatterbins @@ -709,19 +798,23 @@ end @deps scatterhist scatterbins -@recipe function f(h::StatsBase.Histogram{T, 1, E}) where {T, E} +@recipe function f(h::StatsBase.Histogram{T,1,E}) where {T,E} seriestype --> :barbins st_map = Dict( - :bar => :barbins, :scatter => :scatterbins, :step => :stepbins, - :steppost => :stepbins # :step can be mapped to :steppost in pre-processing + :bar => :barbins, + :scatter => :scatterbins, + :step => :stepbins, + :steppost => :stepbins, # :step can be mapped to :steppost in pre-processing ) - seriestype := get(st_map, plotattributes[:seriestype], plotattributes[:seriestype]) + seriestype := + get(st_map, plotattributes[:seriestype], plotattributes[:seriestype]) if plotattributes[:seriestype] == :scatterbins # Workaround, error bars currently not set correctly by scatterbins - edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, h.edges[1], h.weights) - xerror --> diff(h.edges[1])/2 + edge, weights, xscale, yscale, baseline = + _preprocess_binlike(plotattributes, h.edges[1], h.weights) + xerror --> diff(h.edges[1]) / 2 seriestype := :scatter (Plots._bin_centers(edge), weights) else @@ -730,7 +823,7 @@ end end -@recipe function f(hv::AbstractVector{H}) where H <: StatsBase.Histogram +@recipe function f(hv::AbstractVector{H}) where {H<:StatsBase.Histogram} for h in hv @series begin h @@ -769,7 +862,12 @@ Plots.@deps bins2d heatmap @recipe function f(::Type{Val{:histogram2d}}, x, y, z) - h = _make_hist((x, y), plotattributes[:bins], normed = plotattributes[:normalize], weights = plotattributes[:weights]) + h = _make_hist( + (x, y), + plotattributes[:bins], + normed = plotattributes[:normalize], + weights = plotattributes[:weights], + ) x := h.edges[1] y := h.edges[2] z := Surface(h.weights) @@ -779,7 +877,7 @@ end @deps histogram2d bins2d -@recipe function f(h::StatsBase.Histogram{T, 2, E}) where {T, E} +@recipe function f(h::StatsBase.Histogram{T,2,E}) where {T,E} seriestype --> :bins2d (h.edges[1], h.edges[2], Surface(h.weights)) end @@ -801,6 +899,91 @@ end # note: don't add dependencies because this really isn't a drop-in replacement +# --------------------------------------------------------------------------- +# lens! - magnify a region of a plot +lens!(args...;kwargs...) = plot!(args...; seriestype=:lens, kwargs...) +export lens! +@recipe function f(::Type{Val{:lens}}, plt::AbstractPlot) + sp_index, inset_bbox = plotattributes[:inset_subplots] + if !(width(inset_bbox) isa Measures.Length{:w,<:Real}) + throw(ArgumentError("Inset bounding box needs to in relative coordinates.")) + end + sp = plt.subplots[sp_index] + xl1, xl2 = xlims(plt.subplots[sp_index]) + bbx1 = xl1 + left(inset_bbox).value * (xl2 - xl1) + bbx2 = bbx1 + width(inset_bbox).value * (xl2 - xl1) + yl1, yl2 = ylims(plt.subplots[sp_index]) + bby1 = yl1 + (1 - bottom(inset_bbox).value) * (yl2 - yl1) + bby2 = bby1 + height(inset_bbox).value * (yl2 - yl1) + bbx = bbx1 + width(inset_bbox).value * (xl2 - xl1) / 2 + bby = bby1 + height(inset_bbox).value * (yl2 - yl1) / 2 + lens_index = last(plt.subplots)[:subplot_index] + 1 + x1, x2 = plotattributes[:x] + y1, y2 = plotattributes[:y] + seriestype := :path + label := "" + linecolor := :lightgray + bbx_mag = (x1 + x2) / 2 + bby_mag = (y1 + y2) / 2 + xi_lens, yi_lens = intersection_point(bbx_mag, bby_mag, bbx, bby, abs(bby2 - bby1), abs(bbx2 - bbx1)) + xi_mag, yi_mag = intersection_point(bbx, bby, bbx_mag, bby_mag, abs(y2 - y1), abs(x2 - x1)) + # add lines + if xl1 < xi_lens < xl2 && + yl1 < yi_lens < yl2 + @series begin + subplot := sp_index + x := [xi_mag, xi_lens] + y := [yi_mag, yi_lens] + () + end + end + # add magnification shape + @series begin + subplot := sp_index + x := [x1, x1, x2, x2, x1] + y := [y1, y2, y2, y1, y1] + () + end + # add subplot + for series in sp.series_list + @series begin + plotattributes = merge(plotattributes, copy(series.plotattributes)) + subplot := lens_index + label := "" + xlims := (x1, x2) + ylims := (y1, y2) + () + end + end + backup = copy(plotattributes) + empty!(plotattributes) + seriestype := :path + series_plotindex := backup[:series_plotindex] +end + +function intersection_point(xA, yA, xB, yB, h, w) + s = (yA - yB) / (xA - xB) + hh = h / 2 + hw = w / 2 + # left or right? + if -hh <= s * hw <= hh + if xA > xB + # right + return xB + hw, yB + s * hw + else # left + return xB - hw, yB - s * hw + end + # top or bot? + elseif -hw <= hh/s <= hw + if yA > yB + # top + return xB + hh/s, yB + hh + else + # bottom + return xB - hh/s, yB - hh + end + end +end # --------------------------------------------------------------------------- # contourf - filled contours @@ -855,7 +1038,11 @@ end @recipe function f(::Type{Val{:yerror}}, x, y, z) error_style!(plotattributes) markershape := :hline - plotattributes[:x], plotattributes[:y] = error_coords(plotattributes[:x], plotattributes[:y], error_zipit(plotattributes[:yerror])) + plotattributes[:x], plotattributes[:y] = error_coords( + plotattributes[:x], + plotattributes[:y], + error_zipit(plotattributes[:yerror]), + ) () end @deps yerror path @@ -863,7 +1050,11 @@ end @recipe function f(::Type{Val{:xerror}}, x, y, z) error_style!(plotattributes) markershape := :vline - plotattributes[:y], plotattributes[:x] = error_coords(plotattributes[:y], plotattributes[:x], error_zipit(plotattributes[:xerror])) + plotattributes[:y], plotattributes[:x] = error_coords( + plotattributes[:y], + plotattributes[:x], + error_zipit(plotattributes[:xerror]), + ) () end @deps xerror path @@ -898,15 +1089,15 @@ function quiver_using_arrows(plotattributes::AKW) first(vi), last(vi) elseif isscalar(vi) vi, vi - elseif isa(vi,Function) + elseif isa(vi, Function) vi(xi, yi) else error("unexpected vi type $(typeof(vi)) for quiver: $vi") end # add the points - nanappend!(x, [xi, xi+vx, NaN]) - nanappend!(y, [yi, yi+vy, NaN]) + nanappend!(x, [xi, xi + vx, NaN]) + nanappend!(y, [yi, yi + vy, NaN]) end plotattributes[:x], plotattributes[:y] = x, y @@ -936,7 +1127,7 @@ function quiver_using_hack(plotattributes::AKW) first(vi), last(vi) elseif isscalar(vi) vi, vi - elseif isa(vi,Function) + elseif isa(vi, Function) vi(xi, yi) else error("unexpected vi type $(typeof(vi)) for quiver: $vi") @@ -951,8 +1142,11 @@ function quiver_using_hack(plotattributes::AKW) U1 *= arrow_h U2 *= arrow_w - ppv = p+v - nanappend!(pts, P2[p, ppv-U1, ppv-U1+U2, ppv, ppv-U1-U2, ppv-U1]) + ppv = p + v + nanappend!( + pts, + P2[p, ppv - U1, ppv - U1 + U2, ppv, ppv - U1 - U2, ppv - U1], + ) end plotattributes[:x], plotattributes[:y] = Plots.unzip(pts[2:end]) @@ -977,32 +1171,28 @@ end "Represent Open High Low Close data (used in finance)" mutable struct OHLC{T<:Real} - open::T - high::T - low::T - close::T + open::T + high::T + low::T + close::T end Base.convert(::Type{OHLC}, tup::Tuple) = OHLC(tup...) # Base.tuple(ohlc::OHLC) = (ohlc.open, ohlc.high, ohlc.low, ohlc.close) # get one OHLC path function get_xy(o::OHLC, x, xdiff) - xl, xm, xr = x-xdiff, x, x+xdiff - ox = [xl, xm, NaN, - xm, xm, NaN, - xm, xr] - oy = [o.open, o.open, NaN, - o.low, o.high, NaN, - o.close, o.close] + xl, xm, xr = x - xdiff, x, x + xdiff + ox = [xl, xm, NaN, xm, xm, NaN, xm, xr] + oy = [o.open, o.open, NaN, o.low, o.high, NaN, o.close, o.close] ox, oy end # get the joined vector function get_xy(v::AVec{OHLC}, x = eachindex(v)) - xdiff = 0.3ignorenan_mean(abs.(diff(x))) + xdiff = 0.3 * ignorenan_mean(abs.(diff(x))) x_out, y_out = zeros(0), zeros(0) - for (i,ohlc) in enumerate(v) - ox,oy = get_xy(ohlc, x[i], xdiff) + for (i, ohlc) in enumerate(v) + ox, oy = get_xy(ohlc, x[i], xdiff) nanappend!(x_out, ox) nanappend!(y_out, oy) end @@ -1015,10 +1205,17 @@ end # to squash ambiguity warnings... @recipe f(x::AVec{Function}, v::AVec{OHLC}) = error() -@recipe f(x::AVec{Function}, v::AVec{Tuple{R1,R2,R3,R4}}) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = error() +@recipe f( + x::AVec{Function}, + v::AVec{Tuple{R1,R2,R3,R4}}, +) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = error() # this must be OHLC? -@recipe f(x::AVec, ohlc::AVec{Tuple{R1,R2,R3,R4}}) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = x, OHLC[OHLC(t...) for t in ohlc] +@recipe f( + x::AVec, + ohlc::AVec{Tuple{R1,R2,R3,R4}}, +) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = + x, OHLC[OHLC(t...) for t in ohlc] @recipe function f(x::AVec, v::AVec{OHLC}) seriestype := :path @@ -1056,11 +1253,11 @@ end @assert length(g.args) == 1 && typeof(g.args[1]) <: AbstractMatrix seriestype := :spy mat = g.args[1] - n,m = axes(mat) + n, m = axes(mat) Plots.SliceIt, m, n, Surface(mat) end -@recipe function f(::Type{Val{:spy}}, x,y,z) +@recipe function f(::Type{Val{:spy}}, x, y, z) yflip := true aspect_ratio := 1 rs, cs, zs = findnz(z.surf) @@ -1086,7 +1283,8 @@ end # ------------------------------------------------- "Adds ax+b... straight line over the current plot, without changing the axis limits" -abline!(plt::Plot, a, b; kw...) = plot!(plt, [0, 1], [b, b+a]; seriestype = :straightline, kw...) +abline!(plt::Plot, a, b; kw...) = + plot!(plt, [0, 1], [b, b + a]; seriestype = :straightline, kw...) abline!(args...; kw...) = abline!(current(), args...; kw...) @@ -1099,9 +1297,11 @@ datetimeformatter(dt) = string(DateTime(Dates.UTM(dt))) timeformatter(t) = string(Dates.Time(Dates.Nanosecond(t))) @recipe f(::Type{Date}, dt::Date) = (dt -> Dates.value(dt), dateformatter) -@recipe f(::Type{DateTime}, dt::DateTime) = (dt -> Dates.value(dt), datetimeformatter) +@recipe f(::Type{DateTime}, dt::DateTime) = + (dt -> Dates.value(dt), datetimeformatter) @recipe f(::Type{Dates.Time}, t::Dates.Time) = (t -> Dates.value(t), timeformatter) -@recipe f(::Type{P}, t::P) where P <: Dates.Period = (t -> Dates.value(t), t -> string(P(t))) +@recipe f(::Type{P}, t::P) where {P<:Dates.Period} = + (t -> Dates.value(t), t -> string(P(t))) # ------------------------------------------------- # Characters @@ -1111,7 +1311,7 @@ timeformatter(t) = string(Dates.Time(Dates.Nanosecond(t))) # ------------------------------------------------- # Complex Numbers -@recipe function f(A::Array{Complex{T}}) where T<:Number +@recipe function f(A::Array{Complex{T}}) where {T<:Number} xguide --> "Re(x)" yguide --> "Im(x)" real.(A), imag.(A) @@ -1120,10 +1320,10 @@ end # Splits a complex matrix to its real and complex parts # Reals defaults solid, imaginary defaults dashed # Label defaults are changed to match the real-imaginary reference / indexing -@recipe function f(x::AbstractArray{T},y::Array{Complex{T2}}) where {T<:Real,T2} - ylabel --> "Re(y)" - zlabel --> "Im(y)" - x,real.(y),imag.(y) +@recipe function f(x::AbstractArray{T}, y::Array{Complex{T2}}) where {T<:Real,T2} + ylabel --> "Re(y)" + zlabel --> "Im(y)" + x, real.(y), imag.(y) end @@ -1137,7 +1337,7 @@ end end library = PlotUtils.color_libraries[cl.args[1]] - z = sqrt.((1:15)*reshape(1:20,1,:)) + z = sqrt.((1:15) * reshape(1:20, 1, :)) seriestype := :heatmap ticks := nothing @@ -1161,7 +1361,7 @@ end if !(length(grad.args) == 1 && isa(grad.args[1], Symbol)) error("showgradient takes the name of a color gradient as a Symbol") end - z = sqrt.((1:15)*reshape(1:20,1,:)) + z = sqrt.((1:15) * reshape(1:20, 1, :)) seriestype := :heatmap ticks := nothing legend := false @@ -1184,9 +1384,9 @@ end weights = cumsum(weights, dims = 2) seriestype := :shape - # create a filled polygon for each item - for c=axes(weights,2) - sx = vcat(weights[:,c], c==1 ? zeros(n) : reverse(weights[:,c-1])) + # create a filled polygon for each item + for c in axes(weights, 2) + sx = vcat(weights[:, c], c == 1 ? zeros(n) : reverse(weights[:, c - 1])) sy = vcat(returns, reverse(returns)) @series Plots.isvertical(plotattributes) ? (sx, sy) : (sy, sx) end @@ -1205,13 +1405,13 @@ julia> areaplot(1:3, [1 2 3; 7 8 9; 4 5 6], seriescolor = [:red :green :blue], f @userplot AreaPlot @recipe function f(a::AreaPlot) - data = cumsum(a.args[end], dims=2) + data = cumsum(a.args[end], dims = 2) x = length(a.args) == 1 ? (axes(data, 1)) : a.args[1] seriestype := :line for i in axes(data, 2) @series begin - fillrange := i > 1 ? data[:,i-1] : 0 - x, data[:,i] + fillrange := i > 1 ? data[:, i - 1] : 0 + x, data[:, i] end end end diff --git a/src/series.jl b/src/series.jl index 6726de71..bba2f313 100644 --- a/src/series.jl +++ b/src/series.jl @@ -347,6 +347,7 @@ end seriestype := :heatmap yflip --> true cbar --> false + aspect_ratio --> :equal z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat) SliceIt, m, n, Surface(z) end diff --git a/src/utils.jl b/src/utils.jl index 3c6994f7..ef15b8b5 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -116,24 +116,11 @@ end function replace_image_with_heatmap(z::Array{T}) where T<:Colorant n, m = size(z) - # idx = 0 colors = ColorGradient(vec(z)) newz = reshape(range(0, stop=1, length=n*m), n, m) newz, colors - # newz = zeros(n, m) - # for i=1:n, j=1:m - # push!(colors, T(z[i,j]...)) - # newz[i,j] = idx / (n*m-1) - # idx += 1 - # end - # newz, ColorGradient(colors) end -function imageHack(plotattributes::AKW) - is_seriestype_supported(:heatmap) || error("Neither :image or :heatmap are supported!") - plotattributes[:seriestype] = :heatmap - plotattributes[:z], plotattributes[:fillcolor] = replace_image_with_heatmap(plotattributes[:z].surf) -end # --------------------------------------------------------------- "Build line segments for plotting" @@ -693,6 +680,10 @@ function get_markerstrokealpha(series, i::Int = 1) _cycle(series[:markerstrokealpha], i) end +function get_markerstrokewidth(series, i::Int = 1) + _cycle(series[:markerstrokewidth], i) +end + function has_attribute_segments(series::Series) # we want to check if a series needs to be split into segments just because # of its attributes @@ -706,6 +697,19 @@ function has_attribute_segments(series::Series) 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)) end +function get_aspect_ratio(sp) + aspect_ratio = sp[:aspect_ratio] + if aspect_ratio == :auto + aspect_ratio = :none + for series in series_list(sp) + if series[:seriestype] == :image + aspect_ratio = :equal + end + end + end + return aspect_ratio +end + # --------------------------------------------------------------- makekw(; kw...) = KW(kw) diff --git a/test/runtests.jl b/test/runtests.jl index 7539f496..037f7653 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -42,7 +42,31 @@ include("imgcomp.jl") 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 = is_ci() ? 1e-2 : 1e-3 + +## Uncomment the following lines to update reference images for different backends + +#= +@testset "GR" begin + image_comparison_facts(:gr, tol=img_tol, skip = Plots._backend_skips[:gr]) +end + +plotly() +@testset "Plotly" begin + image_comparison_facts(:plotly, tol=img_tol, skip = Plots._backend_skips[:plotlyjs]) +end + +pyplot() +@testset "PyPlot" begin + image_comparison_facts(:pyplot, tol=img_tol, skip = Plots._backend_skips[:pyplot]) +end + +pgfplots() +@testset "PGFPlots" begin + image_comparison_facts(:pgfplots, tol=img_tol, skip = Plots._backend_skips[:pgfplots]) +end +=# +## @testset "Backends" begin