diff --git a/src/axes.jl b/src/axes.jl index 4288cde3..a3c3293c 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -239,8 +239,13 @@ function get_ticks(axis::Axis) # discrete ticks... axis[:continuous_values], dvals elseif ticks == :auto - # compute optimal ticks and labels - optimal_ticks_and_labels(axis) + if ispolar(axis.sps[1]) && axis[:letter] == :x + #force theta axis to be full circle + (collect(0:pi/4:7pi/4), string.(0:45:315)) + else + # compute optimal ticks and labels + optimal_ticks_and_labels(axis) + end elseif typeof(ticks) <: Union{AVec, Int} # override ticks, but get the labels optimal_ticks_and_labels(axis, ticks) @@ -427,7 +432,16 @@ function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis) if !isfinite(amin) && !isfinite(amax) amin, amax = 0.0, 1.0 end - if should_widen + if ispolar(axis.sps[1]) + if axis[:letter] == :x + amin, amax = 0, 2pi + elseif lims == :auto + #widen max radius so ticks dont overlap with theta axis + amin, amax + 0.1 * abs(amax - amin) + else + amin, amax + end + elseif should_widen widen(amin, amax) else amin, amax diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 0b964aa0..9619f638 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -205,35 +205,62 @@ function gr_text(x, y, s) end end -function gr_polaraxes(rmin, rmax) +function gr_polaraxes(rmin::Real, rmax::Real, sp::Subplot) GR.savestate() - GR.setlinetype(GR.LINETYPE_SOLID) - GR.setlinecolorind(88) - tick = 0.5 * GR.tick(rmin, rmax) - n = round(Int, (rmax - rmin) / tick + 0.5) - for i in 0:n - r = float(i) / n - if i % 2 == 0 - GR.setlinecolorind(88) - if i > 0 - GR.drawarc(-r, r, -r, r, 0, 359) - end - GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) - x, y = GR.wctondc(0.05, r) - GR.text(x, y, string(signif(rmin + i * tick, 12))) - else - GR.setlinecolorind(90) - GR.drawarc(-r, r, -r, r, 0, 359) + xaxis = sp[:xaxis] + yaxis = sp[:yaxis] + + α = 0:45:315 + a = α .+ 90 + sinf = sind.(a) + cosf = cosd.(a) + rtick_values, rtick_labels = get_ticks(yaxis) + + #draw angular grid + if xaxis[:grid] + gr_set_line(xaxis[:gridlinewidth], xaxis[:gridstyle], xaxis[:foreground_color_grid]) + GR.settransparency(xaxis[:gridalpha]) + for i in 1:length(α) + GR.polyline([sinf[i], 0], [cosf[i], 0]) end end - for α in 0:45:315 - a = α + 90 - sinf = sin(a * pi / 180) - cosf = cos(a * pi / 180) - GR.polyline([sinf, 0], [cosf, 0]) - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_HALF) - x, y = GR.wctondc(1.1 * sinf, 1.1 * cosf) - GR.textext(x, y, string(α, "^o")) + + #draw radial grid + if yaxis[:grid] + gr_set_line(yaxis[:gridlinewidth], yaxis[:gridstyle], yaxis[:foreground_color_grid]) + GR.settransparency(yaxis[:gridalpha]) + for i in 1:length(rtick_values) + r = (rtick_values[i] - rmin) / (rmax - rmin) + if r <= 1.0 && r >= 0.0 + GR.drawarc(-r, r, -r, r, 0, 359) + end + end + GR.drawarc(-1, 1, -1, 1, 0, 359) + end + + #prepare to draw ticks + GR.settransparency(1) + GR.setlinecolorind(90) + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_HALF) + + #draw angular ticks + if xaxis[:showaxis] + GR.drawarc(-1, 1, -1, 1, 0, 359) + for i in 1:length(α) + x, y = GR.wctondc(1.1 * sinf[i], 1.1 * cosf[i]) + GR.textext(x, y, string((360-α[i])%360, "^o")) + end + end + + #draw radial ticks + if yaxis[:showaxis] + for i in 1:length(rtick_values) + r = (rtick_values[i] - rmin) / (rmax - rmin) + if r <= 1.0 && r >= 0.0 + x, y = GR.wctondc(0.05, r) + gr_text(x, y, _cycle(rtick_labels, i)) + end + end end GR.restorestate() end @@ -758,9 +785,9 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) elseif ispolar(sp) r = gr_set_viewport_polar() - rmin, rmax = GR.adjustrange(ignorenan_minimum(r), ignorenan_maximum(r)) - # rmin, rmax = axis_limits(sp[:yaxis]) - gr_polaraxes(rmin, rmax) + #rmin, rmax = GR.adjustrange(ignorenan_minimum(r), ignorenan_maximum(r)) + rmin, rmax = axis_limits(sp[:yaxis]) + gr_polaraxes(rmin, rmax, sp) elseif draw_axes if xmax > xmin && ymax > ymin diff --git a/src/backends/inspectdr.jl b/src/backends/inspectdr.jl index da7bc144..7d09045a 100644 --- a/src/backends/inspectdr.jl +++ b/src/backends/inspectdr.jl @@ -245,7 +245,7 @@ function _series_added(plt::Plot{InspectDRBackend}, series::Series) #No support for polar grid... but can still perform polar transformation: if ispolar(sp) Θ = x; r = y - x = r.*cos(Θ); y = r.*sin(Θ) + x = r.*cos.(Θ); y = r.*sin.(Θ) end # doesn't handle mismatched x/y - wrap data (pyplot behaviour): diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index 93acf765..8cfc5873 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -223,6 +223,9 @@ function pgf_series(sp::Subplot, series::Series) # If a marker_z is used pass it as third coordinate to a 2D plot. # See "Scatter Plots" in PGFPlots documentation d[:x], d[:y], d[:marker_z] + elseif ispolar(sp) + theta, r = filter_radial_data(d[:x], d[:y], axis_limits(sp[:yaxis])) + rad2deg.(theta), r else d[:x], d[:y] end @@ -295,19 +298,23 @@ function pgf_axis(sp::Subplot, letter) # grid on or off if axis[:grid] && framestyle != :none push!(style, "$(letter)majorgrids = true") + else + push!(style, "$(letter)majorgrids = false") end # limits # TODO: support zlims if letter != :z - lims = axis_limits(axis) + lims = ispolar(sp) && letter == :x ? rad2deg.(axis_limits(axis)) : axis_limits(axis) kw[Symbol(letter,:min)] = lims[1] kw[Symbol(letter,:max)] = lims[2] end if !(axis[:ticks] in (nothing, false, :none)) && framestyle != :none ticks = get_ticks(axis) - push!(style, string(letter, "tick = {", join(ticks[1],","), "}")) + #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, 415] : ticks[1] + push!(style, string(letter, "tick = {", join(tick_values,","), "}")) if axis[:showaxis] && axis[:scale] in (:ln, :log2, :log10) && axis[:ticks] == :auto # wrap the power part of label with } tick_labels = String[begin @@ -317,7 +324,8 @@ function pgf_axis(sp::Subplot, letter) end for label in ticks[2]] push!(style, string(letter, "ticklabels = {\$", join(tick_labels,"\$,\$"), "\$}")) elseif axis[:showaxis] - push!(style, string(letter, "ticklabels = {", join(ticks[2],","), "}")) + tick_labels = ispolar(sp) && letter == :x ? [ticks[2][3:end]..., "0", "45"] : ticks[2] + push!(style, string(letter, "ticklabels = {", join(tick_labels,","), "}")) else push!(style, string(letter, "ticklabels = {}")) end @@ -405,6 +413,9 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) axisf = PGFPlots.Axis if sp[:projection] == :polar axisf = PGFPlots.PolarAxis + #make radial axis vertical + kw[:xmin] = 90 + kw[:xmax] = 450 end # Search series for any gradient. In case one series uses a gradient set diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index b15e82c0..e9a662cc 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -291,6 +291,22 @@ function plotly_axis(axis::Axis, sp::Subplot) ax end +function plotly_polaraxis(axis::Axis) + ax = KW( + :visible => axis[:showaxis], + :showline => axis[:grid], + ) + + if axis[:letter] == :x + ax[:range] = rad2deg.(axis_limits(axis)) + else + ax[:range] = axis_limits(axis) + ax[:orientation] = -90 + end + + ax +end + function plotly_layout(plt::Plot) d_out = KW() @@ -345,6 +361,9 @@ function plotly_layout(plt::Plot) ), ), ) + elseif ispolar(sp) + d_out[Symbol("angularaxis$spidx")] = plotly_polaraxis(sp[:xaxis]) + d_out[Symbol("radialaxis$spidx")] = plotly_polaraxis(sp[:yaxis]) else d_out[Symbol("xaxis$spidx")] = plotly_axis(sp[:xaxis], sp) d_out[Symbol("yaxis$spidx")] = plotly_axis(sp[:yaxis], sp) @@ -698,8 +717,9 @@ end function plotly_polar!(d_out::KW, series::Series) # convert polar plots x/y to theta/radius if ispolar(series[:subplot]) - d_out[:t] = rad2deg(pop!(d_out, :x)) - d_out[:r] = pop!(d_out, :y) + theta, r = filter_radial_data(pop!(d_out, :x), pop!(d_out, :y), axis_limits(series[:subplot][:yaxis])) + d_out[:t] = rad2deg.(theta) + d_out[:r] = r end end diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 4a8c3c95..d325b21b 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -1054,6 +1054,9 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) end py_set_scale(ax, axis) py_set_lims(ax, axis) + if ispolar(sp) && letter == :y + ax[:set_rlabel_position](90) + end ticks = sp[:framestyle] == :none ? nothing : get_ticks(axis) # don't show the 0 tick label for the origin framestyle if sp[:framestyle] == :origin && length(ticks) > 1 @@ -1080,6 +1083,8 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) linewidth = axis[:gridlinewidth], alpha = axis[:gridalpha]) ax[:set_axisbelow](true) + else + pyaxis[:grid](false) end py_set_axis_colors(sp, ax, axis) end @@ -1088,7 +1093,11 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) if !sp[:xaxis][:showaxis] kw = KW() for dir in (:top, :bottom) - ax[:spines][string(dir)][:set_visible](false) + if ispolar(sp) + ax[:spines]["polar"][:set_visible](false) + else + ax[:spines][string(dir)][:set_visible](false) + end kw[dir] = kw[Symbol(:label,dir)] = "off" end ax[:xaxis][:set_tick_params](; which="both", kw...) @@ -1096,7 +1105,9 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) if !sp[:yaxis][:showaxis] kw = KW() for dir in (:left, :right) - ax[:spines][string(dir)][:set_visible](false) + if !ispolar(sp) + ax[:spines][string(dir)][:set_visible](false) + end kw[dir] = kw[Symbol(:label,dir)] = "off" end ax[:yaxis][:set_tick_params](; which="both", kw...) diff --git a/src/utils.jl b/src/utils.jl index 81cd3571..d19a2acb 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -363,15 +363,26 @@ end function convert_to_polar(x, y, r_extrema = calc_r_extrema(x, y)) rmin, rmax = r_extrema - phi, r = x, y + theta, r = filter_radial_data(x, y, r_extrema) r = (r - rmin) / (rmax - rmin) - n = max(length(phi), length(r)) - x = zeros(n) - y = zeros(n) + x = r.*cos.(theta) + y = r.*sin.(theta) + x, y +end + +# Filters radial data for points within the axis limits +function filter_radial_data(theta, r, r_extrema::Tuple{Real, Real}) + n = max(length(theta), length(r)) + rmin, rmax = r_extrema + x, y = zeros(n), zeros(n) for i in 1:n - x[i] = _cycle(r,i) * cos.(_cycle(phi,i)) - y[i] = _cycle(r,i) * sin.(_cycle(phi,i)) + x[i] = _cycle(theta, i) + y[i] = _cycle(r, i) end + points = map((a, b) -> (a, b), x, y) + filter!(a -> a[2] >= rmin && a[2] <= rmax, points) + x = map(a -> a[1], points) + y = map(a -> a[2], points) x, y end