Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New recipe: lens #2372

Merged
merged 14 commits into from
Mar 5, 2020
22 changes: 22 additions & 0 deletions src/examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,28 @@ const _examples = PlotExample[
),
],
),
PlotExample(
"Lens",
"A lens lets you easily magnify a region of a plot. x and y coordinates refer to the to be magnified region and the via the `inset` keyword the subplot index and the bounding box (in relative coordinates) of the inset plot with the magnified plot can be specified. Additional attributes count for the inset plot.",
[
quote
begin
plot(
[(0, 0), (0, 0.9), (1, 0.9), (2, 1), (3, 0.9), (80, 0)],
legend = :outertopright,
)
plot!([(0, 0), (0, 0.9), (2, 0.9), (3, 1), (4, 0.9), (80, 0)])
plot!([(0, 0), (0, 0.9), (3, 0.9), (4, 1), (5, 0.9), (80, 0)])
plot!([(0, 0), (0, 0.9), (4, 0.9), (5, 1), (6, 0.9), (80, 0)])
lens!(
[1, 6],
[0.9, 1.1],
inset = (1, bbox(0.5, 0.0, 0.4, 0.4)),
)
end
end,
],
),
]

# Some constants for PlotDocs and PlotReferenceImages
Expand Down
85 changes: 85 additions & 0 deletions src/recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,91 @@ end
# note: don't add dependencies because this really isn't a drop-in replacement


# ---------------------------------------------------------------------------
# lens! - magnify a region of a plot
lens!(args...;kwargs...) = plot!(args...; seriestype=:lens, kwargs...)
export lens!
@recipe function f(::Type{Val{:lens}}, plt::AbstractPlot)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised this works - it's the type recipe signature, which is usually just used for type conversions to vector. Should this not be a "user recipe" with a @userplot shorthand defined?

Copy link
Member Author

@BeastyBlacksmith BeastyBlacksmith Jan 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought, this is the signature of a plot recipe ( and a valid usecase )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also I would like to only define the lens! shorthand, since lens by itself doesn't make too much sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you're right. I don't think they are used anywhere in the ecosystem, but I may be wrong (it lists MarginalHist as an example, but looking down on that page clearly shows that marginalhist is a "user recipe"). Defining a lens! shorthand makes sense - but I feel that calling it with seriestype as done here feels unideomatic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be a more ideomatic alternative? I don't bother too much to change this.

Copy link
Member

@mkborregaard mkborregaard Jan 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just the lens! shorthand? Sorry I have zero experience using plot recipes

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, now I got it. You meant the way to call the recipe is unideomatic, I thought the implementation with using the seriestype. I will add the lens! shorthand.

sp_index, inset_bbox = plotattributes[:inset_subplots]
if !(width(inset_bbox) isa Measures.Length{:w,<:Real})
throw(ArgumentError("Inset bounding box needs to in relative coordinates."))
end
sp = plt.subplots[sp_index]
xl1, xl2 = xlims(plt.subplots[sp_index])
bbx1 = xl1 + left(inset_bbox).value * (xl2 - xl1)
bbx2 = bbx1 + width(inset_bbox).value * (xl2 - xl1)
yl1, yl2 = ylims(plt.subplots[sp_index])
bby1 = yl1 + (1 - bottom(inset_bbox).value) * (yl2 - yl1)
bby2 = bby1 + height(inset_bbox).value * (yl2 - yl1)
bbx = bbx1 + width(inset_bbox).value * (xl2 - xl1) / 2
bby = bby1 + height(inset_bbox).value * (yl2 - yl1) / 2
lens_index = last(plt.subplots)[:subplot_index] + 1
x1, x2 = plotattributes[:x]
y1, y2 = plotattributes[:y]
seriestype := :path
label := ""
linecolor := :lightgray
bbx_mag = (x1 + x2) / 2
bby_mag = (y1 + y2) / 2
xi_lens, yi_lens = intersection_point(bbx_mag, bby_mag, bbx, bby, abs(bby2 - bby1), abs(bbx2 - bbx1))
xi_mag, yi_mag = intersection_point(bbx, bby, bbx_mag, bby_mag, abs(y2 - y1), abs(x2 - x1))
# add lines
if xl1 < xi_lens < xl2 &&
yl1 < yi_lens < yl2
@series begin
subplot := sp_index
x := [xi_mag, xi_lens]
y := [yi_mag, yi_lens]
()
end
end
# add magnification shape
@series begin
subplot := sp_index
x := [x1, x1, x2, x2, x1]
y := [y1, y2, y2, y1, y1]
()
end
# add subplot
for series in sp.series_list
@series begin
plotattributes = merge(plotattributes, copy(series.plotattributes))
subplot := lens_index
label := ""
xlims := (x1, x2)
ylims := (y1, y2)
()
end
end
backup = copy(plotattributes)
empty!(plotattributes)
seriestype := :path
series_plotindex := backup[:series_plotindex]
end

function intersection_point(xA, yA, xB, yB, h, w)
s = (yA - yB) / (xA - xB)
hh = h / 2
hw = w / 2
# left or right?
if -hh <= s * hw <= hh
if xA > xB
# right
return xB + hw, yB + s * hw
else # left
return xB - hw, yB - s * hw
end
# top or bot?
elseif -hw <= hh/s <= hw
if yA > yB
# top
return xB + hh/s, yB + hh
else
# bottom
return xB - hh/s, yB - hh
end
end
end
# ---------------------------------------------------------------------------
# contourf - filled contours

Expand Down