Cairomakie inset plot at specific (x,y) coordinates

Hi Guys,

by using BBox() one can create an inset plot. The location is determined by the arguments, which are given as pixels.
This works mostly but for certain use cases, it would be nice to specify an absolute location, i.e. in terms of coordinates from the main plot.

maybe this example demonstrates what I mean:

using CairoMakie
let
    x = -3:0.05:3
    y = exp.(-x .^ 2)
    fig = Figure(resolution = (600, 400))
    ax1 = Axis(fig[1, 1])
    lines!(ax1, x, y)
    scatter!(ax1,[1.2],[0.6],label = "I want the inset centered at (x,y) = (1.2,0.6)")
    axislegend()
    # inset
    ax2 = Axis(fig, bbox = BBox(140, 250, 200, 300), title = "inset  at (140, 250, 200, 300)")
    
    lines!(ax2, x, y, color = :red)
    display(fig)
end

grafik

Is there a simple way of doing this?

Not necessarily “simple” but you can project the point you want whenever the axis scene projection or position in the scene changes:

let
    x = -3:0.05:3
    y = exp.(-x .^ 2)
    fig = Figure(resolution = (600, 400))
    ax1 = Axis(fig[1, 1])
    lines!(ax1, x, y)
    scatter!(ax1,[1.2],[0.6],label = "I want the inset centered at (x,y) = (1.2,0.6)")
    axislegend()

    bbox = lift(ax1.scene.camera.projectionview, ax1.scene.px_area) do _, pxa
        p = Makie.project(ax1.scene, Point(1.2, 0.6))
        c = p + pxa.origin
        Rect2f(c .- Point2f(50, 50), (100, 100))
    end

    # inset
    ax2 = Axis(fig, bbox = bbox, title = "inset centered on point", backgroundcolor = (:white, 0.9))
    translate!(ax2.blockscene, 0, 0, 100)
    
    lines!(ax2, x, y, color = :red)
    fig
end

If you translate the axis blockscene forward it correctly covers the other content.

Another option could be to specify a rectangle in data coordinates:

let
    x = -3:0.05:3
    y = exp.(-x .^ 2)
    fig = Figure(resolution = (600, 400))
    ax1 = Axis(fig[1, 1])
    lines!(ax1, x, y)
    scatter!(ax1,[1.2],[0.6],label = "I want the inset centered at (x,y) = (1.2,0.6)")
    axislegend()

    bbox = lift(ax1.scene.camera.projectionview, ax1.scene.px_area) do _, pxa
        bl = Makie.project(ax1.scene, Point2f(0, 0)) + pxa.origin
        tr = Makie.project(ax1.scene, Point2f(2, 0.5)) + pxa.origin
        Rect2f(bl, tr - bl)
    end

    # inset
    ax2 = Axis(fig, bbox = bbox, title = "inset on rect in data coords")
    translate!(ax2.blockscene, 0, 0, 100)

    lines!(ax2, x, y, color = :red)
    fig
end

3 Likes

Thanks jules,
looks like this does exactly what I want.
For whoever is interested in this: Using this solution I defined myself a small helper function that creates an inset axis centered at a point with specified size (for now in pixels).

using CairoMakie
function getbboxatPoint(ax,coords,extent = (30,30))
    width,height = extent
    bbox = lift(ax.scene.camera.projectionview, ax.scene.px_area) do _, pxa
        p = Makie.project(ax.scene, Point(coords...))
        
        c = p + pxa.origin
        R = Rect2f(c .- Point2f(width, height),(2*width,2* height))
        println(c)
        R
    end
    return bbox
end

function insetAtPoint(fig,ax,Point,extent = (30,30); width = extent[1], height = extent[2],kwargs...)

    bbox = getbboxatPoint(ax,Point,extent;width,)

    ax2 = Axis(fig, bbox = bbox;kwargs...)
    translate!(ax2.blockscene, 0, 0, 100)
    return ax2
end

let
    x = -3:0.05:3
    y = exp.(-x .^ 2)
    fig = Figure(resolution = (500, 600))
    ax1 = Axis(fig[1, 1])
    lines!(ax1, x, y)
    insetCoords = (1,0.5)

    scatter!(ax1,[insetCoords[1]],[insetCoords[2]],label = "I want the inset centered at (x,y) = $(insetCoords)")
    axislegend()

    # inset
    ax2 = insetAtPoint(fig,ax1, insetCoords,(80,50); title = "inset centered on point", backgroundcolor = (:white, 0.9))
    translate!(ax2.blockscene, 0, 0, 100)
    
    lines!(ax2, x, y, color = :red)
    fig
end

Cheers!

Thank you @jules, @Salmon, this has been very useful for me. I updated @Salmon’s code to honor the xscale and yscale parameters of the parent axis. I also changed px_area to viewport (following a deprecation warning).

import CairoMakie

function getbboxatPoint(ax, coords, extent=(30, 30))
  width, height = extent
  xscale, yscale = ax.xscale[], ax.yscale[]
  bbox = CairoMakie.lift(ax.scene.camera.projectionview, ax.scene.viewport) do _, pxa
    p = CairoMakie.Makie.project(ax.scene, MM.Point(xscale(coords[1]), yscale(coords[2])))

    c = p + pxa.origin
    R = CairoMakie.Rect2f(c .- MM.Point2f(width, height), (2 * width, 2 * height))
  end
  return bbox
end

function insetAtPoint(fig, ax, Point, extent=(30, 30); kwargs...)

  bbox = getbboxatPoint(ax, Point, extent)

  ax2 = CairoMakie.Axis(fig, bbox=bbox; kwargs...)
  CairoMakie.translate!(ax2.blockscene, 0, 0, 100)
  return ax2
end