GLMakie plot-help 2: Issue with arrows and sliders

Hi,

This is second post related to https://discourse.julialang.org/t/glmakie-beginners-help-multiple-sliders-and-an-arrow/92113.
Currently: I am facing the following issues:

  1. Arrow heads pointing in the other direction: If I switch the base and head then it works fine[see figure of point 3]. :hushed:
arrows!(ax1, xbase, ybase, xhead, yhead)
#replaced with 
arrows!(ax1, xhead, yhead, xbase, ybase)
  1. How to get the point coordinates on the figure? GLMakie does not recognize the following:
refpoint = @lift(Point($xreference,$yreference))
scatter!(refpoint, color = :red, markersize = 20)

arrowheadpoint = @lift(Point(xhead,yhead))
scatter!(arrowheadpoint, color = :blue, markersize = 20)
  1. Creating another figure with multiple arrows all together but still using sliders: is that possible? For example, create a slider with 3 points then plot all the arrows?

Is the following strategy correct?

#Create a observables list [refpoint1, refpoint2, refpoint3, refpoint4]
b1 = [-4, 2, 6, -5] 
b2 = [16, 8, -18, -15]
#Create a list [arrowheadpoint1, arrowheadpoint2, arrowheadpoint3, arrowheadpoint4] 
h1 = [(x/(1 + 2 * 0.5 * r(x,y,0.5))) for x in b1, y in b2]
h2 = [y + r(x,y,0.5) for x in b1, y in b2]


#plot corresponding 4 arrows
arrows!(b1, b2, h1, h2, arrowsize = 0.2, lengthscale = 0.3)

The code so far with all the tries is attached here!

using GLMakie
using GeometryBasics
fig = Figure(resolution = (3456, 2234)) 
ax1 = fig[1, 1] = Axis(fig,
    # borders
    aspect = 1, limits = ((-10, 10), (-10, 10)),
    # title
    title = "sliders demo",
    titlegap = 48, titlesize = 60,
    # x-axis
    xautolimitmargin = (0, 0), xgridwidth = 2, xticklabelsize = 36,
    xticks = LinearTicks(20), xticksize = 18,
    # y-axis
    yautolimitmargin = (0, 0), ygridwidth = 2, yticklabelpad = 14,
    yticklabelsize = 36, yticks = LinearTicks(20), yticksize = 18
)

# darken axes

vlines!(ax1, [0], linewidth = 2)
hlines!(ax1, [0], linewidth = 2)

# create sliders

lsgrid = labelslidergrid!(fig,
    ["scale", "xcoordinate", "ycoordinate"],
    Ref(LinRange(-10:0.01:10));
    formats = [x -> "$(round(x, digits = 2))"],
    labelkw = Dict([(:fontsize, 30)]),
    sliderkw = Dict([(:linewidth, 24)]),
    valuekw = Dict([(:fontsize, 30)])
)

# set starting position for scale

set_close_to!(lsgrid.sliders[1], 1.0)

# layout sliders

sl_sublayout = GridLayout(height = 150)
fig[2, 1] = sl_sublayout
fig[2, 1] = lsgrid.layout

# create listener

scale = lsgrid.sliders[1].value

xreference = lsgrid.sliders[2].value

yreference = lsgrid.sliders[3].value


# arrow base points
xbase = @lift([$xreference])
ybase = @lift([$yreference])

refpoint = @lift(Point($xreference,$yreference))
scatter!(refpoint, color = :red, markersize = 20)

x = -10:0.01:10

m = @lift($scale .* x.^2)

d = @lift(1 ./ (2 .* $scale) .+ (1 ./(2 .* $scale)) .* cbrt.((13.5) .* $scale.^2 .* x.^2))

the_max = max(f(X[end]), g(X[1]))

#plot(X, f, fill = (the_max, 0.5, :auto))

line1 = lines!(ax1, x, m, color = :blue, linewidth = 5)
line2 = lines!(ax1, x, d, color = :red, linewidth = 5)


x = -10:0.01:10

p(x,y,scale) = -(2* scale *y-1)^2/(12 * scale^2) #p

q(x,y,scale) = ((2 * scale * y-1)^3-(27* scale^2) * (x^2))/(108 * scale^3) #q

del(x,y,scale) = ((27 * scale^2 * x^2 - 2((2 * scale * y)-1)^3) * x^2)/(1728* scale^4) #delta

r(x,y,scale) = del(x,y,scale) ≥ 0 ?  (-(scale * y+1)/(3* scale) + cbrt(-q(x,y, scale)/2 + sqrt(del(x,y, scale))) + cbrt(-q(x,y, scale)/2 - sqrt(del(x,y, scale)))) : -(scale * y+1)/(3* scale) + (abs(2* scale *y+1)/(3* scale))* cos((1/3) * acos((-q(x,y, scale)/2)/(-p(x,y, scale)/3)^(3/2))) #piecewise expression


xhead = lift(xreference, yreference, scale) do x, y, s
    return [ x/(1 + 2 * s * r(x,y,s)) ]
end
yhead = lift(xreference, yreference, scale) do x, y, s
    return [ y + r(x,y,s) ]
end


arrowheadpoint = @lift(Point(xhead,yhead))
scatter!(arrowheadpoint, color = :blue, markersize = 20)

arrows!(ax1, xbase, ybase, xhead, yhead)

#Figure 1
display(fig)

#Figure 2
#Create a observables list [refpoint1, refpoint2, refpoint3, refpoint4]
b1 = [-4, 2, 6, -5] 
b2 = [16, 8, -18, -15]
#Create a list [arrowheadpoint1, arrowheadpoint2, arrowheadpoint3, arrowheadpoint4] 
h1 = [(x/(1 + 2 * 0.5 * r(x,y,0.5))) for x in b1, y in b2]
h2 = [y + r(x,y,0.5) for x in b1, y in b2]


#plot corresponding 4 arrows
arrows!(b1, b2, h1, h2, arrowsize = 0.2, lengthscale = 0.3)

Thanks so much for your time! Any help with the code restructuring comments/criticisms and hints/references is highly appreciated.:).

As mentioned by others in the previous post, you should create what is called a “minimum working example” (MWE). This should be a very simple example that will run that demonstrates the problem you are having. The code you provided does not run and has quite a lot of complexities that are probably not necessary, but make it difficult for other people to read your code.

Related, whenever I try to do something complicated and new, I try to create a very simple example with only the elements that I need. Then when it works I can add complexity. If I were doing your project, for example, I would just start with something like a horizontal line or single parabola and a single arrow. Then build up from there.

With that in mind, I tried to simplify your code so that it will run. I left just a single arrow so that we can get that working. I don’t have time right now to try to get it to work the way I think you want, but I at least wanted to demonstrate how to make a MWE and try to understand your code better.

To answer your question, it should be possible to create three responsive arrows, but let’s start by getting one working. :wink:

Some notes on what I did:

  • Removed GeometryBasics package. I don’t see why you need this. (Maybe I’m wrong)
  • Updated syntax for the latest GLMakie (v0.8.0). Particularly using SliderGrid, as I mentioned in the previous post. (A lot has changed with many bug fixes. Of course if you have a very good reason for using the older version, let us know.)
  • Deleted a bunch of unnecessary code so that I can see how you are making just a single arrow that depends on xreference, yreference, and scale.

What I might try next:
I think the bones of what you want to do are there (for just this one arrow). I think I would adjust the equations you are using to calculate the endpoints, since I don’t think it is pointing at what you want. I don’t quite understand how you are calculating the arrow. The equations look pretty complicated and have some hard-coded values (1728?) that I don’t understand.

MWE:

using GLMakie

fig = Figure()
display(fig)
ax1 = Axis(fig[1, 1])

xlims!(-10, 10)
ylims!(-10, 10)

lsgrid = SliderGrid(fig[1, 2],
    (label = "scale", range = -10:0.01:10, format = "{:.1f}", startvalue = 1),
    (label = "xcoordinate", range = -10:0.01:10, format = "{:.1f}", startvalue = 1), 
    (label = "ycoordinate", range = -10:0.01:10, format = "{:.1f}", startvalue = 1),
    tellheight = false
)

scale = lsgrid.sliders[1].value
xreference = lsgrid.sliders[2].value
yreference = lsgrid.sliders[3].value

# arrow base points
xbase = @lift([$xreference])
ybase = @lift([$yreference])

refpoint = @lift(Point2f($xreference, $yreference))
scatter!(refpoint, color = :red, markersize = 20)

x = -10:0.01:10
m = @lift($scale .* x.^2)
d = @lift(1 ./ (2 .* $scale) .+ (1 ./ (2 .* $scale)) .* cbrt.((13.5) .* $scale.^2 .* x.^2))

line1 = lines!(ax1, x, m, color = :blue, linewidth = 5)
line2 = lines!(ax1, x, d, color = :red, linewidth = 5)

x = -10:0.01:10
p(x, y, scale) = -(2 * scale * y - 1)^2 / (12 * scale^2) #p
q(x, y, scale) = ((2 * scale * y - 1)^3 - (27 * scale^2) * (x^2)) / (108 * scale^3) #q
del(x, y, scale) = ((27 * scale^2 * x^2 - 2((2 * scale * y) - 1)^3) * x^2) / (1728 * scale^4) #delta
r(x, y, scale) = del(x, y, scale) ≥ 0 ?  (-(scale * y + 1)/(3* scale) + cbrt(-q(x, y, scale)/2 + sqrt(del(x, y, scale))) + cbrt(-q(x, y, scale) / 2 - sqrt(del(x, y, scale)))) : -(scale * y + 1) / (3 * scale) + (abs(2 * scale * y + 1) / (3 * scale)) * cos((1/3) * acos((-q(x, y, scale) / 2) / (-p(x, y, scale) / 3)^(3/2))) #piecewise expression

xhead = lift(xreference, yreference, scale) do x, y, s
    return [ x / (1 + 2 * s * r(x, y, s)) ]
end
yhead = lift(xreference, yreference, scale) do x, y, s
    return [ y + r(x, y, s) ]
end

arrows!(ax1, xbase, ybase, xhead, yhead)
2 Likes

Regarding the arrows!: I used a bit of a wrong notation for the example I gave in GLMakie beginners help: Multiple sliders and an arrow - #3 by fatteneder
E.g. I used xbase, ybase, xhead, yhead.
Just took another look at the docstring (try ?arrow in the REPL) and noticed that arrows wants arguments like arrows!(ax1, x, y, u, v) where x,y refer to the base point of the arrow(s) to draw and u, v are the directions of the arrow(s) as seen from the base points.

Maybe that helps with clearing up the arrows formulae.

Thanks Garek, I see one quick issue in this MWE. The reference point should not be on parabola but rather the arrow head should be on the parabola[always]. How to fix that? I double checked equations are mathematically correct. I will still look at it again for one more day.

Since u and v are direction vectors: so for two points (x_1,x_2) and (y_1,y_2), (u,v) will be (x_2-x_1, y_2-y_1)? Do I need to lift those[to work with the sliders] as well in this MWE?

If you want the arrows to properly react to your sliders, then yes.

xhead = lift(xreference, yreference, α) do x, y, s
    return [ x / (1 + 2 * s * r(x, y, s)) ]
end
yhead = lift(xreference, yreference, α) do x, y, s
    return [ y + r(x, y, s) ]
end

xdir = lift(xreference, yreference, xhead, yhead) do x, y, u, v
    return [  u - x ]
end

ydir = lift(xreference, yreference, xhead, yhead) do x, y, u, v
    return [ v - y ]
end

arrows!(ax1, xbase, ybase, xdir, ydir)

Is this the right way to lift (u,v)? The reason I lift without reference @ and $xhead, $yhead, because these two variables/observables are already referenced before?

yes looks good
did you not try it?

Theoretically the above is correct: but throw out

MethodError: no method matching -(::Vector{Float64}, ::Float64).

I can see the dimension mismatch of (xbase, ybase) being vectors[based on the sliders range input] and (xdir, ydir) being scalars. How to use lifting in such a case

xdir = lift(xreference, yreference, xhead, yhead) do x, y, u, v
    return [  u - x ]
end

ydir = lift(xreference, yreference, xhead, yhead) do x, y, u, v
    return [ v - y ]
end

so that (xdir, ydir) is also a vector?

I assume you defined xreference, yreference, xhead, yhead as in my post here: GLMakie beginners help: Multiple sliders and an arrow - #3 by fatteneder

As you say the problem is a dimension mismatch: [xy]head being (an observable of) a vector and [xy]reference being (an observable of ) a scalar.
Since your vectors [xy]base only have one component, just use

xdir = lift(xreference, yreference, xhead, yhead) do x, y, u, v
    return [  u[1] - x ]
end

ydir = lift(xreference, yreference, xhead, yhead) do x, y, u, v
    return [ v[1] - y ]
end

In the other post I did not anticipate that there might be a dimension mismatch.
So perhaps it would be good to re-organize the code a bit. Building on top of @garrek’s MWE from above:

using GLMakie

fig = Figure()
display(fig)
ax1 = Axis(fig[1, 1])

xlims!(-10, 10)
ylims!(-10, 10)

lsgrid = SliderGrid(fig[1, 2],
    (label = "scale", range = -10:0.01:10, format = "{:.1f}", startvalue = 1),
    (label = "xcoordinate", range = -10:0.01:10, format = "{:.1f}", startvalue = 1), 
    (label = "ycoordinate", range = -10:0.01:10, format = "{:.1f}", startvalue = 1),
    tellheight = false
)

scale = lsgrid.sliders[1].value
xreference = lsgrid.sliders[2].value
yreference = lsgrid.sliders[3].value

# arrow base points
xbase = @lift([$xreference])
ybase = @lift([$yreference])

refpoint = @lift(Point2f($xreference, $yreference))
scatter!(refpoint, color = :red, markersize = 20)

x = -10:0.01:10
m = @lift($scale .* x.^2)
d = @lift(1 ./ (2 .* $scale) .+ (1 ./ (2 .* $scale)) .* cbrt.((13.5) .* $scale.^2 .* x.^2))

line1 = lines!(ax1, x, m, color = :blue, linewidth = 5)
line2 = lines!(ax1, x, d, color = :red, linewidth = 5)

x = -10:0.01:10
p(x, y, scale) = -(2 * scale * y - 1)^2 / (12 * scale^2) #p
q(x, y, scale) = ((2 * scale * y - 1)^3 - (27 * scale^2) * (x^2)) / (108 * scale^3) #q
del(x, y, scale) = ((27 * scale^2 * x^2 - 2((2 * scale * y) - 1)^3) * x^2) / (1728 * scale^4) #delta
r(x, y, scale) = del(x, y, scale) ≥ 0 ?  (-(scale * y + 1)/(3* scale) + cbrt(-q(x, y, scale)/2 + sqrt(del(x, y, scale))) + cbrt(-q(x, y, scale) / 2 - sqrt(del(x, y, scale)))) : -(scale * y + 1) / (3 * scale) + (abs(2 * scale * y + 1) / (3 * scale)) * cos((1/3) * acos((-q(x, y, scale) / 2) / (-p(x, y, scale) / 3)^(3/2))) #piecewise expression

# compute base point of arrow
xhead = lift(xreference, yreference, scale) do x, y, s
    return x / (1 + 2 * s * r(x, y, s))
end
yhead = lift(xreference, yreference, scale) do x, y, s
    return y + r(x, y, s)
end
# compute direction of arrow
xdir = lift(xreference, yreference, xhead, yhead) do x, y, u, v
    return [ u - x ]
end

ydir = lift(xreference, yreference, xhead, yhead) do x, y, u, v
    return [ v - y ]
end

# promote arrow data to vectors for usage with arrows! below
v_xhead = @lift([$xhead])
v_yhead = @lift([$yhead])
v_xdir  = @lift([$xdir])
v_ydir  = @lift([$ydir])

arrows!(ax1, xbase, ybase, xdir, ydir)
1 Like