Skip to content

Commit

Permalink
Replace radial_distortion_threshhold with directly setting radius_at_…
Browse files Browse the repository at this point in the history
…origin (#3381)

# Description

Fixes #3375 by allowing users to set a radial offset that can be used to
map a negative rmin to 0. E.g.:
```julia
pow2db(x) = 20log10(x)    
theta_prime = pi
angle(a,b) = min(abs(a-b), 2π-abs(a-b))
gain(theta) = abs(sinc(2.0*angle(theta,theta_prime)))

thetas = range(0, 2pi, length=361)
rs = pow2db.(gain.(thetas))

f_polar = Figure()
ax_polar_lin = PolarAxis(f_polar[1,1], title="Gain")
lines!(ax_polar_lin, thetas, gain.(thetas))
ax_polar_dB = PolarAxis(f_polar[1,2], title="Gain in dB", radius_at_origin = -80)
lines!(ax_polar_dB, thetas, rs)
f_polar
```

![Screenshot from 2023-11-17
14-46-38](https://github.com/MakieOrg/Makie.jl/assets/10947937/d3933529-0705-4a28-844e-27dfbce9f6ff)

Some open questions:
- Should negative rlimits automatically result in radii getting shifted?
- Should zooming be allowed to change `radius_at_origin`?
- Should we adjust `theta_0` to work like r0/radius_at_origin? Currently
we have `theta_out = theta_in + theta_0` and `r_out = r_in - r0`

Note that offsetting radii results in distortions:
```julia
phis = range(pi/4, 9pi/4, length=201)
rs = 1.0 ./ sin.(range(pi/4, 3pi/4, length=51)[1:end-1])
rs = vcat(rs, rs, rs, rs, rs[1])

fig = Figure()
ax = PolarAxis(fig[1, 1], radius_at_origin = -2)
lines!(ax, phis, rs)
ax = PolarAxis(fig[1, 2])
lines!(ax, phis, rs)
ax = PolarAxis(fig[1, 3], radius_at_origin = 0.5)
lines!(ax, phis, rs)
fig
```

![Screenshot from 2023-11-17
14-57-01](https://github.com/MakieOrg/Makie.jl/assets/10947937/a6390ba2-526d-4fcb-8702-69e8d89e206f)

## Changes

- replace `radial_distortion_threshold` with `radius_at_origin` which
allows you to set an offset for radii
- add `clip_r::Bool` to PolarAxis and Polar transform, allowing you to
set whether to enforce $r \ge 0$ or not
- for `clip_r = true` (old behavior) return `NaN` rather than `r = 0` if
$r < 0$.

## Type of change

- [x] (Somewhat minor) Breaking change (fix or feature that would cause
existing functionality to not work as expected)

## Checklist

- [x] Added an entry in NEWS.md (for new features and breaking changes)
- [x] Added or changed relevant sections in the documentation
- [x] Added unit tests for new algorithms, conversion methods, etc.
- [x] Added reference image tests for new plotting functions, recipes,
visual options, etc.
  • Loading branch information
SimonDanisch committed Nov 20, 2023
2 parents f7aa963 + 7f0091d commit 7e72ed9
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 92 deletions.
4 changes: 3 additions & 1 deletion NEWS.md
@@ -1,5 +1,7 @@
# News

## master

## 0.20

- GLMakie has gained support for HiDPI (aka Retina) screens. This also enables saving images with higher resolution than screen pixel dimensions [#2544](https://github.com/MakieOrg/Makie.jl/pull/2544).
Expand All @@ -9,13 +11,13 @@
- Changed the glyph used for negative numbers in tick labels from hyphen to minus [#3379](https://github.com/MakieOrg/Makie.jl/pull/3379).
- New declarative API for AlgebraOfGraphics, Pluto and easier dashboards [#3281](https://github.com/MakieOrg/Makie.jl/pull/3281).
- WGLMakie gets faster line rendering with less updating bugs [#3062](https://github.com/MakieOrg/Makie.jl/pull/3062).
- **breaking** Replaced `PolarAxis.radial_distortion_threshold` with `PolarAxis.radius_at_origin`. [#3381](https://github.com/MakieOrg/Makie.jl/pull/3381)
- **breaking** Deprecated the `resolution` keyword in favor of `size` to reflect that this value is not a pixel resolution anymore [#3343](https://github.com/MakieOrg/Makie.jl/pull/3343).
- **breaking** Refactored the `SurfaceLike` family of traits into `VertexGrid`, `CellGrid` and `ImageLike` [#3106](https://github.com/MakieOrg/Makie.jl/pull/3106).
- **breaking** Deprecated `pixelarea(scene)` and `scene.px_area` in favor of viewport.
- **breaking** Refactoring the `Combined` Plot object and renaming it to `Plot`, improving compile times ~2x [#3082](https://github.com/MakieOrg/Makie.jl/pull/3082).
- **breaking** removed old depreactions in [#3113](https://github.com/MakieOrg/Makie.jl/pull/3113/commits/3a39210ef87a0032d78cb27c0c1019faa604effd).


## v0.19.12

- Added `cornerradius` attribute to `Box` for rounded corners [#3346](https://github.com/MakieOrg/Makie.jl/pull/3346).
Expand Down
17 changes: 17 additions & 0 deletions ReferenceTests/src/tests/figures_and_makielayout.jl
Expand Up @@ -211,6 +211,23 @@ end
f
end

@reference_test "PolarAxis radial shift and clip" begin
phis = range(pi/4, 9pi/4, length=201)
rs = 1.0 ./ sin.(range(pi/4, 3pi/4, length=51)[1:end-1])
rs = vcat(rs, rs, rs, rs, rs[1])

fig = Figure(size = (900, 300))
ax1 = PolarAxis(fig[1, 1], clip_r = false, radius_at_origin = -2) # red square, black, blue bulging
ax2 = PolarAxis(fig[1, 2], clip_r = false, radius_at_origin = 0) # red flower, black square, blue bulging
ax3 = PolarAxis(fig[1, 3], clip_r = false, radius_at_origin = 0.5) # red large flower, black star, blue square
for ax in (ax1, ax2, ax3)
lines!(ax, phis, rs .- 2, color = :red, linewidth = 4)
lines!(ax, phis, rs, color = :black, linewidth = 4)
lines!(ax, phis, rs .+ 0.5, color = :blue, linewidth = 4)
end
fig
end

@reference_test "Axis3 axis reversal" begin
f = Figure(size = (1000, 1000))
revstr(dir, rev) = rev ? "$dir rev" : ""
Expand Down
64 changes: 55 additions & 9 deletions docs/reference/blocks/polaraxis.md
Expand Up @@ -204,9 +204,9 @@ clipping via the `clip` attribute.

For reference, the z values used by `PolarAxis` are `po.griddepth[] = 8999` for grid lines, 9000 for the clip polygons, 9001 for spines and 9002 for tick labels.

### Radial Distortion
### Radial Offset

If you have a plot with a large rmin and rmax over a wide range of angles you will end up with a narrow PolarAxis.
If you have a plot with rlimits far away from 0 you will end up with a lot of empty space in the PolarAxis.
Consider for example:

\begin{examplefigure}{svg = true}
Expand All @@ -218,22 +218,68 @@ fig
```
\end{examplefigure}

In this case you may want to distort the r-direction to make more of your data visible.
This can be done by setting `ax.radial_distortion_threshold` to a value between 0 and 1.
In this case you may want to offset the r-direction to make more of your data visible.
This can be done by setting `ax.radius_at_origin` which translates radii as `r_out = r_in - radius_at_origin`.

\begin{examplefigure}{svg = true}
```julia
fig = Figure()
ax = PolarAxis(fig[1, 1], thetalimits = (0, pi), radial_distortion_threshold = 0.2, rlimits = (nothing, nothing))
ax = PolarAxis(fig[1, 1], thetalimits = (0, pi), radius_at_origin = 8)
lines!(ax, range(0, pi, length=100), 10 .+ sin.(0.3 .* (1:100)))
fig
```
\end{examplefigure}

Internally PolarAxis will check `rmin/rmax` against the set threshold.
If that ratio exceed the threshold, the polar transform is adjusted to shift all radii by some `r0` such that `(rmin - r0) / rmax - r0) == ax.radial_distortion_threshold`.
In effect this will hold the inner cutout/clip radius at a fraction of the outer radius.
Note that at `ax.radial_distortion_threshold >= 1.0` (default) this will never distort your data.
This can also be used to show a plot with negative radii:

\begin{examplefigure}{svg = true}
```julia
fig = Figure()
ax = PolarAxis(fig[1, 1], thetalimits = (0, pi), radius_at_origin = -12)
lines!(ax, range(0, pi, length=100), sin.(0.3 .* (1:100)) .- 10)
fig
```
\end{examplefigure}

Note however that translating radii results in some level of distortion:

\begin{examplefigure}{svg = true}
```julia
phis = range(pi/4, 9pi/4, length=201)
rs = 1.0 ./ sin.(range(pi/4, 3pi/4, length=51)[1:end-1])
rs = vcat(rs, rs, rs, rs, rs[1])

fig = Figure(size = (900, 300))
ax1 = PolarAxis(fig[1, 1], radius_at_origin = -2, title = "radius_at_origin = -2")
ax2 = PolarAxis(fig[1, 2], radius_at_origin = 0, title = "radius_at_origin = 0")
ax3 = PolarAxis(fig[1, 3], radius_at_origin = 0.5, title = "radius_at_origin = 0.5")
for ax in (ax1, ax2, ax3)
lines!(ax, phis, rs .- 2, color = :red, linewidth = 4)
lines!(ax, phis, rs, color = :black, linewidth = 4)
lines!(ax, phis, rs .+ 0.5, color = :blue, linewidth = 4)
end
fig
```
\end{examplefigure}

### Radial clipping

By default radii `r_out = r_in - radius_at_origin < 0` are clipped by the Polar transform.
This can be disabled by setting `ax.clip_r = false`.
With that setting `r_out < 0` will pass through the polar transform as is, resulting in a coordinate at $(|r_{out}|, \theta - pi)$.

\begin{examplefigure}{svg = true}
```julia
fig = Figure(size = (600, 300))
ax1 = PolarAxis(fig[1, 1], radius_at_origin = 0.0, clip_r = true, title = "clip_r = true")
ax2 = PolarAxis(fig[1, 2], radius_at_origin = 0.0, clip_r = false, title = "clip_r = false")
for ax in (ax1, ax2)
lines!(ax, 0..2pi, phi -> cos(2phi) - 0.5, color = :red, linewidth = 4)
lines!(ax, 0..2pi, phi -> sin(2phi), color = :black, linewidth = 4)
end
fig
```
\end{examplefigure}

## Attributes

Expand Down
14 changes: 14 additions & 0 deletions src/basic_recipes/triplot.jl
Expand Up @@ -239,3 +239,17 @@ function Makie.plot!(p::Triplot{<:Tuple{<:DelTri.Triangulation}})
strokecolor=p.strokecolor, marker=p.marker, visible=p.show_points, depth_shift=-3.0f-5)
return p
end


function data_limits(p::Triplot{<:Tuple{<:Vector{<:Point}}})
if transform_func(p) isa Polar
# Because the Polar transform is handled explicitly we cannot rely
# on the default data_limits. (data limits are pre transform)
iter = (to_ndim(Point3f, p, 0f0) for p in p.converted[1][])
limits_from_transformed_points(iter)
else
# First component is either another Voronoiplot or a poly plot. Both
# cases span the full limits of the plot
data_limits(p.plots[1])
end
end
39 changes: 26 additions & 13 deletions src/layouting/transformation.jl
Expand Up @@ -378,39 +378,52 @@ end
################################################################################

"""
Polar(theta_0::Float64 = 0.0, direction::Int = +1, r0::Float64 = 0)
Polar(theta_as_x = true, clip_r = true, theta_0::Float64 = 0.0, direction::Int = +1, r0::Float64 = 0)
This struct defines a general polar-to-cartesian transformation, i.e.
This struct defines a general polar-to-cartesian transformation, i.e.,
```math
(r, θ) -> ((r - r₀) ⋅ \\cos(direction ⋅ (θ + θ₀)), (r - r₀) ⋅ \\sin(direction \\cdot (θ + θ₀)))
```
where theta is assumed to be in radians.
`direction` should be either -1 or +1, `r0` should be positive and `theta_0` may be any value.
Note that for `r0 != 0` the inversion may return wrong results.
where θ is assumed to be in radians.
Controls:
- `theta_as_x = true` controls the order of incoming arguments. If true, a `Point2f`
is interpreted as `(θ, r)`, otherwise `(r, θ)`.
- `clip_r = true` controls whether negative radii are clipped. If true, `r < 0`
produces `NaN`, otherwise they simply enter in the formula above as is. Note that
the inversion only returns `r ≥ 0`
- `theta_0 = 0` offsets angles by the specified amount.
- `direction = +1` inverts the direction of θ.
- `r0 = 0` offsets radii by the specified amount. Not that this will affect the
shape of transformed objects.
"""
struct Polar
theta_as_x::Bool
clip_r::Bool
theta_0::Float64
direction::Int
r0::Float64
function Polar(theta_as_x = true, theta_0 = 0.0, direction = +1, r0 = 0)
return new(theta_as_x, theta_0, direction, r0)

function Polar(theta_0::Real = 0.0, direction::Int = +1, r0::Real = 0, theta_as_x::Bool = true, clip_r::Bool = true)
return new(theta_as_x, clip_r, theta_0, direction, r0)
end
end

Base.broadcastable(x::Polar) = (x,)

function apply_transform(trans::Polar, point::VecTypes{2, T}) where T <: Real
if trans.theta_as_x
r = max(0.0, point[2] - trans.r0)
θ = trans.direction * (point[1] + trans.theta_0)
θ, r = point
else
r = max(0.0, point[1] - trans.r0)
θ = trans.direction * (point[2] + trans.theta_0)
r, θ = point
end
r = r - trans.r0
if trans.clip_r && (r < 0.0)
return Point2{T}(NaN)
end
θ = trans.direction *+ trans.theta_0)
y, x = r .* sincos(θ)
return Point2{T}(x, y)
end
Expand Down

0 comments on commit 7e72ed9

Please sign in to comment.