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

spacings in hstack/vstack/gridstack #1169

Open
IgorDouven opened this issue Jun 22, 2018 · 14 comments
Open

spacings in hstack/vstack/gridstack #1169

IgorDouven opened this issue Jun 22, 2018 · 14 comments

Comments

@IgorDouven
Copy link

Is there a way to fine-tune spacings when using hstack/vstack/gridstack? I tried to control this via plot_padding for the individual plots that are being stacked, but this doesn't allow me to reduce vertical space between the rows (which is badly needed in my plot). Something like Spacings in Mathematica's GraphicsGrid would be nice to have.

@Mattriks
Copy link
Member

Here is a wip vstack Mk 2 function - feedback welcome!

using Colors, Compose, DataFrames, Gadfly

function vstack2(plots::Vector{Plot}; spacing::Float64=0.0, heights::Vector{<:Real}=Float64[])
    n = length(plots)
    heights==Float64[] && (heights = fill(1/n, n))
    sumh = cumsum([0;heights])
    vpos = sumh + spacing.*[0; 1:n-1; n-1]
    M = [(context(0,v,1,h), render(p)) for (v,h,p) in zip(vpos[1:n], heights, plots)]
    return compose(context(units=UnitBox(0,0,1,vpos[n+1])), M...)
end

srand(123)
D = vcat([DataFrame(x=1:5, y=rand(5), g=g) for g in [" A "," B "," C "," D "]]...)

bgcol = RGBA(0.99,0.96,0.9,0.4)
pa = plot(D[1:10,:], y=:y, xgroup=:g, Geom.subplot_grid(layer(x=:x, Geom.point, Geom.hair)), style(panel_fill=bgcol))
pb = plot(D[11:20,:], y=:y, xgroup=:g, Geom.subplot_grid(layer(x=:x, Geom.point, Geom.hair)))

# spacing and heights are in the same "relative" units
p = vstack2([pa,pb,pa], spacing=-0.8, heights=[3,4,3])
draw(PNG(6inch,6inch),p)

issue1169

@IgorDouven
Copy link
Author

IgorDouven commented Jun 23, 2018

Thanks, but this didn't work for me. I would now have to first use hstack to collect some plots into rows and then I would want to use vstack2 to collate those rows. However, in that case the input for the latter function is of the wrong type (Compose.Context instead of Vector{Plot}). Is there a way around this?

@Mattriks
Copy link
Member

I tested this one with hstacked plots:

using Compose, Gadfly

function vstack2(plots::Vector{T}; spacing::Float64=0.0, heights::Vector{<:Real}=Float64[]) where T<:Union{Context,Plot}
    n = length(plots)
    heights==Float64[] && (heights = ones(n))
    sumh = cumsum([0;heights])
    vpos = sumh + spacing.*[0; 1:n-1; n-1]
    i = isa.(plots, Plot)
    any(i) && (plots[i] = render.(plots[i]))
    M = [(context(0,v,1,h), p)  for (v,h,p) in zip(vpos[1:n], heights, plots)]
    return compose(context(units=UnitBox(0,0,1,vpos[n+1])), M...)
end

@IgorDouven
Copy link
Author

This worked for me as well -- great many thanks! I'm closing the issue now, though it would be nice to have this function in the documentation.

@bjarthur
Copy link
Member

let's leave this open as adding this functionality would be useful

@bjarthur bjarthur reopened this Jun 24, 2018
@Mattriks
Copy link
Member

Mattriks commented Jun 25, 2018

Yes I would like to open a PR for this sometime (with new functions for Gadfly.vstack, Gadfly.hstack and Gadfly.gridstack). There is an issue here that currently Gadfly.{hv}stack use Compose.{hv}stack, but the new functions (e.g. vstack2 above) do not use Compose.{hv}stack. I think Compose.{hv}stack should remain as they are1, The question then is: what is the best way of incorporating the new Gadfly._stack functions into Gadfly? e.g. should they be renamed?

1Just adding that I think the idea here is to discontinue the use of the Compose._stack functions in Gadfly, but leave the Compose._stack functions as they are, for people who are using Compose to make graphics/pictures.

@bjarthur
Copy link
Member

i'd suggest making a new function, say figure(), which when given a column vector of Plots acts like vstack, given a row vector like hstack, and given a matrix like gridstack. keyword arguments could replace title and specify spacing.

would be great to also be able to do insets, as well as to align plots by their axis origins.

see https://cran.r-project.org/web/packages/egg/vignettes/Ecosystem.html for how R implements all of this functionality.

my only hesitation is that this approach is not declarative.

@Mattriks
Copy link
Member

I like the suggestion of a figure() function (tbd). I view it as a helper function, for users who want that. If you want declarative graphics than that functionality is in Compose e.g. here is an inset plot:

using Compose, DataFrames, Gadfly
pa = plot(XPI, x=:Year, y=:TCC, Geom.line,
    Coord.cartesian(ymax=150),
    Theme(default_color="red", panel_fill="CornSilk") )
pb = plot(XPI, x=:Year, y=:CPI, Geom.line, Coord.cartesian(ymin=10),
    Guide.ylabel("CPI (\$US '000s)"),
    Guide.title("Xmas Price Index
(https://en.wikipedia.org/wiki/Christmas_Price_Index)"),
    Guide.annotation(compose(context(0w,0h,0.75w,0.5h), render(pa))),
    Theme(default_color="green")
)

xpi

@jonathanBieler
Copy link
Contributor

jonathanBieler commented Dec 17, 2018

One issue with figure is that it's used to open and manage figures in Matlab-like figure manager. But since it's either without argument or just an index it could be fine.

What about a inset function for inset, that work like layer with additional position/size arguments ?

plot(
    layer(x=x, y=y, Geom.point)
    inset(x=z, y=w, Geom.line, position=(0w,0h,0.75w,0.5h) ),
)

@Mattriks
Copy link
Member

In Gadfly Grammar of Graphics, my plot above could be done perhaps like this:

plot( layer(x=Year, y=CPI, Geom.line, Theme(...)),
    layer(x=Year, y=TCC, Geom.inset(Geom.line, pos=(0.0w,0.0h,0.75w,0.5h)), Theme(...) ) 
)

similar in syntax to Geom.subplot_grid().

@CNOT
Copy link

CNOT commented Oct 17, 2019

Let me put this here too. It would be really nice, if one had the option to use the same legend or title or axis labels for a stack of plots. Geom.subplot_grid assumes there is some sort of categorized structure to your data, so it doesn't satisfy the simple cases where you basically just want to display the same data in different scales without repeating the legend.

Also, I don't know how to use the compose workarounds for a legend.

After some googling, I figured an R's ggplot2 approach would be to extract the legend from one plot and then create a custom layout and place each plot and the legend in their corresponding cells. grid_arrange_shared_legend

Is there a way to do the same in Julia's Gadfly?

@Mattriks
Copy link
Member

Here's my quick attempt at such a plot:

using Compose, DataFrames, Gadfly, RDatasets
mtcars = sort(dataset("datasets", "mtcars"), :Cyl)

myplot(D) = plot(D, x=:MPG, y=:WT, color=:Cyl, Geom.point, 
    Scale.color_discrete, Theme(key_position=:none, plot_padding=[1mm]))
Dp = by(mtcars, :Cyl, myplot) 

p1 = plot(mtcars, x=:MPG, y=:WT, color=:Cyl, Geom.point, 
    Scale.color_discrete, Theme(key_position=:none, plot_padding=[1mm]))

p2 = gridstack(reshape(Union{Plot,Compose.Context}[Dp.x1; context()], 2, 2))

gck = Guide.manual_color_key("", ["4","6","8"].*"\t\t", Scale.color_discrete().f(3))
p3 = compose(context(0.4,0.5), render(gck, Theme(), Gadfly.Aesthetics())[1].ctxs[3])

# vstack2 is in a post above
vstack2([hstack(p1, p2); p3], heights=[9,1])

grid_arrange

There are some color issues in p2, and the development of a Geom.subplot_wrap would help (#855).

@Mattriks
Copy link
Member

Example 2

gck = Guide.manual_color_key("Cylinders", ["4","6","8"].*"\t\t", Scale.color_discrete().f(3))
p3 = compose(context(),(context(0.5,0), render(gck, Theme(), Gadfly.Aesthetics())[1].ctxs[1]))
p2 = gridstack(reshape(Union{Plot,Compose.Context}[Dp.x1; p3], 2, 2))
hstack(p1, p2)

grid_arrange

@CNOT
Copy link

CNOT commented Oct 18, 2019

This actually helps a lot. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants