From 4f171e3eb540e8fca41246708c3f88bb06550645 Mon Sep 17 00:00:00 2001 From: Andrew Palugniok Date: Thu, 2 Nov 2017 16:32:57 +0000 Subject: [PATCH 1/8] Add handling of polar axes. --- src/axes.jl | 20 +++++++++++++++++--- src/backends/pgfplots.jl | 7 +++++-- src/backends/plotly.jl | 19 +++++++++++++++++++ src/backends/pyplot.jl | 5 +++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/axes.jl b/src/axes.jl index 4288cde3..7ca04d2b 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, 1.1*amax + else + amin, amax + end + elseif should_widen widen(amin, amax) else amin, amax diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index ef009fe1..5f73a674 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -220,6 +220,8 @@ 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) + rad2deg.(d[:x]), d[:y] else d[:x], d[:y] end @@ -297,14 +299,15 @@ function pgf_axis(sp::Subplot, letter) # 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],","), "}")) + tick_values = ispolar(sp) && letter == :x ? rad2deg.(ticks[1]) : 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 diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index b15e82c0..e06f4151 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[:grid], + :showline => axis[:grid], + ) + + if axis[:letter] == :x + ax[:range] = rad2deg.(axis_limits(axis)) + else + ax[:range] = axis_limits(axis) + ax[:orientation] = 0 + 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) diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 4a8c3c95..9022a692 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](0) + 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 From 12aa43ff5f2239e02d038695c0729b86e868e04b Mon Sep 17 00:00:00 2001 From: Andrew Palugniok Date: Thu, 2 Nov 2017 16:33:05 +0000 Subject: [PATCH 2/8] Fix deprecations. --- src/backends/inspectdr.jl | 2 +- src/backends/plotly.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/plotly.jl b/src/backends/plotly.jl index e06f4151..96c54c9c 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -717,7 +717,7 @@ 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[:t] = rad2deg.(pop!(d_out, :x)) d_out[:r] = pop!(d_out, :y) end end From f0ad851aa7a7c47d490073e552a4637dfe12d755 Mon Sep 17 00:00:00 2001 From: Andrew Palugniok Date: Fri, 3 Nov 2017 19:45:17 +0000 Subject: [PATCH 3/8] Make radial axis vertical. --- src/backends/pgfplots.jl | 9 +++++++-- src/backends/plotly.jl | 2 +- src/backends/pyplot.jl | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index 5f73a674..e0f35f4d 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -306,7 +306,8 @@ function pgf_axis(sp::Subplot, letter) if !(axis[:ticks] in (nothing, false, :none)) && framestyle != :none ticks = get_ticks(axis) - tick_values = ispolar(sp) && letter == :x ? rad2deg.(ticks[1]) : 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 } @@ -317,7 +318,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 +407,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 96c54c9c..940e36f6 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -301,7 +301,7 @@ function plotly_polaraxis(axis::Axis) ax[:range] = rad2deg.(axis_limits(axis)) else ax[:range] = axis_limits(axis) - ax[:orientation] = 0 + ax[:orientation] = -90 end ax diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 9022a692..86a8b5a0 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -1055,7 +1055,7 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) py_set_scale(ax, axis) py_set_lims(ax, axis) if ispolar(sp) && letter == :y - ax[:set_rlabel_position](0) + 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 From 2fe2ff474c3871400300371847709fa9e2859a59 Mon Sep 17 00:00:00 2001 From: Andrew Palugniok Date: Sat, 4 Nov 2017 21:27:02 +0000 Subject: [PATCH 4/8] Implement grid attributes for polar plots. --- src/backends/gr.jl | 85 +++++++++++++++++++++++++++------------- src/backends/pgfplots.jl | 2 + src/backends/plotly.jl | 2 +- src/backends/pyplot.jl | 10 ++++- 4 files changed, 69 insertions(+), 30 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index ef539066..cfb6e85d 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -205,35 +205,66 @@ 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) + xaxis = sp[:xaxis] + yaxis = sp[:yaxis] + + α = 0:45:315 + a = α .+ 90 + sinf = sind.(a) + cosf = cosd.(a) 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) + + #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 0:n + r = float(i) / n + if i % 2 == 0 + if i > 0 + GR.drawarc(-r, r, -r, r, 0, 359) + end + else + GR.drawarc(-r, r, -r, r, 0, 359) + end + end + 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 0:n + r = float(i) / n + if i % 2 == 0 + x, y = GR.wctondc(0.05, r) + GR.text(x, y, string(signif(rmin + i * tick, 12))) + end + end end GR.restorestate() end @@ -760,9 +791,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/pgfplots.jl b/src/backends/pgfplots.jl index e0f35f4d..da527f9a 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -294,6 +294,8 @@ 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 diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index 940e36f6..9ada51f7 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -293,7 +293,7 @@ end function plotly_polaraxis(axis::Axis) ax = KW( - :visible => axis[:grid], + :visible => axis[:showaxis], :showline => axis[:grid], ) diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 86a8b5a0..d325b21b 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -1093,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...) @@ -1101,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...) From cc43202e8f854648cd00e498e9ffe138665c6d5f Mon Sep 17 00:00:00 2001 From: Andrew Palugniok Date: Sat, 4 Nov 2017 23:04:49 +0000 Subject: [PATCH 5/8] Fix radial axis tick alignment for GR. --- src/backends/gr.jl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index cfb6e85d..b31a37ca 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -231,15 +231,12 @@ function gr_polaraxes(rmin::Real, rmax::Real, sp::Subplot) gr_set_line(yaxis[:gridlinewidth], yaxis[:gridstyle], yaxis[:foreground_color_grid]) GR.settransparency(yaxis[:gridalpha]) for i in 0:n - r = float(i) / n - if i % 2 == 0 - if i > 0 - GR.drawarc(-r, r, -r, r, 0, 359) - end - else + r = float(i) * tick / rmax + if r <= 1.0 GR.drawarc(-r, r, -r, r, 0, 359) end end + GR.drawarc(-1, 1, -1, 1, 0, 359) end #prepare to draw ticks @@ -259,8 +256,8 @@ function gr_polaraxes(rmin::Real, rmax::Real, sp::Subplot) #draw radial ticks if yaxis[:showaxis] for i in 0:n - r = float(i) / n - if i % 2 == 0 + r = float(i) * tick / rmax + if i % 2 == 0 && r <= 1.0 x, y = GR.wctondc(0.05, r) GR.text(x, y, string(signif(rmin + i * tick, 12))) end From 08a6f3af3659309875097cf90018f33f517dbafc Mon Sep 17 00:00:00 2001 From: Andrew Palugniok Date: Sun, 5 Nov 2017 10:37:51 +0000 Subject: [PATCH 6/8] Allow custom radial ticks for GR. Fix polar ylims for non-zero ymin values in GR. --- src/backends/gr.jl | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index b31a37ca..9c21cf3e 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -214,8 +214,7 @@ function gr_polaraxes(rmin::Real, rmax::Real, sp::Subplot) a = α .+ 90 sinf = sind.(a) cosf = cosd.(a) - tick = 0.5 * GR.tick(rmin, rmax) - n = round(Int, (rmax - rmin) / tick + 0.5) + rtick_values, rtick_labels = get_ticks(yaxis) #draw angular grid if xaxis[:grid] @@ -230,9 +229,9 @@ function gr_polaraxes(rmin::Real, rmax::Real, sp::Subplot) if yaxis[:grid] gr_set_line(yaxis[:gridlinewidth], yaxis[:gridstyle], yaxis[:foreground_color_grid]) GR.settransparency(yaxis[:gridalpha]) - for i in 0:n - r = float(i) * tick / rmax - if r <= 1.0 + 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 @@ -255,11 +254,11 @@ function gr_polaraxes(rmin::Real, rmax::Real, sp::Subplot) #draw radial ticks if yaxis[:showaxis] - for i in 0:n - r = float(i) * tick / rmax - if i % 2 == 0 && r <= 1.0 + 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, string(signif(rmin + i * tick, 12))) + gr_text(x, y, _cycle(rtick_labels, i)) end end end From 8bbdb0f1b8c784988a8b423c43291f6d44924d01 Mon Sep 17 00:00:00 2001 From: Andrew Palugniok Date: Sun, 12 Nov 2017 18:08:40 +0000 Subject: [PATCH 7/8] Adds radial data filtering for points within axis limits. --- src/backends/pgfplots.jl | 3 ++- src/backends/plotly.jl | 5 +++-- src/utils.jl | 23 +++++++++++++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index da527f9a..43c15eab 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -221,7 +221,8 @@ function pgf_series(sp::Subplot, series::Series) # See "Scatter Plots" in PGFPlots documentation d[:x], d[:y], d[:marker_z] elseif ispolar(sp) - rad2deg.(d[:x]), d[:y] + theta, r = filter_radial_data(d[:x], d[:y], axis_limits(sp[:yaxis])) + rad2deg.(theta), r else d[:x], d[:y] end diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index 9ada51f7..e9a662cc 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -717,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/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 From 240c1efab9720148848896364cc6365df3695ae3 Mon Sep 17 00:00:00 2001 From: Andrew Palugniok Date: Sun, 12 Nov 2017 18:09:08 +0000 Subject: [PATCH 8/8] Improve default radial axis padding. --- src/axes.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axes.jl b/src/axes.jl index 7ca04d2b..a3c3293c 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -437,7 +437,7 @@ function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis) amin, amax = 0, 2pi elseif lims == :auto #widen max radius so ticks dont overlap with theta axis - amin, 1.1*amax + amin, amax + 0.1 * abs(amax - amin) else amin, amax end