Skip to content

Refactor arrows #4925

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

Open
wants to merge 61 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
6e46d26
change something to open pr
ffreyer Apr 15, 2025
0a0126a
rely on PointBased() conversion
ffreyer Apr 16, 2025
24b976a
Merge branch 'master' into ff/arrows
ffreyer Apr 29, 2025
9985f23
prototype 2D arrows
ffreyer Apr 29, 2025
e8cb77b
resolve normalize directly on directions
ffreyer May 1, 2025
1784669
split points and directions for 3D
ffreyer May 1, 2025
11c2b8a
prototype arrows 3d
ffreyer May 1, 2025
a72ac8b
add maxshaftlength
ffreyer May 1, 2025
db78b50
add markerscale, move lengthscale
ffreyer May 1, 2025
e63c521
fix default_automatic logic
ffreyer May 1, 2025
ecd676e
update default radii, lengths, fix radius being treated as diameter
ffreyer May 1, 2025
31c3781
consider align in boundingboxes
ffreyer May 1, 2025
73ff555
generic attributes
ffreyer May 1, 2025
fe3dcd1
let poly handle colors
ffreyer May 2, 2025
a8e94ab
skip empty/hidden meshes
ffreyer May 2, 2025
9ad5e59
add argmode to allow interpreting directions and endpoints
ffreyer May 2, 2025
512832d
fix limits
ffreyer May 2, 2025
4b1fab1
fix arrows 3d
ffreyer May 2, 2025
beb083f
clean up docs and conversions, reorganize code
ffreyer May 5, 2025
b7413ae
restore quality attribute
ffreyer May 5, 2025
ade865d
deprecate arrows
ffreyer May 5, 2025
f67d091
refactor skipping to be easier to reason with
ffreyer May 5, 2025
1d86ee4
reimplement tooltips for arrows
ffreyer May 5, 2025
5985d27
try to restore old defaults
ffreyer May 6, 2025
3cf93c8
mask AA issues with lines/stroke
ffreyer May 6, 2025
f09e9af
mask more
ffreyer May 6, 2025
2e5df3d
rework color handling to fix type issues and allow mixed inputs
ffreyer May 6, 2025
66641ed
Merge branch 'master' into ff/arrows
ffreyer May 6, 2025
d63c38a
bump requirements for Cone
ffreyer May 6, 2025
b303696
update changelog
ffreyer May 6, 2025
6385a0f
add strokemask as an attribute
ffreyer May 6, 2025
6c79758
fix Makie tests
ffreyer May 6, 2025
c620861
fix streamplot
ffreyer May 6, 2025
cccfa21
fix renamed function
ffreyer May 6, 2025
a6cfe1d
fix shading issue with o vector in Cone?
ffreyer May 6, 2025
6ab1989
fix DataInspector test?
ffreyer May 6, 2025
a762e77
fix CairoMakie shading
ffreyer May 7, 2025
785ecad
revert GeometryBasics.rotation() name change
ffreyer May 7, 2025
b3ea0ea
fix indexing error due to NaN data
ffreyer May 12, 2025
289b3b0
add markerspace
ffreyer May 12, 2025
3c1e573
remove transform_marker and fix Axis3 arrows more directly
ffreyer May 12, 2025
f9181d9
add transformation test for arrows 2D
ffreyer May 12, 2025
d947f41
add min/maxshaftlength test
ffreyer May 12, 2025
ccddf67
Merge branch 'master' into ff/arrows
ffreyer May 12, 2025
ec1755c
fix update order
ffreyer May 13, 2025
637ee8c
add 3d transformation test
ffreyer May 13, 2025
0198177
test colormapping
ffreyer May 13, 2025
541ac65
fix func typing, mesh gen
ffreyer May 13, 2025
456f15d
test updates, metrics, func argument
ffreyer May 13, 2025
4438d95
test transparency
ffreyer May 13, 2025
d86a0f5
update docs
ffreyer May 13, 2025
9b264b3
switch to image asset
ffreyer May 13, 2025
484c737
separate attribute lists
ffreyer May 13, 2025
5eec224
fix tests
ffreyer May 13, 2025
0f268e8
fix more tests
ffreyer May 13, 2025
e6bafd9
fix CairoMakie shading
ffreyer May 13, 2025
7d7c916
center y in 2d arrow markers, add & test callback shape constructor
ffreyer May 13, 2025
a222533
test arrow scaling more directly
ffreyer May 14, 2025
affee5e
add scale vs elongation example
ffreyer May 14, 2025
ce2a6d1
deprecate transform_marker
ffreyer May 14, 2025
1a4721c
change removed attribute error to warning
ffreyer May 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## [Unreleased]
- Refactored `arrows` to solve various issues with conversions, broken color handling, transparency, alignment and sizing. [#4925](https://github.com/MakieOrg/Makie.jl/pull/4925)

## [0.22.5] - 2025-05-12

Expand Down
13 changes: 9 additions & 4 deletions CairoMakie/src/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ function draw_mesh3D(
if isnothing(meshnormals)
ns = nothing
else
ns = map(n -> normalize(normalmatrix * n), meshnormals)
ns = map(n -> zero_normalize(normalmatrix * n), meshnormals)
end

# Face culling
Expand Down Expand Up @@ -1185,10 +1185,15 @@ function draw_pattern(ctx, zorder, shading, meshfaces, ts, per_face_col, ns, vs,
facecolors = per_face_col[k]
# light calculation
if shading && !isnothing(ns)
# these face index expressions currently allocate for SizedVectors
# if done like `ns[f]`
mean_normal = sum(i -> ns[i], f) / length(f)
c1, c2, c3 = Base.Cartesian.@ntuple 3 i -> begin
# these face index expressions currently allocate for SizedVectors
# if done like `ns[f]`
N = ns[f[i]]
# normals are usually interpolated on the face, which allows
# Vec3f(0) to be used to give a vertex no weight on the normal
# direction. To reproduce this here we mix in a tiny amount of
# the mean normal direction.
N = normalize(ns[f[i]] + 1e-20 * mean_normal)
v = vs[f[i]]
c = facecolors[i]
_calculate_shaded_vertexcolors(N, v, c, lightdir, light_color, ambient, diffuse, specular, shininess)
Expand Down
2 changes: 2 additions & 0 deletions CairoMakie/src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -697,3 +697,5 @@ function best_font(c::Char, font = Makie.defaultfont())
end
return font
end

zero_normalize(v::AbstractVector{T}) where T = v ./ (norm(v) + eps(zero(T)))
2 changes: 1 addition & 1 deletion GLMakie/assets/shader/util.vert
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ vec3 qmul(vec4 quat, vec3 vec){
void rotate(Nothing r, int index, inout vec3 V, inout vec3 N){} // no-op
void rotate(vec4 q, int index, inout vec3 V, inout vec3 N){
V = qmul(q, V);
N = normalize(qmul(q, N));
N = qmul(q, N);
}
void rotate(samplerBuffer vectors, int index, inout vec3 V, inout vec3 N){
vec4 r = texelFetch(vectors, index);
Expand Down
82 changes: 1 addition & 81 deletions MakieCore/src/basic_plots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -726,84 +726,4 @@ Draws a wireframe, either interpreted as a surface or as a mesh.
@recipe Wireframe begin
documented_attributes(LineSegments)...
depth_shift = -1f-5
end

"""
arrows(points, directions; kwargs...)
arrows(x, y, u, v)
arrows(x::AbstractVector, y::AbstractVector, u::AbstractMatrix, v::AbstractMatrix)
arrows(x, y, z, u, v, w)
arrows(x, y, [z], f::Function)

Plots arrows at the specified points with the specified components.
`u` and `v` are interpreted as vector components (`u` being the x
and `v` being the y), and the vectors are plotted with the tails at
`x`, `y`.

If `x, y, u, v` are `<: AbstractVector`, then each 'row' is plotted
as a single vector.

If `u, v` are `<: AbstractMatrix`, then `x` and `y` are interpreted as
specifications for a grid, and `u, v` are plotted as arrows along the
grid.

`arrows` can also work in three dimensions.

If a `Function` is provided in place of `u, v, [w]`, then it must accept
a `Point` as input, and return an appropriately dimensioned `Point`, `Vec`,
or other array-like output.
"""
@recipe Arrows (points, directions) begin
"Sets the color of arrowheads and lines. Can be overridden separately using `linecolor` and `arrowcolor`."
color = :black
"""Scales the size of the arrow head. This defaults to
`0.3` in the 2D case and `Vec3f(0.2, 0.2, 0.3)` in the 3D case. For the latter
the first two components scale the radius (in x/y direction) and the last scales
the length of the cone. If the arrowsize is set to 1, the cone will have a
diameter and length of 1."""
arrowsize = automatic
"""Defines the marker (2D) or mesh (3D) that is used as
the arrow head. The default for is `'▲'` in 2D and a cone mesh in 3D. For the
latter the mesh should start at `Point3f(0)` and point in positive z-direction."""
arrowhead = automatic
"""Defines the mesh used to draw the arrow tail in 3D.
It should start at `Point3f(0)` and extend in negative z-direction. The default
is a cylinder. This has no effect on the 2D plot."""
arrowtail = automatic
"""Sets the color used for the arrow tail which is represented by a line in 2D.
Will copy `color` if set to `automatic`.
"""
linecolor = automatic
"""Sets the linestyle used in 2D. Does not apply to 3D plots."""
linestyle = nothing
"""Sets how arrows are positioned. By default arrows start at
the given positions and extend along the given directions. If this attribute is
set to `:head`, `:lineend`, `:tailend`, `:headstart` or `:center` the given
positions will be between the head and tail of each arrow instead."""
align = :origin
"""By default the lengths of the directions given to `arrows`
are used to scale the length of the arrow tails. If this attribute is set to
true the directions are normalized, skipping this scaling."""
normalize = false
"""Scales the length of the arrow tail."""
lengthscale = 1f0

"""Defines the number of angle subdivisions used when generating
the arrow head and tail meshes. Consider lowering this if you have performance
issues. Only applies to 3D plots."""
quality = 32
markerspace = :pixel

mixin_generic_plot_attributes()...
mixin_shading_attributes()...
mixin_colormap_attributes()...

fxaa = automatic
"""Scales the width/diameter of the arrow tail.
Defaults to `1` for 2D and `0.05` for the 3D case."""
linewidth = automatic
"""Sets the color of the arrow head. Will copy `color` if set to `automatic`."""
arrowcolor = automatic
"Controls whether marker attributes get transformed by the model matrix."
transform_marker = automatic
end
end
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ FixedPointNumbers = "0.6, 0.7, 0.8"
Format = "1.3"
FreeType = "3.0, 4.0"
FreeTypeAbstraction = "0.10.3"
GeometryBasics = "0.5"
GeometryBasics = "0.5.9"
GridLayoutBase = "0.11"
ImageBase = "0.1.7"
ImageIO = "0.5, 0.6"
Expand Down
153 changes: 153 additions & 0 deletions ReferenceTests/src/tests/examples2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2035,3 +2035,156 @@ end
Makie.step!(st)
st
end

@reference_test "Transformed 2D Arrows" begin
ps = [Point2f(i, 2^i) for i in 1:10]
vs = [Vec2f(1, 100) for _ in 1:10]
f,a,p = arrows2d(ps, vs, color = log10.(norm.(ps)), colormap = :RdBu)
arrows2d(f[1,2], ps, vs, color = log10.(norm.(ps)), axis = (yscale = log10,))

ps = coordinates(Rect2f(-1, -1, 2, 2))
a, p = arrows2d(f[2,1], ps, ps)
scatter!(a, 0,0, markersize = 50, marker = '+')
translate!(p, 1, 1, 0)

a, p = arrows2d(f[2,2], ps, ps)
scatter!(a, 0,0, markersize = 50, marker = '+')
scale!(p, 1.0/sqrt(2), 1.0/sqrt(2), 1)
Makie.rotate!(p, pi/4)

f
end

@reference_test "arrow min- and maxshaftlength scaling" begin
# widths should not scale while the tip ends in the gray area (between min
# and maxshaftlength)
scene = Scene(camera = campixel!, size = (500, 500))
min = 30; max = 60
linesegments!(scene, [-10, 510], [0.5(min+max), 0.5(min+max)] .+ 40, color = :lightgray, linewidth = max-min)
heights = [10, min-10, min, min+10, max-10, max, max+10, 180] .+ 40
p = arrows2d!(scene,
50:50:400, zeros(8),
zeros(8), heights,
minshaftlength = min, maxshaftlength = max,
shaftwidth = 20, tipwidth = 40, tiplength = 40,
strokemask = 0
)
scatter!(scene, 50:50:400, fill(20, 8), marker = Rect, markersize = 20, color = :red)

component_widths = widths.(Rect2f.(p.plots[1].args[1][]))
for i in 1:8
scale = heights[i] / (clamp(heights[i] - p.tiplength[], min, max) + p.tiplength[])
@test component_widths[2i-1][1] ≈ p.shaftwidth[] * scale # shaft
@test component_widths[2i][1] ≈ p.tipwidth[] * scale # tip
end

linesegments!(scene, [-10, 510], [0.5(min+max), 0.5(min+max)] .+ 290, color = :lightgray, linewidth = max-min)
p = arrows3d!(scene,
50:50:400, fill(250, 8),
zeros(8), heights,
minshaftlength = min, maxshaftlength = max,
shaftradius = 10, tipradius = 20, tiplength = 40,
markerscale = 1.0
)
sp = scatter!(scene, 50:50:400, fill(270, 8), marker = Rect, markersize = 20, color = :red)
translate!(sp, 0, 0, 100)

for i in 1:8
scale = heights[i] / (clamp(heights[i] - p.tiplength[], min, max) + p.tiplength[])
@test p.plots[2].markersize[][i][1] ≈ 2 * p.shaftradius[] * scale # shaft
@test p.plots[3].markersize[][i][1] ≈ 2 * p.tipradius[] * scale # tip
end

scene
end

function arrow_align_test(plotfunc, tail, taillength)
function draw_row!(ax, y; kwargs...)
plotfunc(ax, (1, y), (0, 1), align = -0.5; kwargs...)
plotfunc(ax, (2, y), (0, 1), align = :tail; kwargs...)
plotfunc(ax, (3, y), (0, 1), align = :center; kwargs...)
plotfunc(ax, (4, y), (0, 1), align = :tip; kwargs...)
plotfunc(ax, (5, y), (0, 1), align = 1.5; kwargs...)
end

fig = Figure()
ax = Axis(fig[1, 1])

hlines!(ax, [1, 3, 5])

draw_row!(ax, 1)
draw_row!(ax, 3; lengthscale = 0.5, color = RGBf(0.8, 0.2, 0.1), alpha = 0.3)
draw_row!(ax, 5; tail = tail, taillength = taillength,
tailcolor = :orange, shaftcolor = RGBAf(0.1, 0.9, 0.2, 0.5), tipcolor = :red)

plotfunc(ax, (1, 7), (1, 8), argmode = :endpoints, lengthscale = 0.5, align = -0.5)
plotfunc(ax, (2, 7), (2, 8), argmode = :endpoints, lengthscale = 0.5, align = :tail)
plotfunc(ax, (3, 7), (3, 8), argmode = :endpoints, lengthscale = 0.5, align = :center)
plotfunc(ax, (4, 7), (4, 8), argmode = :endpoints, lengthscale = 0.5, align = :tip)
plotfunc(ax, (5, 7), (5, 8), argmode = :endpoints, lengthscale = 0.5, align = 1.5)
hlines!(ax, [7, 8], color = :red)

fig
end

@reference_test "arrows2d alignment" begin
arrow_align_test(arrows2d!, Point2f[(0, 0), (1, -0.5), (1, 0.5)], 8)
end

@reference_test "arrows3d alignment" begin
arrow_align_test(arrows3d!, Makie.Cone(Point3f(0,0,1), Point3f(0,0,0), 0.5f0), 0.4)
end

@reference_test "arrows2d updates" begin
grad_func(p) = 0.2 * p .- 0.01 * p.^3
ps = [Point2f(x, y) for x in -5:5, y in -5:5]
f, a, p = arrows2d(ps, grad_func)

st = Makie.Stepper(f)
Makie.step!(st)

p.color[] = :orange
p[1][] = vec(ps .+ Point2f(0.2))
p.lengthscale[] = 1.5
p.tiplength = 4
p.tipwidth = 8
p.shaftwidth = 1
p.taillength = 8
p.tailwidth = 6
Makie.step!(st)

p.args[2][] = p -> 0.01 * p.^3 - 0.2 * p + 0.00001 * p.^5
p.align = :center
p.shaftcolor = :blue
p.tail = Rect2f(0,-0.5,1,1)
p.tailwidth = 8
Makie.step!(st)
st
end

# Adjusted from 2d version
@reference_test "arrows3d updates" begin
grad_func(p) = 0.2 * p .- 0.01 * p.^3
ps = [Point2f(x, y) for x in -5:5, y in -5:5]
f, a, p = arrows3d(ps, grad_func)

st = Makie.Stepper(f)
Makie.step!(st)

p.color[] = :orange
p[1][] = vec(ps .+ Point2f(0.2))
p.lengthscale[] = 1.5
p.tiplength = 0.2
p.tipradius = 0.08
p.shaftradius = 0.1
p.tail = Rect3f(-0.5,-0.5,0, 1,1,1)
p.taillength = 0.2
p.tailradius = 0.2
Makie.step!(st)

p.args[2][] = p -> 0.01 * p.^3 - 0.2 * p + 0.00001 * p.^5
p.align = :center
p.shaftcolor = :blue
Makie.step!(st)
st
end
30 changes: 26 additions & 4 deletions ReferenceTests/src/tests/examples3d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,8 @@ end
a = Axis3(f[1,1])
r = range(-1, 1, length = 5)
arrows!(a, Point3f[(1, 0, 0), (0,0,0)], Point3f[(0,0,0.1), (1,0,0)], color = :gray)
arrows!(a, Point3f[(-1, 1, 0), (0,0,0)], Point3f[(0,0,0.1), (-1,1,0)], color = :lightblue,
transform_marker = false)
arrows!(a, Point3f[(1, -1, 0), (0,0,0)], Point3f[(0,0,0.1), (1,-1,0)], color = :yellow,
transform_marker = true)
arrows!(a, Point3f[(-1, 1, 0), (0,0,0)], Point3f[(0,0,0.1), (-1,1,0)], color = :lightblue,)
arrows!(a, Point3f[(1, -1, 0), (0,0,0)], Point3f[(0,0,0.1), (1,-1,0)], color = :yellow,)
mesh!(a, Rect2f(-1,-1,2,2), color = (:red, 0.5), transparency = true)
f
end
Expand Down Expand Up @@ -773,3 +771,27 @@ end
mesh!(ax, uv3_mesh(positions); color=data, shading=NoShading)
f
end

@reference_test "Transformed 3D Arrows" begin
ps = [Point2f(i, 2^i) for i in 1:10]
vs = [Vec2f(1, 100) for _ in 1:10]
f,a,p = arrows3d(ps, vs, markerscale = 1, tiplength = 30, color = log10.(norm.(ps)), colormap = :RdBu)
arrows3d(f[1,2], ps, vs, markerscale = 1, color = log10.(norm.(ps)), axis = (yscale = log10,))

ps = coordinates(Rect3f(-1, -1, -1, 2, 2, 2))
a, p = arrows3d(f[2,1], ps, ps)
meshscatter!(a, Point3f(0), markersize = 1, marker = Rect3f(-0.5, -0.5, -0.5, 1, 1, 1))
translate!(p, 0, 0, 1)

a, p = arrows3d(f[2,2], ps, ps)
meshscatter!(a, Point3f(0), markersize = 1, marker = Rect3f(-0.5, -0.5, -0.5, 1, 1, 1))
scale!(p, 1.0/sqrt(2), 1.0/sqrt(2), 1.0/sqrt(2))
Makie.rotate!(p, Vec3f(0,0,1), pi/4)

startpoints = Makie.apply_transform_and_model(p, ps)
endpoints = Makie.apply_transform_and_model(p, ps + ps)
meshscatter!(a, startpoints, color = :red)
meshscatter!(a, endpoints, color = :red)

f
end
5 changes: 3 additions & 2 deletions ReferenceTests/src/tests/generic_components.jl
Original file line number Diff line number Diff line change
Expand Up @@ -474,8 +474,9 @@ end

# barplot, arrows, contourf, volumeslices, band, spy, heatmapshader
p9 = barplot!(scene, [180, 200, 220], [40, 20, 60])
p10 = arrows!(scene, Point2f[(200, 30)], Vec2f[(0, 20)], linewidth = 5, arrowsize = Vec3f(20))
p11 = arrows!(scene, Point3f[(220, 80, 0)], Vec3f[(-30, -10, 0)], linewidth = 5, arrowsize = Vec3f(15))
p10 = arrows2d!(scene, Point2f[(200, 30)], Vec2f[(0, 30)], shaftwidth = 4, tiplength = 15, tipwidth = 12)
p11 = arrows3d!(scene, Point3f[(220, 80, 0)], Vec3f[(-48, -16, 0)],
shaftradius = 2.5, tiplength = 15, tipradius = 7, markerscale = 1.0)
p12 = contourf!(scene, 240..280, 10..50, [1 2 1; 2 0 2; 1 2 1], levels = 3)
p13 = spy!(scene, 240..280, 60..100, [1 2 1; 2 0 2; 1 2 1])
p14 = band!(scene, [150, 180, 210, 240], [110, 80, 90, 110], [120, 110, 130, 120])
Expand Down
4 changes: 2 additions & 2 deletions WGLMakie/assets/particles.vert
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ vec3 qmul(vec4 q, vec3 v){

void rotate(vec4 q, inout vec3 V, inout vec3 N){
V = qmul(q, V);
N = normalize(qmul(q, N));
N = qmul(q, N);
}

vec4 to_vec4(vec3 v3){return vec4(v3, 1.0);}
Expand Down Expand Up @@ -99,7 +99,7 @@ void main(){
position_world = model * to_vec4(to_vec3(get_offset())) + vec4(vertex_position, 0);

process_clip_planes(position_world.xyz);
o_normal = normalize(N);
o_normal = N;
frag_color = vertex_color(get_color(), get_colorrange(), colormap);
frag_uv = apply_uv_transform(get_uv_transform(), get_uv());
// direction to camera
Expand Down
Binary file added docs/src/assets/arrow_components.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading