Replies: 9 comments
-
@jkrumbiegel @piever someone told me it'd be best to ping the two of you here! I'd be happy to hear your thoughts on this abstract concept idea 😄 |
Beta Was this translation helpful? Give feedback.
-
I also think it would be nice to get rid of the In my mind, it seems like a possible solution would be to encode the orientation directly in the OTOH, I confess I haven't a clear idea as to what |
Beta Was this translation helpful? Give feedback.
-
@piever an even better approach would be to stop using x/y/z at all. I'd instead make them just positional arguments and encode the layout within the types and order of them (as proposed). What do you think about all the other benefits I came up with by generalizing it into an AxisSpace type? Generally speaking, having x/y/z variants is a strong hint towards lacking abstraction. |
Beta Was this translation helpful? Give feedback.
-
To flip the axis is suprisingly simple (but does need some patching): using Makie, CairoMakie
# patch a bug in Makie, will push a PR laters
@eval Makie begin
function apply_transform(f::PointTrans{N}, point::VecTypes{N}) where N
return f.f(point)
end
function apply_transform(f::PointTrans{N1}, point::VecTypes{N2}) where {N1, N2}
p_dim = to_ndim(Point{N1, Float32}, point, 0.0)
p_trans = f.f(p_dim)
if N1 < N2
p_large = ntuple(i-> i <= N1 ? p_trans[i] : point[i], N2)
return Point{N2, Float32}(p_large)
else
return to_ndim(Point{N2, Float32}, p_trans, 0.0)
end
end
end Then we can define a transformation which just reverses coordinate order: trans = Makie.PointTrans{2}(reverse)
Makie.apply_transform(::typeof(trans), r::Rect2) = Rect2(reverse(r.origin), reverse(r.widths))
Finally we plot: tbl = (x = [1, 1, 1, 2, 2, 2, 3, 3, 3],
height = 0.1:0.1:0.9,
grp = [1, 2, 3, 1, 2, 3, 1, 2, 3],
grp1 = [1, 2, 2, 1, 1, 2, 1, 1, 2],
grp2 = [1, 1, 2, 1, 2, 1, 1, 2, 1]
)
fig, ax, plt = barplot(tbl.x, tbl.height,
stack = tbl.grp,
color = tbl.grp,
orientation = :x)
st = Stepper(fig)
ax.title[] = "Step 1"
Makie.step!(st)
ax.scene.transformation.transform_func[] = trans
ax.title[] = "Step 2"
Makie.step!(st)
save("axis_transformation", st)
Note that the ticks are off here. Adding so we only need to make the standard We could theoretically implement this using a @piever: I agree with your idea that "x" should always refer to horizontal, and "y" to vertical. @rapus95: I'm not sure that the idea you propose would work with generic (non-affine) transforms, e.g. geographic projections. However, this prototype does seem to do something like what you're proposing, and further transformations could also be composed onto this. |
Beta Was this translation helpful? Give feedback.
-
Flipping the axis still doesn't solve the problem if you have a recipe that is only defined in one direction that you want to use with a recipe that is defined in the other direction. If you flip the axis you have the reverse issue. So we do need support for every plot object to flip it, however that is done most efficiently. |
Beta Was this translation helpful? Give feedback.
-
I think this is the key point. I wonder whether this happens in practice. I was coming to the conclusion that flipping just one plot but not others on the same axis is mostly error-prone and shouldn't be supported. (At least, it would certainly confuse the scale / labelling mechanism of AlgebraOfGraphics.) Still, I could well be missing some important cases. Do you have a concrete example in mind when this is useful? |
Beta Was this translation helpful? Give feedback.
-
I personally see the low level drawing mechanism as somewhat removed from a higher level view. In a way a density plot is just some patch, and people often create plots where they add elements not in a semantically congruent way, but just as a stopgap solution or because it quickly gets them their results. Of course AlgebraOfGraphics has to take a much stricter view on this. I imagine a scenario where you've added a bunch of plots to an axis, and the last thing you want to add is a density plot, but flipped. So now do you have to flip the axis and flip all other plotting functions' arguments as well? |
Beta Was this translation helpful? Give feedback.
-
@asinghvi17 I would model geographic projections as a special case with 2 Domain space dimensions which map to a boolean (0-dimensional codomain) whether a domain space pixel is set or not. An image that's mapped becomes a 3d object, 2 domain spaces and color as codomain and so on. @jkrumbiegel well yes, if at some point you want to flip everything then you would need to do that explicitly. But given the abstraction I propose it theoretically would be possible to just store the space objects in the plot object and make a modifying call that swaps the order of spaces. this could even be a method for |
Beta Was this translation helpful? Give feedback.
-
Stumbling over it again, I thought it'd make sense to slightly restructure my idea and get at it from a different perspective. I also split it up into two different proposals which are entirely orthogonal. Proposal 1: Introduce an AxisSpace typeConceptstruct AxisSpace{T} #T is the type of the values of the dimension. (Number, String, DateTime etc)
limits::Tuple{T,T}
label
#...
end
struct Axis
horizontal::AxisSpace
vertical::AxisSpace
#Axis3 would just get another AxisSpace
title
plots
#...
end Benefits
Breaking?We might be able to get there w/o breaking anything by redirecting all properties that start with x or y into the corresponding AxisSpace instance. If properly implemented, this shouldn't have any overhead at all due to constant propagation. function Base.getproperty(a::Axis, sym::Symbol)
tmp = string(sym)
frst, rst = tmp[1], Symbol(tmp[2:end])
if frst=='x'
return getproperty(a.horizontal, rst)
elseif frst=='y'
return getproperty(a.vertical, rst)
else
return getfield(a, sym)
end
end
#plus setters & constructors Proposal 2: encode plot direction into wrapper typesConceptmight need better names for these types because those words are particularly related to mathematics (which I like though). Not meant to entirely replace the current syntax, but to complement it to always have a uniform and unambiguous way for describing the intention and concisely implementing it. abstract type DimCoDim{T} end
abstract type AbstractDomain{T} <: DimCoDim{T} end
abstract type AbstractCodomain{T} <: DimCoDim{T} end
struct Domain{T} <: AbstractDomain{T} #default that wraps the plot data
data::T
end
struct Codomain{T} <: AbstractCodomain{T} #default that wraps the plot data
data::T
end
#do actual work, optionally specialized for different axis data types
plot(p::CombinedPlot, hor::DimCoDim, ver::DimCoDim; kwargs...) = dowork(...)
# fallback that implements current meaning of argument order and plot direction
# and can be specialized for certain Plot types to use different defaults.
function plot(p::CombinedPlot, a, b; direction=:x, kwargs...)
if direction==:x
return plot(p, Codomain(b), Domain(a))
else
return plot(p, Domain(a), Codomain(b))
end
end Example
Benefits
|
Beta Was this translation helpful? Give feedback.
-
Over time this proposal evolved a bit further. There you find the current state: #1731 (comment)
Outdated
Proposal: Introduce an AxisSpace type
First draft (=everything in this issue is an idea to evolve into a perfect solution for as many problems as possible):
Alternative names:
AxisSpace: AxisDimension
TargetSpace: ImageSpace
Direction keyword (#926)
Solves the problem by getting rid of the keyword entirely and encoding the plot direction in the relative position of DomainSpace and TargetSpace objects:
This would plot to the right because the domainspace is on the vertical axis. Switching the two arguments will plot to the top.
Bonus:
Axis decoration rendering (#1347)
easily extensible interface for plotting different axis value types independently of x, y etc:
Axis linking
This also allows for a simple way of linking multiple axes by simply supplying the same space to multiple plots! (And in a further future we could even derive layout optimizations like omitting some axis labels if neighboring plots share the same AxisSpace for the same axis)
higher dimensional plotting
We can encode data as a combination of any number of DomainSpace and TargetSpace objects selectively plotted across multiple plots. 3D plots would just be 3 AxisSpace objects passed together. If it's 2 DomainSpace it most porbably will be some surface type of plotting (we could even use this for guidance/error checking regarding correct use of plot types) and if it's 2 TargetSpace it refers to some curve-like plotting.
Compatibility
Since we introduce a new type we can easily introduce it into the syntax and just redirect the current syntax into proper AxisSpace calls. At some future point we can deprecate some of the current syntax and on a major version increase drop it. But until then it just offers a way more versatile way of plotting!
Tl;Dr: As the proposal is strictly more powerful, we can just embed the current syntax into it and thus have maximum compatibility.
Opinion
I like this idea especially because it brings us closer to the mathematical concept of plotting. And given Julia's capability to recreate mathematical generalizations, I assume it's profitable to stay close to mathematics.
Beta Was this translation helpful? Give feedback.
All reactions