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

Wrapped facet & wrapped concat / repeat #393

Closed
kanitw opened this issue Mar 21, 2015 · 48 comments
Closed

Wrapped facet & wrapped concat / repeat #393

kanitw opened this issue Mar 21, 2015 · 48 comments

Comments

@kanitw
Copy link
Member

kanitw commented Mar 21, 2015

Current vegalite supports small multiples only on one axis (with row/col similar to Tableau)

However, it's useful to create wrapped small mutliples (similar to lattice in r / ggplot)
http://codealamode.blogspot.com/2012/02/trellis-graphs-in-ggplot2.html

or this example

@kanitw kanitw changed the title Add row&col / wrapped trellis as a shelf Add wrapped trellis as a shelf Apr 13, 2015
@kanitw kanitw added this to the 1.0 (Should Have Later) milestone Apr 13, 2015
@kanitw kanitw modified the milestones: 1.0 (Should Have Later), Nice to have Apr 21, 2015
@kanitw kanitw modified the milestones: Nice to have, x.x (Should Have Later) May 24, 2015
@kanitw kanitw added Easy and removed Easy labels Nov 13, 2015
@kanitw kanitw modified the milestones: x.x (Should Have Later), 1.x Should have after 1.0 , x.x (Nice to Have) Dec 1, 2015
@kanitw kanitw changed the title Add wrapped trellis as a shelf Support wrapped trellis Jan 12, 2016
@kanitw
Copy link
Member Author

kanitw commented Jan 19, 2016

Should this be called facet_wrap or facet_matrix?

ggplot uses the former, but essentially it's for creating trellis matrix.

@kanitw kanitw modified the milestones: x.x (Nice to Have), 2.x.x Patches & Features Oct 15, 2016
@eocarragain
Copy link

+1

@domoritz
Copy link
Member

We almost support this already. It's really easy with the new Vega layouts that we are already using.

@kanitw
Copy link
Member Author

kanitw commented May 19, 2018

I guess one argument for saying facet = wrapped facet is that it will likely be used more often.

(In a way, once we have wrapped facet, I suspect that people will use row/column mostly when they want to use both row and column?)

@jakevdp
Copy link
Contributor

jakevdp commented May 19, 2018

@kanitw I like that idea.

It has the added benefit that it removes the ambiguity of what it means if someone specifies both column and wrap, for example. This proposal lets you specify either:

  1. row and/or column, which has a well-defined output, or
  2. a wrapped facet, which has a well-defined output

but not both.

@davidanthoff
Copy link
Contributor

I think (not 100% sure yet, though) I would prefer a different name for the encoding channel and a top level operator, i.e. something different from @kanitw latest code example.

I'm (finally) landing on what I think is a really nice julia syntax for vega-lite, but the latest design iteration I have kind of relies on not having overlaps between the names of encoding channels and top level spec elements.

@kanitw
Copy link
Member Author

kanitw commented May 22, 2018

@davidanthoff If you can give a brief example of how your API looks like that would be great.

We might take that into consideration. That said, we will have to design what's best for the JSON format here so if the proposal to have facet = wrapped facet seems the best for VL, we may have to do it.


Put columns at the top-level?

By the way, for number of columns, I think it might be better to put it at the top-level, at least for the full syntax since (1) it's a property of the facet operator, not the field (2) we will likely want to apply the same idea to a wrapped repeat so the number of columns should be in a consistent place.

{
  // if facet is fieldDef right away, then it's a wrapped facet
  "facet": {"field": "Origin", "type": "nominal"} 
  "spec": ...,
  "columns": 4
}
{
  // if repeat is an array right away, then it's a wrapped repeat
  "repeat": ["a", "b", "c"] // can't put column inside here
  "spec": ...,
  "columns": 4
}

I guess the facet shorthand have two options (a) have column in the field def or (b) have no column at all (as it's not really a field's property), but users can customize that is a facet config.


Wrapped concat = Grid ?

Finally, thinking about wrapped concat -- that's basically a flow/grid layout. Not sure what's the best term, but columns can be consistently put at the top-level too.

// this will produce  
// spec1  spec2
// spec3  spec4

{
  "concat/grid/flow/wrapped_concat": [spec1, spec2, spec3, spec4],
  "columns": 2  
}

@davidanthoff
Copy link
Contributor

If you can give a brief example of how your API looks like that would be great.

Sure. Nothing is final, at this point, but this is where I'm going right now.

In general you pipe data into a spec. And the most verbose version of doing a spec resembles the JSON closely:

data |> @vlplot(
  mark=:bar,
  encoding={
    x={field=:field_a},
    y={field=:field_b}
  }
)

And then I provide a number of shortcuts that make common cases easier. I have altair's shorthand, the ability to use enc instead of encoding, and then for the mark type allow specifying the type positional without the mark= keyword. So that gets this down to:

data |> @vlplot(:bar,enc={x="field_a:q",y="field_b:q"})

I'd really like to also get rid of the nesting caused by enc=. In general I feel the hierarchical stuff is really a nice fit for the JSON format, but a real pain at the REPL for interactive work. Just keeping track how many parenthesis you need to close etc. is not a good experience at the REPL if I want to try something quickly. So my latest version also allows one to just skip the whole enc={} step. So then it looks like this:

data |> @vlplot(:bar, x="field_a:q", y="field_b:q")

I think that is pretty much ideal for the simple and common cases at the REPL where I might just want to quickly explore a dataset.

But this last step (optionally leaving out the enc={}) really only works if there are no name overlaps between top level elements and things that one can have inside enc={}... So I'm a bit nervous about that option because it would mean that if you guys introduce a name conflict in a new version of vega-lite, my design would kind of break :)

You can look at many more examples here, here, here and here. All the examples are copied from either the vega-examples or the altair examples, and I've always pasted the original above the julia syntax so that one can easily compare. These notebooks don't yet use all of the simplifications I described in this comment here.

@kanitw
Copy link
Member Author

kanitw commented May 22, 2018

@davidanthoff I see. I think you should distinguish between a Repl method for a single view spec (@vlplot) and methods for view composition like concat / facet as they are different kind of methods anyway. For example, if you have (@vlfacet) for a faceted spec, then facet would be the facet property of Facet spec in @vlfacet. In @vlplot, facet would then be the shortcut facet encoding.

@davidanthoff
Copy link
Contributor

Let me think about that. Right now the view composition is done mostly via various julia operator overloads (I don't have examples for all of those options up yet), which ends up being quite intuitive for the most part.

@domoritz
Copy link
Member

In collisions are a concern, another option is to add a prefix to the encodings: enc_color, enc_x, ... etc. However, I agree with @kanitw that composition should be an operator and not an argument.

@kanitw
Copy link
Member Author

kanitw commented May 25, 2018

@jheer proposes another alternative which is using row and column to indicate the orientation for the wrapped facet. Basically, we can get something like

data: ...
facet: {
   column: {field: 'category', type: 'nominal'}
},
columns: 5, // there will be 5 columns per row
spec: ...
data: ...
facet: {
   row: {field: 'category', type: 'nominal'}
},
rows: 5, // there will be 5 rows per column
spec: ...

If both facet.row and facet.column are specified, columns or rows will be ignored and there will no wrapping.

This syntax is nice in the sense that we preserve the existing syntax and only need to extend the facet spec with rows/columns.

(Note that we can support rows by doing some math.)

That said, writing this spec down, I start to feel that I still like the one above that just simply has facet: {field: ..., ...} more as the row/column channels feel a bit redundant here. It also seems to be more clear if we have a flow direction parameter (to say whether it is horizontal or vertical).

I also don't think that the MVP of this need to support wrapped facet with vertical flow.

@eibanez
Copy link
Contributor

eibanez commented May 25, 2018

I like that. But how would you defined this when you are giving column or row in encoding?

An alternative would be allowing one of the row/columns to be defined as it is and the other as a value. That would be read as: encode this data value as rows and use 5 columns.

@kanitw
Copy link
Member Author

kanitw commented May 25, 2018

An alternative would be allowing one of the row/columns to be defined as it is and the other as a value. That would be read as: encode this data value as rows and use 5 columns.

Not sure what you mean here. Can you elaborate?

@eibanez
Copy link
Contributor

eibanez commented May 25, 2018

Something like this:

facet: {
   column: {field: 'category', type: 'nominal'},
   row: 5  // there will be 5 rows
},
spec: ...```

@eibanez
Copy link
Contributor

eibanez commented May 25, 2018

Or {value: 5}.

This would allow you to write wrapped facets in this "long" or the "short" form (with encoding).

@kanitw
Copy link
Member Author

kanitw commented May 26, 2018

@eibanez We should not mix between number of columns/rows and the field that the row or column channels encode. You can actually encode things along the columns and limit the number of column to 5 columns per row too. Mixing them up introduce an unnecessary limitations.

@davidanthoff
Copy link
Contributor

think you should distinguish between a Repl method for a single view spec (@vlplot) and methods for view composition like concat / facet as they are different kind of methods anyway. For example, if you have (@vlfacet) for a faceted spec, then facet would be the facet property of Facet spec in @vlfacet. In @vlplot, facet would then be the shortcut facet encoding.

I thought about it, and I think I kind of like the idea of additional top level things like @vlfacet, but mostly as an additional option. One of the core design principles I followed (at least so far) was that one could take any valid vega-lite JSON spec and essentially translate it one-to-one to a call to @vlplot, by simply doing some very minimal syntax adjustments. I do think that is a valuable design principle because it makes it possible to essentially take pure vega-lite examples and documentation and apply it more or less directly in julia. I don't think I'd like to give up on that design principle for this one case, which suggests that the naming conflict would remain.

In collisions are a concern, another option is to add a prefix to the encodings: enc_color, enc_x

I think I'd also like to avoid that, it makes every case more verbose, which is something I'm trying to avoid.

So, from my point of view ideally you guys would use something like facet_wrap for the encoding channel. But, I can completely understand if other considerations swamp that. I think in that case I would just introduce facet_wrap (or wrap or something like it) as the encoding channel shorthand in the julia wrapper. That would be pretty simple. So while I'm trying to avoid too many of these deviations from the vega-lite spec, it would be a second best option that would certainly work.

@davidanthoff
Copy link
Contributor

using row and column to indicate the orientation for the wrapped facet. Basically, we can get something like...

I have to admit that I'm not a fan of having additional things like columns at the top level, I think that pulls things that in my mind belong together too far apart. I would much prefer that for the operator version, everything related to faceting is inside the facet element, i.e. I like the earlier proposal better.

@domoritz
Copy link
Member

domoritz commented May 29, 2018

One of the core design principles I followed (at least so far) was that one could take any valid vega-lite JSON spec and essentially translate it one-to-one to a call to @vlplot, by simply doing some very minimal syntax adjustments.

How does this work with arbitrarily nested specs? For example vconcat(hconcat(spec0,layer(spex5,spec6)), hconcat(layer(spec1, spec2, spec3)),spec4).

@davidanthoff
Copy link
Contributor

How does this work with arbitrarily nested specs?

Just write it down as JSON, remove the quotes around key names, replace : with = in key-value pairs, and it should all work. For example:

data |> @vlplot(
  vconcat=[
    {
      mark=:bar,
      encoding={
        x=:foo
      }
    },
    {
      mark=:line,
      encoding={
        x=:bar
      }
    }
  ]
)

There is a more convenient syntax to concat specs, but the raw thing just works as well.

@kanitw
Copy link
Member Author

kanitw commented Jun 6, 2018

Another complicated issue is that for row/column facet -- we use headers to display labels for each subplot of the facet.

However, for wrapped facet, we need to use a different mechanism (e.g., title of each subplot).

For this reason, the schema of facet wrap's field definition may be different from facet row/column' field definition as it wouldn't have "headers". Or we will have to re-design how we specify facet title and labels.

If we do the former, we shouldn't re-use facet row/column syntax for wrapped facet.

@domoritz
Copy link
Member

domoritz commented Oct 3, 2018

@sirahd Do you want to look into this?

@kanitw
Copy link
Member Author

kanitw commented Jan 15, 2019

I've summarize the proposed syntax in #393. So let's close this and continue our discussion there.

@kanitw kanitw closed this as completed Jan 15, 2019
@domoritz
Copy link
Member

The actual issue is #4457

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

7 participants