-
Notifications
You must be signed in to change notification settings - Fork 629
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
Add Stack Transform #3771
Add Stack Transform #3771
Conversation
} else if (isStack(t)) { | ||
const stack = head = StackNode.makeFromTransform(head, t); | ||
|
||
for (const field of keys(stack.producedFields())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is called repeatedly through this method. In a separate PR, we should refactor this part #3772.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great start. Please see comments below.
I appreciate that you try to make atomic commits overall!
Some tips:
- Try to structure so that "refactor existing code" (like "Refactor to generate as property during make.") come earlier.
- Try to avoid make later commits overriding prior changes. (Otherwise these commits are no longer atomic, and then reviewers need to look at more diff than they need to.)
Note: Github currently has a stupid bug that "hide some of my comments". Please make sure to expand each of them and address them accordingly.
src/compile/data/stack.ts
Outdated
/** | ||
* Stack measure's field. Used in makeFromTransform | ||
*/ | ||
stack?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this different from field
? Isn't it exactly the same thing?
Note that the we should design the component such that it represents information that's equilvalent to what we need in the output Vega stack transform. (We should later decouple things that we need for impute
to a separate ImputeNode
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which one should I keep stack
or field
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also name it stackField
.
(We later do rename it in the assemble method anyway):
const {facetby, field: stackField, dimensionFieldDef, impute, offset, sort, stackby} = this._stack;
src/compile/data/stack.ts
Outdated
out[this._stack.field + '_start'] = true; | ||
out[this._stack.field + '_end'] = true; | ||
|
||
const out = this._stack.as.reduce((result, item) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just return this without declaring?
src/compile/data/stack.ts
Outdated
*/ | ||
sort: VgSort; | ||
sort: VgSort | SortField; // TODO Simplify the sort. make uses VgSort. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why union type here? Why not converting SortField to VgSort right away?
src/compile/data/stack.ts
Outdated
*/ | ||
field: string; | ||
field?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will keep either stack
or field
and make that compulsory.
src/compile/data/stack.ts
Outdated
const dimensionField = dimensionFieldDef ? vgField(dimensionFieldDef, {binSuffix: 'mid'}): undefined; | ||
// Detects whether assemble call is from Node created from make or MakeFromTransform | ||
// TODO Create a stricter function to check that. | ||
if (this._stack.stackby) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not add this additional nesting level. See the rationale in the comment on the "else" line.
src/transform.ts
Outdated
*/ | ||
groupby: string[]; | ||
/** | ||
* Mode for stacking marks. Default is 'zero' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use our standard default value format.
Mode for stacking marks.
__Default value:__ `"zero"`
src/transform.ts
Outdated
sort?: SortField ; | ||
/** | ||
* Output field names. | ||
* y2 or x2 is optional if only one name is provided the end will be “<name>_end” |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
People might not get what is x2
and y2
. Maybe:
Output field names. This can be either a string or an array of strings with two elements denoting the name for the fields for stack start and stack end respectively.
If a single string is provided, the end field will be "_end".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a single string is provided, the end field will be "_end".
Right now if a single string(eg. "values") is provided, the end field is "values_end".
test/compile/data/stack.test.ts
Outdated
"color": {"field": "c", "type": "ordinal",} | ||
} | ||
}); | ||
describe('StackNode.make', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makeForEncoding?
src/compile/data/stack.ts
Outdated
} else { | ||
const {stack, groupby, offset, sort, as} = this._stack; | ||
let normalizedAs: Array<string>; | ||
if (isAsValidArray(as)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this logic for as appearing here too? I don't think this is necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just see that you removed this in a later commit.
src/compile/data/stack.ts
Outdated
@@ -23,7 +23,7 @@ function getStackByFields(model: UnitModel): string[] { | |||
|
|||
export interface StackComponent { | |||
// TODO it would be cleaner to compose the two interfaces to create this one. | |||
// TODO remove Used in *comments | |||
// TODO Rename make to makeFromTransform |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename make to makeFromEncoding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have a few more comments :)
(For examples, you might want to separate examples to another branch after addressing these few comments as we're still debating about it on Slack.)
src/compile/data/stack.ts
Outdated
value: 0 | ||
}); | ||
} | ||
if(facetby) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why adding this if(facetby) {
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this since I haven't read how facetby
is generated, but the general logic behind the if
is that there are 3 code segments in assemble:
- Segment 1: when
this._stack
has been created bymakeFromEncoding
whereimpute
:true
- Segment 2: when
this._stack
has been created bymakeFromEncoding
- Segment 3. when
this._stack
has been created bymakeFromTransform
The existing code structure led me to believe that Segment 1 and Segment 2 are not disjoint and both can be executed for a single input spec.
To maintain a flat structure, initially this._stack
with impute:true
is handled, then all the this._stack
created by makeFromEncoding
(including the ones operated on in the previous step), then finally the ones created by makeFromTransform
which is disjoint to Segment 1 and 2.
facetby
is undefined
for any this._stack
created by makeFromTransform
. So those cases skip the if(facetby) {
block.
src/transform.ts
Outdated
* Output field names. This can be either a string or an array of strings with | ||
* two elements denoting the name for the fields for stack start and stack end | ||
* respectively. | ||
* If a single string(eg."val") is provided, the end field will be "val_end". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
string(eg."val")
string (e.g., "val")
src/compile/data/stack.ts
Outdated
/** | ||
* Faceted field. | ||
* Faceted field. Used in makeFromEncoding. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/compile/data/stack.ts
Outdated
} | ||
// TODO Better Name for this function | ||
function isAsValidArray(as: string[] | string): as is string[] { | ||
return isArray(as) && as.every(s => isString(s)) && as.length ===2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really wanna keep this method, check if array.length > 1. (The 3rd, 4th, ... elements can be ignored.)
src/transform.ts
Outdated
/** | ||
* Field that determines the order of leaves in the stacked charts. | ||
*/ | ||
sort?: SortField ; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SortField;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually shouldn't it be SortField[]
like window?
src/compile/data/stack.ts
Outdated
public static makeFromTransform(parent: DataFlowNode, stackTransform: StackTransform) { | ||
|
||
const {stack, groupby, as, offset='zero'} = stackTransform; | ||
const sort = stackTransform.sort || {'field': stack, 'order': 'ascending'}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious why this default is needed?
Note that Github collapses a few of my comments again ><" -- make sure to look at them :) |
|
src/compile/data/stack.ts
Outdated
|
||
} | ||
|
||
function isAsValidArray(as: string[] | string): as is string[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isValidAsArray
src/compile/data/stack.ts
Outdated
groupby: stackby, | ||
key: dimensionField, | ||
method: 'value', | ||
value: 0 | ||
}); | ||
} | ||
if(facetby) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, you should look at your overall PR diff and think why you do certain things.
Here you add if (facetby)
to wrap the main code for the transform assemble logic (as it creates the stack transform). Having a case that we can skip the main logic is quite questionable.
From your earlier rationale, you say that facetby is undefined
in makeFromTransform
. But why should that be undefined? (That's actually something you set yourself.)
FYI, previously facetby
was required but somehow you make it optional in this PR. (Why?)
I actually mentioned earlier that the facetby should be an empty array []
in either case.
Thus, facetby shouldn't be optional and then you don't have to add the if here.
Just FYI, it's okay to forget some details I mentioned because this is quite a complicated code base. But feel free to ask again if you are unsure about something. :)
A good case to test whether this logic is now correct for facetby is to try to see if you can modify
https://github.com/vega/vega-lite/blob/master/examples/specs/normalized/trellis_stacked_bar_normalized.vl.json
such that you have the stack transform inside the inner spec
.
{
"$schema": "https://vega.github.io/schema/vega-lite/v2.json",
"data": {
"url": "data/barley.json"
},
"facet": {
"column": {
"field": "year",
"type": "ordinal"
}
},
"spec": {
"transform": [{aggregate: ...}, {stack: ...}],
"mark": "bar",
"encoding": {
...
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/compile/data/stack.ts
Outdated
let normalizedAs: Array<string>; | ||
if (isAsValidArray(as)) { | ||
normalizedAs = as; | ||
} else if(typeof as === 'string') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if(isString(as))
@@ -152,15 +207,14 @@ export class StackNode extends DataFlowNode { | |||
} | |||
return [vgField(dimensionFieldDef)]; | |||
} | |||
return []; | |||
return groupby || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with this for now.
Once you decouple Impute from stacking (#1514), we should make dimensionFieldDef
no longer a part of StackComponent and just have StackNode.makeFromEncoding()
create a groupby
, using this method. At that point, we can basically remove facetBy
and just have push new dimension in the facet to groupby
instead.
(Basically, if you look at the code, the final groupby in the stack transform = groupBy that's from dimension fieldDef + facetBy)
Since the code looks pretty stable at this point, I'll merge to a |
* Add Stack Transform (#3771) * Add Stack Transform Examples (#3798) * Add Stacked population, Diverging Bar Chart example, Marimekko Chart of cars.json * Add 3 Mosaic plots of cars.json * Add compiled vega specs and svgs of the added examples * Transpose axes of rect_mosaic_simple to maintain consistency
Fix #3311
To be done:
DocumentationStackTransform
to hide it from the schema for now (and re-compile schema) so we can merge this after you address the unaddressed comments._future
suffix to the example files so they can be included even though stack transform wouldn't be in the schema yet.