Zoomed inset using Makie?

Is it possible to achieve zooming in a portion of an (already drawn) axis and display it as another axis, or as an inset?

Something like this in matplotlib:

Yup this is possible, specifically using the BBox function (which specifies the bounding box of the Axis). Here’s a small example:

using CairoMakie
CairoMakie.activate!(type = "svg")

x = 0.0 : 0.1 : 10.0
y = exp.(-x)

begin
    fig = Figure(size=(800,800))
    ax1 = Axis(
        fig[1,1]; 
        xgridvisible=false, ygridvisible=false
    )

    lines!(ax1, x, y)
    bracket!(5, 0.01, 10, 0.01; offset=5, style=:square, orientation=:up)

    ax2 = Axis(
        fig; 
        yscale=log10,
        bbox=BBox(400, 750, 200, 600)
    )
    lines!(ax2, x[50:end], y[50:end])

    fig
end

This produces:

Hope this helps!

2 Likes

This here worked on GLMakie v0.8.11 (includes update of rectangle-zoom for interactivity). The idea is to turn this into something like an insetaxis method, but I haven’t found time for that yet.

using GLMakie
import GLMakie.Makie.GeometryBasics: coordinates
GLMakie.activate!()

x = collect(1:100)
y = randn(length(x))

fig = Figure()
ax = Axis(fig[1,1])
inset = Axis(fig[1,1]; width=Relative(0.8), height=Relative(0.2),
             halign=0.9, valign=0.1, backgroundcolor=(:white,1.0))

scatterlines!(ax, x, y)
subx, suby = x[30:40], y[30:40]
scatterlines!(inset, subx, suby)

poly!(ax, inset.finallimits, color=(:black,0), strokewidth=1, strokecolor=:black)

translate!(inset.blockscene, 0, 0, 1000)

shifted_bbox = lift(ax.scene.px_area, inset.layoutobservables.computedbbox,
                    ax.scene.camera.projectionview) do px_area, bbox, _
  Rect2f(bbox.origin .- px_area.origin, bbox.widths)
end

scatter!(ax, shifted_bbox, color=:red, space=:pixel)

vert_edges = [ Observable{Any}() for _ = 1:4 ]
onany(inset.finallimits, shifted_bbox) do bbox1, bbox2
  for (i,(c1, c2)) in enumerate(zip(coordinates(bbox1), coordinates(bbox2)))
    cc1 = Makie.project(ax.scene, Point2f(c1))
    vert_edges[i][] = [ cc1, Point2f(c2) ]
  end
end
notify(inset.finallimits)
for vs in vert_edges
  lines!(ax, vs, color=:grey, space=:pixel)
end

display(fig)

image

3 Likes

I don’t think this is what I need, because this example actually draws the data twice (which is what I am doing right now). It’s easy enough for a simple line plot, but if I have multiple layers, or even dynamically drawn axis, I would prefer to just use the data already in axis one.

This also draws the data twice, so not exactly what I am looking for.

I had been looking for something like this for a while; essentially the equivalent of matplotlib’s indicate_inset_zoom. I wrote it into a function which also selectively draws the lines which don’t cross the rectangle (similar to what matplotlib does by default). I’m posting here, because there might be others looking for something like this.

function draw_inset!(ax, inset)

	poly!(ax, inset.finallimits, color=(:black,0), strokewidth=1, strokecolor=:black)
	
	shifted_bbox = lift(ax.scene.viewport, inset.layoutobservables.computedbbox,
						ax.scene.camera.projectionview) do px_area, bbox, _
		Rect2f(bbox.origin .- px_area.origin, bbox.widths)
	end

	# scatter!(ax, shifted_bbox, color=:red, space=:pixel)

	vert_edges = [ Observable{Any}() for _ in 1:4 ]
	connect = [ Observable{Bool}() for _ in 1:4 ] 
	onany(inset.finallimits, shifted_bbox) do bbox1, bbox2
		for (i,(c1, c2)) in enumerate(zip(Makie.GeometryBasics.coordinates(bbox1), Makie.GeometryBasics.coordinates(bbox2)))
		cc1 = Makie.project(ax.scene, Point2f(c1))
		vert_edges[i][] = [ cc1, Point2f(c2) ]
		end

        rectlims = vcat(Makie.project(ax.scene, Point2f(inset.finallimits[].origin))..., 
            Makie.project(ax.scene, Point2f(inset.finallimits[].origin.+inset.finallimits[].widths))...) # x0 y0 x1 y1

        x0 = shifted_bbox[].origin[1] > rectlims[1]
        x1 = shifted_bbox[].origin[1] + shifted_bbox[].widths[1] > rectlims[3]
        y0 = shifted_bbox[].origin[2] > rectlims[2]
        y1 = shifted_bbox[].origin[2] + shifted_bbox[].widths[2] > rectlims[4]
        connect[1][] = xor(x0, y0)
        connect[2][] = x1==y0
        connect[3][] = x0==y1
        connect[4][] = xor(x1, y1) 
	end


	notify(inset.finallimits)

	for (c, vs) in zip(connect,vert_edges)
		lines!(ax, vs, color=:grey, space=:pixel, visible=c)
	end
end

The result looks like this:

The only bug I can’t be bothered to fix right now is that if no xlims or ylims are set, it will set them to (0,10). Calling xlims!() or ylims!() with empty argument fixes it though. Thanks @fatteneder !

1 Like

For this kind of functionality, see zoom_lines!(ax, inset) in MakieExtra.jl:

1 Like