function ticks_and_labels end

module Formatters

    using Showoff

    function scientific(ticks::AbstractVector)
        Showoff.showoff(ticks, :scientific)
    end

    function plain(ticks::AbstractVector)
        try
            Showoff.showoff(ticks, :plain)
        catch e
            Base.showerror(stderr, e)
            println("with ticks: ", ticks)
            String["-Inf", "Inf"]
        end
    end

end
using .Formatters

"""
    $(SIGNATURES)

Plots a 2-dimensional axis.

## Theme
$(ATTRIBUTES)
"""
@recipe(Axis2D) do scene
    Theme(
        visible = true,

        showgrid = true,
        showticks = true,
		showtickmarks = true,
        padding = 0.1,

        ticks = Theme(

            ranges_labels = (automatic, automatic),
            formatter = Formatters.plain,

            gap = 3,
            title_gap = 3,

            linewidth = (1, 1),
            linecolor = ((:black, 0.4), (:black, 0.4)),
            linestyle = (nothing, nothing),

            textcolor = (:black, :black),
            textsize = (5, 5),
            rotation = (0.0, 0.0),
            align = ((:center, :top), (:right, :center)),
            font = lift(dim2, theme(scene, :font)),
        ),
		tickmarks = Theme(
			length = (3.0, 3.0),
			linewidth = (1,1),
			linecolor = ((:black, 0.4), (:black, 0.2)),
			linestyle = (nothing, nothing)
		),

        grid = Theme(
            linewidth = (0.5, 0.5),
            linecolor = ((:black, 0.3), (:black, 0.3)),
            linestyle = (nothing, nothing),
        ),

        frame = Theme(
            linewidth = 1.0,
            linecolor = :black,
            linestyle = nothing,
            axis_position = nothing,
            axis_arrow = false,
            arrow_size = 2.5,
            frames = ((false, false), (false, false)),
        ),

        names = Theme(
            axisnames = ("x", "y"),
            title = nothing,
            textcolor = (:black, :black),
            textsize = (6, 6),
            rotation = (0.0, -1.5pi),
            align = ((:center, :top), (:center, :bottom)),
            font = lift(dim2, theme(scene, :font)),
        )
    )
end

"""
    $(SIGNATURES)

Plots a 3-dimensional Axis.

## Theme
$(ATTRIBUTES)
"""
@recipe(Axis3D) do scene

    q1 = qrotation(Vec3f0(1, 0, 0), -0.5f0*pi)
    q2 = qrotation(Vec3f0(0, 0, 1), 1f0*pi)
    tickrotations3d = (
        qrotation(Vec3f0(0,0,1), -1.5pi),
        q2,
        qrotation(Vec3f0(1, 0, 0), -0.5pi) * q2
    )
    axisnames_rotation3d = tickrotations3d
    tickalign3d = (
        (:left, :center), # x axis
        (:right, :center), # y axis
        (:right, :center), # z axis
    )
    axisnames_align3d = tickalign3d
    tick_color = RGBAf0(0.5, 0.5, 0.5, 0.6)
    grid_color = RGBAf0(0.5, 0.5, 0.5, 0.4)
    grid_thickness = 1
    gridthickness = ntuple(x-> 1f0, Val(3))
    tsize = 5 # in percent
    Theme(
        visible = true,
        showticks = (true, true, true),
        showaxis = (true, true, true),
        showgrid = (true, true, true),
        scale = Vec3f0(1),
        padding = 0.1,
        names = Theme(
            axisnames = ("x", "y", "z"),
            textcolor = (:black, :black, :black),
            rotation = axisnames_rotation3d,
            textsize = (6.0, 6.0, 6.0),
            align = axisnames_align3d,
            font = lift(dim3, theme(scene, :font)),
            gap = 1
        ),

        ticks = Theme(
            ranges_labels = (automatic, automatic),
            formatter = Formatters.plain,

            textcolor = (tick_color, tick_color, tick_color),

            rotation = tickrotations3d,
            textsize =  (tsize, tsize, tsize),
            align = tickalign3d,
            gap = 1,
            font = lift(dim3, theme(scene, :font)),
        ),

        frame = Theme(
            linecolor = (grid_color, grid_color, grid_color),
            linewidth = (grid_thickness, grid_thickness, grid_thickness),
            axiscolor = (:black, :black, :black),
        )
    )
end

isaxis(x) = false
isaxis(x::Union{Axis2D, Axis3D}) = true


const Limits{N} = NTuple{N, Tuple{Number, Number}}

default_ticks(limits::Limits, ticks, scale_func::Function) = default_ticks.(limits, (ticks,), scale_func)
default_ticks(limits::Tuple{Number, Number}, ticks, scale_func::Function) = default_ticks(limits..., ticks, scale_func)

function default_ticks(lmin::Number, lmax::Number, ticks::AbstractVector{<: Number}, scale_func::Function)
    scale_func.((filter(t -> lmin <= t <= lmax, ticks)))
end
function default_ticks(lmin::Number, lmax::Number, ::Automatic, scale_func::Function)
    # scale the limits
    scaled_ticks, mini, maxi = optimize_ticks(
        scale_func(lmin),
        scale_func(lmax);
        k_min = 4, # minimum number of ticks
        k_max = 8, # maximum number of ticks
    )
    length(scaled_ticks) == 1 && isnan(scaled_ticks[1]) && return [-Inf, Inf]
    scaled_ticks
end

function default_ticks(lmin::Number, lmax::Number, n::Integer, scale_func = identity)
    scaled_ticks, mini, maxi = optimize_ticks(
        scale_func(lmin),
        scale_func(lmax);
        k_min = ticks, # minimum number of ticks
        k_max = ticks, # maximum number of ticks
        k_ideal = ticks,
        # `strict_span = false` rewards cases where the span of the
        # chosen  ticks is not too much bigger than amin - amax:
        strict_span = false,
    )
    scaled_ticks
end

default_ticks(x::Automatic, limits::Tuple, n) = default_ticks(limits, n, identity)
function default_ticks(ticks::Tuple, limits::Tuple, n::Tuple)
    default_ticks.(ticks, (limits,), n)
end

default_ticks(ticks::Tuple, limits::Limits, n) = default_ticks.(ticks, limits, (n,))

default_ticks(ticks::Tuple, limits::Limits, n::Tuple) = default_ticks.(ticks, limits, n)

default_ticks(ticks::AbstractVector{<: Number}, limits, n) = ticks


function default_labels(x::NTuple{N, Any}, formatter::Function) where N
    default_labels.(x, formatter)
end

function default_labels(x::AbstractVector, y::AbstractVector, formatter::Function = Formatters.plain)
    default_labels.((x, y), formatter)
end

function default_labels(ticks::AbstractVector, formatter::Function = Formatters.plain)
    if applicable(formatter, ticks)
        return formatter(ticks) # takes the whole array
    elseif applicable(formatter, first(ticks))
        return formatter.(ticks)
    else
        error("Formatting function $(formatter) is neither applicable to $(typeof(ticks)) nor $(eltype(ticks)).")
    end
end

default_labels(x::Automatic, ranges, formatter) = default_labels(ranges, formatter)
default_labels(x::Tuple, ranges::Tuple, formatter) = default_labels.(x, ranges, (formatter,))
default_labels(x::Tuple, ranges, formatter) = default_labels.(x, (ranges,), (formatter,))
default_labels(x::AbstractVector{<: AbstractString}, ranges, formatter::Function) = x
default_labels(x::AbstractVector{<: AbstractString}, ranges::AbstractVector, formatter::Function) = x



function convert_arguments(::Type{<: Axis2D}, limits::Rect)
    e = (minimum(limits), maximum(limits))
    (((e[1][1], e[2][1]), (e[1][2], e[2][2])),)
end
function convert_arguments(::Type{<: Axis3D}, limits::Rect)
    e = (minimum(limits), maximum(limits))
    (((e[1][1], e[2][1]), (e[1][2], e[2][2]), (e[1][3], e[2][3])),)
end
a_length(x::AbstractVector) = length(x)
a_length(x::Automatic) = x

function calculated_attributes!(::Type{<: Union{Axis2D, Axis3D}}, plot)
    ticks = plot.ticks
    args = (plot.padding, plot[1], ticks.ranges, ticks.labels, ticks.formatter)
    ticks[:ranges_labels] = lift(args...) do pad, lims, ranges, labels, formatter
        limit_widths = map(x-> x[2] - x[1], lims)
        pad = (limit_widths .* pad)
        # pad the drawn limits and use them as the ranges
        lim_pad = map((lim, p)-> (lim[1] - p, lim[2] + p), lims, pad)
        num_ticks = labels === automatic ? automatic : a_length.(labels)
        ranges = default_ticks(ranges, lim_pad, num_ticks)
        labels = default_labels(labels, ranges, formatter)
        (ranges, labels)
    end
    return
end

function draw_ticks(
        textbuffer, dim, origin, ticks,
        linewidth, linecolor, linestyle,
        textcolor, textsize, rotation, align, font
    )
    for (tick, str) in ticks
        pos = ntuple(i-> i != dim ? origin[i] : tick, Val(2))
        push!(
            textbuffer,
            str, pos,
            rotation = rotation[dim], textsize = textsize[dim],
            align = align[dim], color = textcolor[dim], font = font[dim]
        )
    end
end

function draw_tickmarks(
						linebuffer, dim, origin, ticks,dir::NTuple{N},
						linewidth, linecolor, linestyle,
						textcolor, textsize, rotation,align,font
						) where N
	for (tick, str) in ticks
		pos = ntuple(i-> i != dim ? origin[i] : tick, Val(2))
		posf0 = Point2f0(pos)
		dirf0  = Pointf0{N}(dir)
		append!(linebuffer,
		      [posf0, posf0 .+ dirf0],
		      color = linecolor[dim], linewidth = linewidth[dim]
		      )
	end
end





function draw_grid(
        linebuffer, dim, origin, ticks, dir::NTuple{N},
        linewidth, linecolor, linestyle
    ) where N
    dirf0 = Pointf0{N}(dir)
    for (tick, str) in ticks
        tup = ntuple(i-> i != dim ? origin[i] : tick, Val(N))
        posf0 = Pointf0{N}(tup)
        append!(
            linebuffer,
            [posf0, posf0 .+ dirf0],
            color = linecolor[dim], linewidth = linewidth[dim]#, linestyle = linestyle[dim]
        )
    end
end


function draw_frame(
        linebuffer, limits::NTuple{N, Any},
        linewidth, linecolor, linestyle,
        axis_position, axis_arrow, arrow_size, frames
    ) where N

    mini = minimum.(limits)
    maxi = maximum.(limits)
    rect = HyperRectangle(Vec(mini), Vec(maxi .- mini))
    origin = Vec{N}(0.0)
    if (origin in rect) && axis_position == :origin
        for i = 1:N
            start = unit(Point{N, Float32}, i) * Float32(mini[i])
            to = unit(Point{N, Float32}, i) * Float32(maxi[i])
            if false #axis_arrow
                arrows(
                    scene, [start, to],
                    linewidth = linewidth, linecolor = linecolor, linestyle = linestyle,
                    arrowsize = arrow_size
                )
            else
                append!(
                    linebuffer, [start, to],
                    linewidth = linewidth, color = linecolor #linestyle = linestyle,
                )
            end
        end
    end
    limit_widths = maxi .- mini
    frames = convert_attribute(frames, key"frames"())
    for side in 1:2
        from = Point{N, Float32}(getindex.(limits, side))
        # if axis is drawn at origin, and we draw frame from origin,
        # we already did this
        if !(from == origin && axis_position == :origin)
            for otherside in 1:2
                for dim in 1:N
                    if frames[N-dim+1][3-otherside]
                        p = ntuple(i-> i == dim ? limits[i][otherside] : limits[i][side], Val(N))
                        to = Point{N, Float32}(p)
                        append!(
                            linebuffer, [from, to],
                            linewidth = linewidth, color = linecolor#, linestyle = linestyle,
                        )
                    end
                end
            end
        end
    end
end

function draw_titles(
        textbuffer,
        xticks, yticks, origin, limit_widths,
        tickfont, tick_size, tick_gap, tick_title_gap,
        axis_labels,
        textcolor, textsize, rotation, align, font,
        title
    )
    tickspace_x = maximum(map(yticks) do tick
        str = last(tick)
        tick_bb = text_bb(str, to_font(tickfont[2]), tick_size[2])
        widths(tick_bb)[1]
    end)

    tickspace_y = widths(text_bb(
        last(first(xticks)), to_font(tickfont[1]), tick_size[1]
    ))[2]

    model_inv = inv(transformationmatrix(textbuffer)[])

    tickspace = transform(model_inv, (tickspace_x, tickspace_y))
    title_start = origin .- (tick_gap .+ tickspace .+ tick_title_gap)
    half_width = origin .+ (limit_widths ./ 2.0)

    posx = (half_width[1], title_start[2])
    posy = (title_start[1], half_width[2])
    positions = (posx, posy)
    for i = 1:2
        if !isempty(axis_labels[i])
            push!(
                textbuffer, axis_labels[i], positions[i],
                textsize = textsize[i], align = align[i], rotation = rotation[i],
                color = textcolor[i], font = font[i]
            )
        end
    end

    if title !== nothing
        # TODO give title own text attributes
        push!(
            textbuffer, title, (half_width[1], (origin .+ (limit_widths))[2]),
            textsize = textsize[1], align = (:center, :top), rotation = rotation[1],
            color = textcolor[1], font = font[1]
        )
    end
end


function ticks_and_labels(x)
    st, s = extrema(x)
    r = LinRange(st, s, 4)
    zip(r, string.(round.(r, 4)))
end

function transform(model::Mat4, x::T) where T
    x4d = to_ndim(Vec4f0, x, 0.0)
    to_ndim(T, model * x4d, 0.0)
end
un_transform(model::Mat4, x) = transform(inv(model), x)

to2tuple(x) = ntuple(i-> x, Val(2))
to2tuple(x::Tuple{<:Any, <: Any}) = x

function draw_axis2d(
        textbuffer,
        frame_linebuffer, grid_linebuffer,tickmarks_linebuffer,
        m, padding, limits, xyrange_labels,
        showgrid, showticks,showtickmarks,
        # grid attributes
        g_linewidth, g_linecolor, g_linestyle,

        # tick attributes
        t_linewidth, t_linecolor, t_linestyle,
        t_textcolor, t_textsize, t_rotation, t_align, t_font,
        t_gap, t_title_gap,

	#tickmark attribures
	tm_linewidth, tm_linecolor, tm_linestyle,
	tm_length,

        # frame attributes
        f_linewidth, f_linecolor, f_linestyle,
        f_axis_position, f_axis_arrow, f_arrow_size, f_frames,

        # title / axis name attributes
        ti_labels,
        ti_textcolor, ti_textsize, ti_rotation, ti_align, ti_font, ti_title
    )
    xyrange, labels = xyrange_labels
    start!(textbuffer); start!(frame_linebuffer); foreach(start!, grid_linebuffer)
    foreach(start!, tickmarks_linebuffer)
    # limits = limits Vec2f0(padding)
    # limits ((xmin, xmax), (ymin, ymax))
    limit_widths = map(x-> x[2] - x[1], limits)
    pad = (limit_widths .* to2tuple(padding))
    # pad the drawn limits
    limits = map((lim, p)-> (lim[1] - p, lim[2] + p), limits, pad)

    # recalculate widths
    limit_widths = map(x-> x[2] - x[1], limits)

    % = mean(limit_widths) / 100 # percentage
    xyticks = zip.(xyrange, labels)
    model_inv = inv(transformationmatrix(textbuffer)[])

    ti_textsize = ti_textsize .* %
    t_textsize = t_textsize .* %
    t_gap = transform(model_inv, to2tuple(t_gap .* %))
    tm_length = transform(model_inv, to2tuple(tm_length .* %))
    t_title_gap = transform(model_inv, to2tuple(t_title_gap .* %))

    origin = first.(limits)
    dirs = ((0.0, Float64(limit_widths[2])), (Float64(limit_widths[1]), 0.0))
    foreach(1:2, dirs, xyticks) do dim, dir, ticks
        if showgrid[dim]
            draw_grid(
                grid_linebuffer[dim], dim, origin, ticks, dir,
                g_linewidth, g_linecolor, g_linestyle
            )
        end
    end
    tm_offsets = ((0.0, Float64(tm_length[2])), (Float64(tm_length[1]), Float64(0.0)))
    foreach(1:2, tm_offsets, xyticks) do dim, offset, ticks
	    if showtickmarks[dim]
		    draw_tickmarks(
						   tickmarks_linebuffer[dim], dim, origin .- offset, ticks, offset,
						   t_linewidth, t_linecolor, t_linestyle,
						   t_textcolor, t_textsize, t_rotation, t_align, t_font
						   )
	    end
	end
	t_offsets = ((0.0, Float64(t_gap[2] + tm_length[2])), (Float64(t_gap[1]+tm_length[1]), Float64(0.0)))
    foreach(1:2, t_offsets, xyticks) do dim, offset, ticks
        if showticks[dim]
            draw_ticks(
                textbuffer, dim, origin .- offset, ticks,
                t_linewidth, t_linecolor, t_linestyle,
                t_textcolor, t_textsize, t_rotation, t_align, t_font
            )
	    end
    end

    draw_frame(
        frame_linebuffer, limits,
        f_linewidth, f_linecolor, f_linestyle,
        f_axis_position, f_axis_arrow, f_arrow_size,
        f_frames,
    )

    draw_titles(
        textbuffer, xyticks..., origin, limit_widths,
        t_font, t_textsize, t_gap, t_title_gap,
        ti_labels,
        ti_textcolor, ti_textsize, ti_rotation, ti_align, ti_font,
        ti_title
    )
    finish!(textbuffer); finish!(frame_linebuffer); foreach(finish!, grid_linebuffer)
	foreach(finish!, tickmarks_linebuffer)
    return
end

# for axis, we don't want to have plot!(scene, args) called on it, so we need to overload it directly
function plot!(scene::SceneLike, ::Type{<: Axis2D}, attributes::Attributes, args...)
    # create "empty" plot type - empty meaning containing no plots, just attributes + arguments
    cplot = Axis2D(scene, attributes, args)
    g_keys = (:linewidth, :linecolor, :linestyle)
    f_keys = (:linewidth, :linecolor, :linestyle, :axis_position, :axis_arrow, :arrow_size, :frames)
    t_keys = (
        :linewidth, :linecolor, :linestyle,
        :textcolor, :textsize, :rotation, :align, :font,
        :gap, :title_gap
    )
    tm_keys = (:linewidth, :linecolor, :linestyle, :length)
    ti_keys = (:axisnames, :textcolor, :textsize, :rotation, :align, :font, :title)

    g_args = getindex.(cplot.grid, g_keys)
    f_args = getindex.(cplot.frame, f_keys)
    t_args = getindex.(cplot.ticks, t_keys)
    tm_args = getindex.(cplot.tickmarks, tm_keys)
    ti_args = getindex.(cplot.names, ti_keys)

    textbuffer = TextBuffer(cplot, Point{2})

    grid_linebuffer = to_node((
        LinesegmentBuffer(
            cplot, Point{2}, transparency = true,
            linestyle = lift(first, cplot[:grid, :linestyle])
        ),
        LinesegmentBuffer(
            cplot, Point{2}, transparency = true,
            linestyle = lift(last, cplot[:grid, :linestyle])
        )
    ))
    frame_linebuffer = to_node(LinesegmentBuffer(
        cplot, Point{2}, transparency = true, linestyle = cplot.frame.linestyle
    ))
    tickmarks_linebuffer = to_node((
				    LinesegmentBuffer(
						      cplot, Point{2}, transparency = true,
						      linestyle=lift(first, cplot[:tickmarks, :linestyle])
						      ),
				    LinesegmentBuffer(
						      cplot, Point{2}, transparency = true,
						      linestyle = lift(last, cplot[:tickmarks, :linestyle])
						      )
    ))
    map_once(
        draw_axis2d,
        to_node(textbuffer),
        frame_linebuffer, grid_linebuffer,tickmarks_linebuffer,
        transformationmatrix(scene),
        cplot.padding, cplot[1], cplot.ticks.ranges_labels,
        lift.((dim2,), (cplot.showgrid, cplot.showticks, cplot.showtickmarks))...,
        g_args..., t_args..., tm_args..., f_args..., ti_args...
    )
    push!(scene, cplot)
    return cplot
end

function labelposition(ranges, dim, dir, tgap, origin::StaticVector{N}) where N
    a, b = extrema(ranges[dim])
    whalf = Float32(((b - a) / 2))
    halfaxis = unit(Point{N, Float32}, dim) .* whalf

    origin .+ (halfaxis .+ (normalize(dir) * tgap))
end


function GeometryTypes.widths(x::AbstractRange)
    mini, maxi = Float32.(extrema(x))
    maxi - mini
end


_widths(x::Tuple{<: Number, <: Number}) = x[2] - x[1]
_widths(x) = Float32(maximum(x) - minimum(x))

to3tuple(x::Tuple{Any}) = (x[1], x[1], x[1])
to3tuple(x::Tuple{Any, Any}) = (x[1], x[2], x[2])
to3tuple(x::Tuple{Any, Any, Any}) = x
to3tuple(x) = ntuple(i-> x, Val(3))

function draw_axis3d(textbuffer, linebuffer, limits, ranges_labels, args...)
    # make sure we extend all args to 3D
    ranges, labels = ranges_labels
    args3d = to3tuple.(args)
    (
        showaxis, showticks, showgrid,
        axisnames, axisnames_color, axisnames_size, axisrotation, axisalign,
        axisnames_font, titlegap,
        gridcolors, gridthickness, axiscolors,
        ttextcolor, trotation, ttextsize, talign, tfont, tgap
    ) = args3d # splat to names

    N = 3
    start!(textbuffer); start!(linebuffer)
    ranges_ticks = Pair.(ranges, labels)
    mini, maxi = first.(limits), last.(limits)

    ranges = map(i-> [mini[i]; first(ranges_ticks[i]); maxi[i]], 1:3)
    ticklabels = map(x-> [""; last(x); ""], ranges_ticks)
    origin = Point{N, Float32}(mini)
    limit_widths = maxi .- mini
    % = minimum(limit_widths) / 100 # percentage
    ttextsize = (%) .* ttextsize
    axisnames_size = (%) .* axisnames_size

    titlegap = (%) .* titlegap
    tgap = (%) .* tgap
    for i = 1:N
        axis_vec = unit(Point{N, Float32}, i)
        width = _widths(ranges[i])
        stop = origin .+ (width .* axis_vec)
        if showaxis[i]
            append!(linebuffer, [origin, stop], color = axiscolors[i], linewidth = 1.5f0)
        end
        if showticks[i]
            range = ranges[i]
            j = mod1(i + 1, N)
            tickdir = unit(Point{N, Float32}, j)
            tickdir, offset2 = if i != 2
                tickdir = unit(Vec{N, Float32}, j)
                tickdir, Float32(_widths(ranges[j]) + tgap[i]) * tickdir
            else
                tickdir = unit(Vec{N, Float32}, 1)
                tickdir, Float32(_widths(ranges[1]) + tgap[i]) * tickdir
            end
            for (j, tick) in enumerate(range)
                labels = ticklabels[i]
                if length(labels) >= j
                    str = labels[j]
                    if !isempty(str)
                        startpos = (origin .+ ((Float32(tick - range[1]) * axis_vec)) .+ offset2)
                        push!(
                            textbuffer, str, startpos,
                            color = ttextcolor[i], rotation = trotation[i],
                            textsize = ttextsize[i], align = talign[i], font = tfont[i]
                        )
                    end
                end
            end
            if !isempty(axisnames[i])
                tick_widths = if length(ticklabels[i]) >= 3
                    widths(text_bb(ticklabels[i][end-1], to_font(tfont[i]), ttextsize[i]))[1]
                else
                    0f0
                end
                pos = (labelposition(ranges, i, tickdir, titlegap[i] + tick_widths, origin) .+ offset2)
                push!(
                    textbuffer, to_latex(axisnames[i]), pos,
                    textsize = axisnames_size[i], color = axisnames_color[i],
                    rotation = axisrotation[i], align = axisalign[i], font = axisnames_font[i]
                )
            end
        end
        if showgrid[i]
            c = gridcolors[i]
            thickness = gridthickness[i]
            for _j = (i + 1):(i + N - 1)
                j = mod1(_j, N)
                dir = unit(Point{N, Float32}, j)
                range = ranges[j]
                for tick in drop(range, 1)
                    offset = Float32(tick - range[1]) * dir
                    append!(
                        linebuffer, [origin .+ offset, stop .+ offset],
                        color = c, linewidth = thickness
                    )
                end
            end
        end
        finish!(textbuffer); finish!(linebuffer)
    end
    return
end


function plot!(scene::SceneLike, ::Type{<: Axis3D}, attributes::Attributes, args...)
    axis = Axis3D(scene, attributes, args)
    textbuffer = TextBuffer(axis, Point{3}, transparency = true)
    linebuffer = LinesegmentBuffer(axis, Point{3}, transparency = true)

    tstyle, ticks, frame = to_value.(getindex.(axis, (:names, :ticks, :frame)))
    titlevals = getindex.(tstyle, (:axisnames, :textcolor, :textsize, :rotation, :align, :font, :gap))
    framevals = getindex.(frame, (:linecolor, :linewidth, :axiscolor))
    tvals = getindex.(ticks, (:textcolor, :rotation, :textsize, :align, :font, :gap))
    args = (
        getindex.(axis, (:showaxis, :showticks, :showgrid))...,
        titlevals..., framevals..., tvals...
    )
    map_once(
        draw_axis3d,
        Node(textbuffer), Node(linebuffer),
        axis[1], axis.ticks.ranges_labels, args...
    )
    push!(scene.plots, axis)
    return axis
end
