Makie: Axis through (0,0)

Hi there!

I wondered whether the following is possible in Makie.

import math
import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    a = []
    for item in x:
        a.append(1/(1+math.exp(-item)))
    return a

    
x = np.arange(-10., 10., 0.2)
sig = sigmoid(x)

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)

# Move left y-axis and bottom x-axis to centre, passing through (0,0)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('center')

# Eliminate upper and right axes
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')

# Show ticks in the left and lower axes only
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')

plt.plot(x,sig)
plt.show()

I have looked through the documentation but not found something to change the position of the spines such that they are centered through (0,0).

Thanks for any help!

Not inbuilt, although one could probably hack it in several ways. You could look at ax.xaxis, I think it has an endpoints observable. You could try setting that to the vertical middle of the axis area you have in ax.scene.px_area

When I was using Plots.jl, I would always make my axis go through (0, 0). Nowaday, I am using Makie and not really missing that feature. But for some plots it is really nice to have the option. Are there plans to add built-in support for this?

Not really plans, but it would in principle not be that difficult to add I think. It might just get a bit more messy with the observables if there are more possible states than top and bottom for xaxisposition etc.

In a quick try I already saw that the grid lines break when there’s an additional :center state, so stuff like that would need to be cleaned up.

Thanks for the idea @jules, I gave it a try here’s what came out. I managed to make the axis go through the middle, however the grid is know messed up.

using GLMakie

fig = Figure()
xticks = -5:1:5
yticks = -5:1:5
ax = Axis(fig[1, 1],  aspect=AxisAspect(1), xticks=xticks, yticks=yticks)
limits!(ax, -5.5, 5.5, -5.5, 5.5)
hidespines!(ax, :t, :r)
xend = ax.xaxis.attributes.endpoints
yend = ax.yaxis.attributes.endpoints

x1, x2 = xend[]
y1, y2= yend[]

Δy = (y2[2] - y1[2])/2
Δx = (x2[1] - x1[1])/2

xend[] = ([x1[1], x1[2] + Δy], [x2[1], x2[2] + Δy])
yend[] = ([y1[1] + Δx, y1[2]], [y2[1] + Δx, y2[2]])

xs = -5:0.1:5
lines!(ax, xs, 5*sin.(xs))

Ah sorry, did not see your last post. But yes I had the same problem :smiley:

Based on your solution, I obtained this

function cartesianaxis!(; origin=false)

    origin ? α = 1 : α = 0 
    # first include the (0,0) point into the plot
    scatter!([0],[0], marker = :circle, color = (:black, α))
    
    ax = current_axis()
    hidespines!(ax)
    ax.xticksvisible[] = false
    ax.yticksvisible[] = false

    autolimits!(ax) # TODO: Related with lifts

    # xlimitmargin = ax.xautolimitmargin[] 
    # ylimitmargin = ax.yautolimitmargin[]
    
    # # ax.xautolimitmargin[] = (0,0)
    # # ax.yautolimitmargin[] = (0,0)
    
    # data space
    xdatamin, xdatamax = ax.xaxis.attributes.limits[] 
    ydatamin, ydatamax = ax.yaxis.attributes.limits[]

    # pixel space Points
    xP1, xP2 = ax.xaxis.attributes.endpoints[]
    yP1, yP2 = ax.yaxis.attributes.endpoints[]

    # TODO: Work on lifts
    xtrs = - (xP2[1] - xP1[1]) / (xdatamax - xdatamin) * xdatamin
    ytrs = - (yP2[2] - yP1[2]) / (ydatamax - ydatamin) * ydatamin

    xP1 += [0, ytrs]
    xP2 += [0, ytrs]
    yP1 += [xtrs, 0]
    yP2 += [xtrs, 0]

    linesegments!(ax.blockscene, [xP1, xP2], color=:black, linewidth = 1.25)
    linesegments!(ax.blockscene, [yP1, yP2], color=:black, linewidth = 1.25)
    scatter!(ax.blockscene, [xP2], color =:black, marker=:rtriangle)
    scatter!(ax.blockscene, [yP2], color =:black, marker=:utriangle)

    f = current_figure()
end

This is the result for fig, ax, plt = lines(rand(10)); lines!(-1 .* rand(10)); cartesianaxis!()

You can hide the grid on your axis, then use a second axis just for the grid. It’s a bit messy but works ok if you can fix or don’t need zooming.

I also did a small tweak with the origin tick by excluding it from the ticks themselves and adding a text “0”.

This code could do with a tidy, but I’ll leave it here for now just as a reference for anyone else landing on this thread.

fig3 = Figure()
xlim = 7
ylim = 5
xticks = vcat([x for x in -xlim:-1], [x for x in 1:xlim])
yticks = vcat([x for x in -ylim:-1], [x for x in 1:ylim])

# Create second axis for gridlines only
ax4 = Axis(
    fig3[1, 1],
    xticks=xticks,
    yticks=yticks,
    xticklabelsvisible=false,
    xticksvisible=false,
    yticklabelsvisible=false,
    yticksvisible=false,
)
hidespines!(ax4)

# Now create axis to be transformed
ax3 = Axis(
    fig3[1, 1],
    xticks=xticks,
    yticks=yticks,
    xgridvisible=false,
    ygridvisible=false,
)
hidespines!(ax3, :t, :r)

margin = 0.5
limits!(ax3, -xlim-margin, xlim+margin, -ylim-margin, ylim+margin)
limits!(ax4, -xlim-margin, xlim+margin, -ylim-margin, ylim+margin)

# Adjust aspect ratio based on limits
ax3.aspect=xlim/ylim
ax4.aspect=xlim/ylim

# Translate spines
x1, x2 = ax3.xaxis.attributes.endpoints[]
xl, xo = x1
xr = x2[1]
y1, y2 = ax3.yaxis.attributes.endpoints[]
yo, yb = y1
yt = y2[2]
new_xo = yb + (yt - yb)/2.0
new_yo = xl + (xr - xl)/2.0

ax3.xaxis.attributes.endpoints[] = ([xl, new_xo], [xr, new_xo])
ax3.yaxis.attributes.endpoints[] = ([new_yo, yb], [new_yo, yt])

# Label origin
text!(ax3, -0.4, -0.5, text="0", color=:white)

# Test lines
xs = LinRange(-xlim, xlim, 150)
lines!(ax3, xs, x->0.25x^2 - 4)
lines!(ax3, [0, 0], [-5, 5], linestyle=:dash)
lines!(ax3, [-7, 7], [0, 0], linestyle=:dash)
lines!(ax3, [-7, 7], [-4, -4], linestyle=:dash)

save("center-spines.png", fig3)

Did you try yautolimitmargin = (0, 0), xautolimitmargin=(0,0)

   Ax_1 = Axis(Fig[1, 1]; ... yautolimitmargin = (0, 0), xautolimitmargin = (0, 0))