Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PolarAxis improvements (full rlimits, thetalimits, improved controls, argument order) #3154

Merged
merged 109 commits into from
Sep 21, 2023

Conversation

ffreyer
Copy link
Collaborator

@ffreyer ffreyer commented Aug 14, 2023

Changelog

Breaking changes

  • The default argument order changed from (r, theta) to (theta, r). This can be changed back with polaraxis.theta_as_x[] = false
  • Changed ax.radius representing rmax to ax.rlimits representing (rmin, rmax)`

Visual/Interactivity changes

  • Placement or r and theta ticks changed to work with section views.
  • Zooming changed to be compatible with section views. Zooming can be restricted interactively with ax.rzoomkey = Keyboard.r and ax.thetazoomkey = Keyboard.theta and more permanently with ax.rzoomlock = false, ax.thetazoomlock = true and ax.fixrmin = true (defaults)
  • Grids are now placed behind plots. To revert this set polaraxis.griddepth[] = 8999.

New Additions/Features

  • Added delete!(::PolarAxis, ::Plot)
  • Added rlims!(ax, rmin, rmax)
  • Added thetalims!(ax, thetamin, thetamax) and ax.thetalimits[]
  • Changed autolimits!(ax[, unlock = true]) to allow the PolarAxis to derive its limits freely and unlock interactions.
  • Added rticklabelrotation = automatic to orient rtick labels according to the corresponding grid lines.
  • Added zoomspeed = 0.1 to control the speed of zooming
  • Added rzoomkey = Keyboard.r to restrict zooming to the r direction
  • Added thetazoomkey = Keyboard.t to restrict zooming to the theta direction
  • Added fixrmin = true to control whether zooming and translating affects rmin
  • Added rzoomlock = false and thetazoomlock = true to block zooming in the respective directions
  • Added r_translation_button = Mouse.right for translating the plot in theta direction
  • Added theta_translation_button = Mouse.right for translating the axis in theta direction
  • Added axis_rotation_button = Keyboard.left_control & Mouse.rightto rotate the axis as a whole
  • Added reset_axis_orientation = false to control whether the reset interaction reorients the axis
  • Added normalize_theta_ticks = true to control whether theta ticks are normalized to a -2pi..2pi range

Background Changes

  • sample_density now applies to clip polygons
  • Reordered attributes
  • Added target_thetalims
  • rtick labels now also affect the space reserved for ticks
  • Added AngularTicks to derive more pleasant theta ticks
  • Changed title align to work with numbers

Description

TODO

  • reposition view based new limits
  • add limit for inner clip and adjust r_0 instead
  • try to prevent frequent regeneration of clip poly
  • improve automatic rtick placement & alignment
  • improve MultipleTicks for angles add AngularTicks
  • clean up rlims!, add thetalims!
  • fix error on zoom
  • fix align not updating with varying theta_0
  • add mouse drag to adjust r limits (and maybe theta_0)
  • stop drag rotation from affecting the clip polygon detach clip orientation from theta_0 and theta limits. You should be able to adjust
  • fix r ticks going out of frame when translating in clipped view
  • cleanup debug code
  • fix title placement in partial PolarAxis

Review

  • default to no distortion, mention effect in docs
  • fix clip & spine resolution
  • add attribute to control clip and spine resolution
  • figure out a better name for maximum_clip_radius (is it better?)
  • fix rticks pointing inwards with direction = -1
  • consider bbox of r ticks when reserving space
  • add rtick_rotation = true/false/automatic/angle attribute
  • reconsider direction * (theta + theta_0) vs direction * theta + theta_0
  • reconsider zooming implementation
  • reconsider translation implementation
  • fix error on zoom
  • fix isfinite(vmax) error when translating a lot
  • improve inner clip resolution (sector)

Type of change

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist

  • Added an entry in NEWS.md (for new features and breaking changes)
  • Added or changed relevant sections in the documentation
  • Added unit tests for new algorithms, conversion methods, etc.
  • Added reference image tests for new plotting functions, recipes, visual options, etc.

@MakieBot
Copy link
Collaborator

MakieBot commented Aug 15, 2023

Compile Times benchmark

Note, that these numbers may fluctuate on the CI servers, so take them with a grain of salt. All benchmark results are based on the mean time and negative percent mean faster than the base branch. Note, that GLMakie + WGLMakie run on an emulated GPU, so the runtime benchmark is much slower. Results are from running:

using_time = @ctime using Backend
# Compile time
create_time = @ctime fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true)
display_time = @ctime Makie.colorbuffer(display(fig))
# Runtime
create_time = @benchmark fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true)
display_time = @benchmark Makie.colorbuffer(display(fig))
using create display create display
GLMakie 11.27s (11.19, 11.43) 0.08+- 1.13s (1.11, 1.14) 0.01+- 802.40ms (787.21, 853.65) 23.26+- 9.77ms (9.69, 9.84) 0.05+- 140.77ms (139.91, 141.97) 0.66+-
master 11.20s (11.15, 11.24) 0.03+- 1.13s (1.12, 1.15) 0.01+- 794.91ms (781.92, 805.69) 9.13+- 9.76ms (9.68, 9.84) 0.05+- 140.58ms (138.83, 141.45) 0.86+-
evaluation +0.58%, 0.07s invariant (1.05d, 0.09p, 0.06std) -0.21%, -0.0s invariant (-0.21d, 0.71p, 0.01std) +0.93%, 7.49ms invariant (0.42d, 0.45p, 16.19std) +0.08%, 0.01ms invariant (0.15d, 0.78p, 0.05std) +0.14%, 0.2ms invariant (0.25d, 0.64p, 0.76std)
CairoMakie 11.37s (11.10, 11.63) 0.18+- 1.17s (1.15, 1.20) 0.02+- 211.55ms (205.83, 222.72) 5.58+- 11.29ms (10.96, 11.77) 0.26+- 6.85ms (6.72, 7.04) 0.12+-
master 11.38s (11.16, 11.83) 0.24+- 1.17s (1.14, 1.25) 0.04+- 211.43ms (205.74, 224.63) 6.68+- 11.23ms (11.03, 11.53) 0.21+- 6.78ms (6.69, 6.94) 0.09+-
evaluation -0.11%, -0.01s invariant (-0.06d, 0.92p, 0.21std) -0.35%, -0.0s invariant (-0.14d, 0.80p, 0.03std) +0.06%, 0.12ms invariant (0.02d, 0.97p, 6.13std) +0.51%, 0.06ms invariant (0.24d, 0.66p, 0.23std) +0.97%, 0.07ms invariant (0.65d, 0.25p, 0.10std)
WGLMakie 17.94s (17.39, 18.63) 0.46+- 1.80s (1.72, 1.91) 0.06+- 16.49s (15.95, 16.96) 0.33+- 23.86ms (23.06, 25.32) 0.85+- 1.56s (1.44, 1.77) 0.13+-
master 17.41s (16.63, 18.14) 0.55+- 1.78s (1.69, 1.92) 0.08+- 16.21s (15.74, 17.06) 0.43+- 23.60ms (22.41, 25.69) 1.06+- 1.52s (1.48, 1.62) 0.05+-
evaluation +2.99%, 0.54s invariant (1.06d, 0.07p, 0.50std) +1.34%, 0.02s invariant (0.35d, 0.52p, 0.07std) +1.64%, 0.27s invariant (0.71d, 0.21p, 0.38std) +1.05%, 0.25ms invariant (0.26d, 0.63p, 0.95std) +2.28%, 0.04s invariant (0.37d, 0.51p, 0.09std)

@ffreyer
Copy link
Collaborator Author

ffreyer commented Aug 15, 2023

For testing:

begin
    f = Figure()
    theta_0 = Slider(f[1, 1], range = range(0, 2pi, length=101)).value
    thetalims = IntervalSlider(f[2, 1], range = range(0, 2pi, length=101)).interval
    rlims = IntervalSlider(f[3, 1], range = range(0, 10, length=101)).interval
    po = PolarAxis(f[4, 1], thetalimits = thetalims, rlimits=rlims, theta_0 = theta_0)
    bb = map(po.thetalimits, po.target_radius, po.theta_0, po.direction) do _, _, _, _
        Makie.transformed_bbox(po)
    end
    p = scatter!(po, 8 .* rand(1000) .+ 1, 2pi .* rand(1000))
    l = lines!(po, bb, color = :yellow, transformation = Transformation())
    translate!(l, 0, 0, 9000)
    f
end

I'm currently handling r and theta limits here by adjusting the clip polygon. The Polar transform still works the same as before, i.e. no extra distortion and fully invertable. This however means that if you're interested in a large rmin, rmax with a small difference, like (8, 10), you will only get a slim ring like this:
Screenshot from 2023-08-15 20-27-03

I'm wondering if instead I should add a radial shift to the Polar transform, so I can keep the clip polygon at set radii (e.g. 0.1 and 1.0). With that the shift would make sure that whatever range needs to be visible would fit into the clip poylgon and the ticks would adjust to communicate that.

@anowacki
Copy link

If it's not too bold to comment, I personally have no problem with a 'slim ring' when I have asked for a small range of r, so I would be happy with that.

(I don't have any opinion on the implementation as I don't understand that.)

@jkrumbiegel
Copy link
Member

I think it would be better to not go the "slim ring" route and instead do it via shift of the transform (a slimmer ring might still be desirable to keep the amount of radial stretching or "aspect ratio" of the data at some desired value.

That would make the transform not fully invertible, but I guess if we throw a clear error if any data clips inwards that's ok?

@ffreyer
Copy link
Collaborator Author

ffreyer commented Aug 17, 2023

I think it would be better to not go the "slim ring" route and instead do it via shift of the transform (a slimmer ring might still be desirable to keep the amount of radial stretching or "aspect ratio" of the data at some desired value.

Yea I added an r0 to the transform to translate data in r direction now, with maximum_clip_radius to set when that is adjusted instead of the clip area.

That would make the transform not fully invertible, but I guess if we throw a clear error if any data clips inwards that's ok?

Since we don't use the inverse transform in PolarAxis at all we could, I guess, but I'm not sure if it's good to do that. It's quite easy for stuff to end up with r < 0 through zooming (and now translating) the axis. We probably shouldn't throw an error every time that happens if someone is using the inverse transform in some way. Probably better to just hide/leave stuff at r = 0.

@github-actions
Copy link
Contributor

Missing reference images

Found 1 new images without existing references.
Upload new reference images before merging this PR.

@ffreyer
Copy link
Collaborator Author

ffreyer commented Aug 17, 2023

Some Notes on how I currently have things set up:

  • radius has been replaced by rlimits which include an rmin and rmax. If rmin > 0 a circular clip in the center appears
  • maximum_clip_radius has been added. If rmin/rmax exceed that value radii are shifted such that (rmin - shift) / (rmax - shift) = maximum_clip_radius. This keeps the inner clip from getting to close to the outer, i.e. it avoids the slim ring problem
  • thetalimits have been added so that the PolarAxis can be reduced to for example a 180° view.
  • theta_0 (now?) acts as an angular shift applying to all angles, i.e. it rotates data, grid lines, ticks and the clip polygon. It does however not change angular ticks - these are only affected by thetalimits (otherwise 0° in your data would not match 0° in tick labels). theta_0 can be used to rotate your 180° view to whatever orientation you like.
  • zooming (scrolling) can affect the size of the inner clip but never remove or create it
  • radial translation (right drag) will generally try to keep data under your cursor which means it currently can remove the inner clip, but not create it.
  • angular translation (disabled by default) will also try to keep data under your cursor. It does not affects thetalimits and theta_0 but does not change the orientation of the clip polygon.

Updated test plot:

f = Figure()

theta_0 = Slider(f[1, 1], range = range(0, 2pi, length=101)).value
thetalims = IntervalSlider(f[2, 1], range = range(0, 2pi, length=101)).interval
rlims = IntervalSlider(f[3, 1], range = range(0, 11, length=111)).interval

po = PolarAxis(f[4, 1:2], thetalimits = thetalims, rlimits=rlims, theta_0 = theta_0)
po.theta_translation_button[] = Mouse.right
bb = map(Makie.transformed_bbox,
    po.target_radius, po.thetalimits,
    map(x -> x.r0, po.scene.transformation.transform_func),
    po.direction,
    po.theta_0,
)

# these are the real values, not the values set by the slider (axis interaction causes desync)
Label(f[1, 2], map(x -> @sprintf("θ₀ = %0.1f°", x * 180/pi), po.theta_0))
Label(f[2, 2], map(lims -> @sprintf("θ = %0.1f° ..  %0.1f°", (180/pi .* lims)...), po.thetalimits))
Label(f[3, 2], map(lims -> @sprintf("r = %0.2f ..  %0.2f", lims...), po.target_radius))

p = scatter!(po, Point2f.(2, 2pi .* (0:7) ./ 8), color = 1:8, colormap = :RdBu)
lines!(po, range(1, 10, 101), range(0, 2pi, length=101))
p = scatter!(po, Point2f.(10, 2pi .* (0:7) ./ 8), color = 1:8, colormap = :RdBu)
l = lines!(po, bb, color = :yellow, transformation = Transformation())
translate!(l, 0, 0, 9000)
f

@github-actions
Copy link
Contributor

Missing reference images

Found 1 new images without existing references.
Upload new reference images before merging this PR.

@github-actions
Copy link
Contributor

Missing reference images

Found 1 new images without existing references.
Upload new reference images before merging this PR.

@ffreyer
Copy link
Collaborator Author

ffreyer commented Aug 30, 2023

Ok, the things you noted should be taken care of now.

Since I switched back to full-circle PolarAxis by default I also adjusted the default interactions:

  • fixrmin = true by default now and also stop radial translation (avoid circle -> ring)
  • rzoomlock = false was added, which can restrict zooming in r direction
  • thetazoomlock = true was added, which stops zooming in theta direction by default (avoid going from circle -> sector)

Other changes:

  • autolimits(::PolarAxis) now unlocks these interactions by default, and also allows PolarAxis to choose limits freely.
  • normalize_theta_ticks = true now controls whether theta ticks get normalized to a -2pi .. 2pi range or not. The normalization will prefer 0° over 360° first, and positive angles over negative angles second.
  • minor tweak to tick generation so -30° .. 30° ends up with 10° steps

@github-actions
Copy link
Contributor

Missing reference images

Found 1 new images without existing references.
Upload new reference images before merging this PR.

function Makie.plot!(
po::PolarAxis, P::Makie.PlotFunc,
attributes::Makie.Attributes, args...;
needs_tight_limits(::Surface) = true
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One set of test failures (basically everything surface related) comes from this, because this also affects Axis. Personally I think surface plots on an Axis defaulting to tight limits is something we want but I can also just revert this.

@ffreyer
Copy link
Collaborator Author

ffreyer commented Sep 13, 2023

The other test failures around PolarAxis have a few sources...

PolarAxis Decorations before:

PolarAxis decorations

After:

PolarAxis decorations
This comes from making rticks align with the edge when thetalimits are less than 2pi. Should be easy enough to revert, but I personally don't have a strong opinion on which looks better. The outer most grid line being faint comes from moving grid lines into the background.

PolarAxis scatterlines spine before:

PolarAxis scatterlines spine

After:

Pre fixes

PolarAxis scatterlines spine

fixed

This has a different default rtick angle, which is also coming from rtick alignment in polar sectors and is easy to revert if we want. It also moved away from tight limits, which I think we want?

Voronoiplot with a nonlinear transform, Triplot with nonlinear transformation

Same rtick angle change as above

tl;dr question @jkrumbiegel, @SimonDanisch

  • Do we want to add tight limits by default for surface in Axis here?
  • Should I revert the rtick angle and alignment changes for a full PolarAxis?
  • How is the "reset limits on display" logic meant to work? Fixed

@SimonDanisch
Copy link
Member

Do we want to add tight limits by default for surface in Axis here?

I'd say we shouldn't!

Should I revert the rtick angle and alignment changes for a full PolarAxis?

I like them on a straight line but don't have a strong opinion ;)

@jkrumbiegel
Copy link
Member

The straight line would be problematic with slightly longer ticks, I'd argue for keeping them horizontal because of that.

@ffreyer
Copy link
Collaborator Author

ffreyer commented Sep 20, 2023

Looking back at this I think there are two specific question about rticks:

  1. Should rticks (by default in a full PolarAxis) be placed on a diagonal like before or on a horizontal like now? (I.e. should rtickangle default to 0 or pi/8?
  2. Should rticks (by default in a full PolarAxis) always be aligned horizontally like before or align themselves based on the angle they are placed at? (I.e. align themselves to the r or theta direction depending on which one is more horizontal.)

Do we want to add tight limits by default for surface in Axis here?

I'd say we shouldn't!

I think we should, because in a 2d Axis surface is more or less the same as image which has tight limits. But if we don't I'd just surface untightened here too

@SimonDanisch
Copy link
Member

I meant, we shouldn't do it in this PR, since its unrelated and strictly speaking breaking (by the definition that we need to update reference images).

@ffreyer
Copy link
Collaborator Author

ffreyer commented Sep 20, 2023

Removed/Commented out the surface tightlimits change, so now a surface plot in PolarAxis produces

Screenshot from 2023-09-20 14-48-14

(from test w/o rlims!() call)


Default rtickangle is unchanged, so the default rtick placement is still on a straight horizontal line:

Screenshot from 2023-09-20 14-41-41

With explicitly set rtickangle, the individual label rotation is now horizontal if thetalimits are > 1.9pi

Screenshot from 2023-09-20 14-41-59

and ":aligned" otherwise. That means they rotate according to their position, but stay close to upright and horizontal

Screenshot from 2023-09-20 14-42-29

@SimonDanisch
Copy link
Member

@ffreyer and I decided to merge this to get it in the next release, since there are only styling changes pending and no other breaking change in sight.
It will be ok, to further tweak the style based on feedback after the release, so we don't need to nail it in this iteration!

@SimonDanisch SimonDanisch merged commit 5a606d5 into master Sep 21, 2023
20 of 26 checks passed
@SimonDanisch SimonDanisch deleted the ff/polar_segments branch September 21, 2023 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants