From cdd57731e4ee5003578aeaf6902e9201e402f3b8 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Fri, 18 May 2018 18:21:52 +0530 Subject: [PATCH 01/19] Changed StackComponent to hold both Stack operations --- src/compile/data/stack.ts | 42 +++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index 001ca95605..cfe1ff4771 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -21,35 +21,55 @@ 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 /** - * Faceted field. + * Faceted field. Used in makeFromEncoding. */ - facetby: string[]; + facetby?: string[]; - dimensionFieldDef: FieldDef; + dimensionFieldDef?: FieldDef; /** - * Stack measure's field + * Stack measure's field. Used in makeFromEncoding. */ - field: string; + field?: string; /** * Level of detail fields for each level in the stacked charts such as color or detail. + * Used in makeFromEncoding. */ - stackby: string[]; + stackby?: string[]; /** * Field that determines order of levels in the stacked charts. + * Used in both but optional in transform. */ - sort: VgSort; + sort?: VgSort; - /** Mode for stacking marks. */ - offset: StackOffset; + /** Mode for stacking marks. Used in both but optional in transform + * TODO write the values and default + */ + offset?: StackOffset; /** - * Whether to impute the data before stacking. + * Whether to impute the data before stacking. Used only in makeFromEncoding. */ - impute: boolean; + impute?: boolean; + + /** + * Stack measure's field. Used in makeFromTransform + */ + stack?: string; + /** + * The data fields to group by. + */ + groupby?: string[]; + /** + * Output field names of each stack field. TODO write about the defaults + */ + as?: string | string[]; + } export class StackNode extends DataFlowNode { From 38ea65cdad2e65014b8b6f7130e33336ad6c89af Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Fri, 18 May 2018 18:52:26 +0530 Subject: [PATCH 02/19] Add StackTransform interface --- src/transform.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/transform.ts b/src/transform.ts index a33c3da617..40676f695b 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -102,6 +102,32 @@ export interface AggregatedFieldDef { as: string; } +export interface StackTransform { + /** + * The field which is stacked. + */ + stack: string; + /** + * The data fields to group by. + */ + groupby: string[]; + /** + * Mode for stacking marks. Default is 'zero' + */ + offset?: 'zero' | 'center' | 'normalize'; + /** + * Field that determines the order of leves in the stacked charts. + */ + sort?: SortField ; + /** + * Output field names. + * y2 or x2 is optional if only one name is provided the end will be “_end” + * Otherwise as will be ["StackField_start","StackField_end"] + */ + as?: string | string[]; + +} + export type WindowOnlyOp = 'row_number' | From 091d30960ff68a1fb331117b7427536477510caf Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Fri, 18 May 2018 22:16:48 +0530 Subject: [PATCH 03/19] Add StackTransform --- build/vega-lite-schema.json | 51 +++++++++++++++++ src/compile/data/parse.ts | 4 +- src/compile/data/stack.ts | 111 +++++++++++++++++++++++------------- src/transform.ts | 6 +- 4 files changed, 130 insertions(+), 42 deletions(-) diff --git a/build/vega-lite-schema.json b/build/vega-lite-schema.json index ad30adf2c9..1d1d8ff035 100644 --- a/build/vega-lite-schema.json +++ b/build/vega-lite-schema.json @@ -6140,6 +6140,54 @@ ], "type": "string" }, + "StackTransform": { + "additionalProperties": false, + "properties": { + "as": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Output field names.\ny2 or x2 is optional if only one name is provided the end will be “_end”\nOtherwise as will be [\"StackField_start\",\"StackField_end\"]" + }, + "groupby": { + "description": "The data fields to group by.", + "items": { + "type": "string" + }, + "type": "array" + }, + "offset": { + "description": "Mode for stacking marks. Default is 'zero'", + "enum": [ + "zero", + "center", + "normalize" + ], + "type": "string" + }, + "sort": { + "$ref": "#/definitions/SortField", + "description": "Field that determines the order of leves in the stacked charts." + }, + "stack": { + "description": "The field which is stacked.", + "type": "string" + } + }, + "required": [ + "stack", + "groupby" + ], + "type": "object" + }, "StyleConfigIndex": { "additionalProperties": { "$ref": "#/definitions/VgMarkConfig" @@ -7302,6 +7350,9 @@ }, { "$ref": "#/definitions/WindowTransform" + }, + { + "$ref": "#/definitions/StackTransform" } ] }, diff --git a/src/compile/data/parse.ts b/src/compile/data/parse.ts index c730992a86..57a3354f02 100644 --- a/src/compile/data/parse.ts +++ b/src/compile/data/parse.ts @@ -1,6 +1,6 @@ import {MAIN, RAW} from '../../data'; import * as log from '../../log'; -import {isAggregate, isBin, isCalculate, isFilter, isLookup, isTimeUnit, isWindow} from '../../transform'; +import {isAggregate, isBin, isCalculate, isFilter, isLookup, isStack, isTimeUnit, isWindow} from '../../transform'; import {Dict, keys} from '../../util'; import {isFacetModel, isLayerModel, isUnitModel, Model} from '../model'; import {requiresSelectionId} from '../selection/selection'; @@ -86,6 +86,8 @@ export function parseTransformArray(head: DataFlowNode, model: Model, ancestorPa for (const field of keys(window.producedFields())) { ancestorParse.set(field, 'derived', false); } + } else if (isStack(t)) { + const stack= head = StackNode.makeFromTransform(head, t); } else { log.warn(log.message.invalidTransformIgnored(t)); return; diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index cfe1ff4771..1fb8b0aecc 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -1,13 +1,14 @@ -import {isArray} from 'vega-util'; +import {isArray, isString} from 'vega-util'; import {FieldDef, isFieldDef, vgField} from '../../fielddef'; +import {SortField} from '../../sort'; import {StackOffset} from '../../stack'; +import {StackTransform} from '../../transform'; import {duplicate} from '../../util'; import {VgSort, VgTransform} from '../../vega.schema'; import {sortParams} from '../common'; import {UnitModel} from './../unit'; import {DataFlowNode} from './dataflow'; - function getStackByFields(model: UnitModel): string[] { return model.stack.stackBy.reduce((fields, by) => { const fieldDef = by.fieldDef; @@ -45,12 +46,12 @@ export interface StackComponent { * Field that determines order of levels in the stacked charts. * Used in both but optional in transform. */ - sort?: VgSort; + sort: VgSort | SortField; /** Mode for stacking marks. Used in both but optional in transform * TODO write the values and default */ - offset?: StackOffset; + offset: StackOffset; /** * Whether to impute the data before stacking. Used only in makeFromEncoding. @@ -72,6 +73,10 @@ export interface StackComponent { } +function isAsValidArray(as: string[] | string): as is string[] { + return isArray(as) && as.every(s => isString(s)) && as.length ===2; +} + export class StackNode extends DataFlowNode { private _stack: StackComponent; @@ -85,6 +90,20 @@ export class StackNode extends DataFlowNode { this._stack = stack; } + public static makeFromTransform(parent: DataFlowNode, model: StackTransform) { + const offset = model.offset || 'zero'; + const sort = model.sort || {'field': model.stack, 'order': 'ascending'}; // refactor possible? + + + + return new StackNode (parent, { + stack: model.stack, + groupby: model.groupby, + offset, + sort, + as: model.as + }); + } public static make(parent: DataFlowNode, model: UnitModel) { const stackProperties = model.stack; @@ -177,50 +196,62 @@ export class StackNode extends DataFlowNode { public assemble(): VgTransform[] { const transform: VgTransform[] = []; + if (this._stack.stackby) { + const {facetby, field: stackField, dimensionFieldDef, impute, offset, sort, stackby} = this._stack; + + // Impute + if (impute && dimensionFieldDef) { + const dimensionField = dimensionFieldDef ? vgField(dimensionFieldDef, {binSuffix: 'mid'}): undefined; + + if (dimensionFieldDef.bin) { + // As we can only impute one field at a time, we need to calculate + // mid point for a binned field + transform.push({ + type: 'formula', + expr: '(' + + vgField(dimensionFieldDef, {expr: 'datum'}) + + '+' + + vgField(dimensionFieldDef, {expr: 'datum', binSuffix: 'end'}) + + ')/2', + as: dimensionField + }); + } - const {facetby, field: stackField, dimensionFieldDef, impute, offset, sort, stackby} = this._stack; - - // Impute - if (impute && dimensionFieldDef) { - const dimensionField = dimensionFieldDef ? vgField(dimensionFieldDef, {binSuffix: 'mid'}): undefined; - - if (dimensionFieldDef.bin) { - // As we can only impute one field at a time, we need to calculate - // mid point for a binned field transform.push({ - type: 'formula', - expr: '(' + - vgField(dimensionFieldDef, {expr: 'datum'}) + - '+' + - vgField(dimensionFieldDef, {expr: 'datum', binSuffix: 'end'}) + - ')/2', - as: dimensionField + type: 'impute', + field: stackField, + groupby: stackby, + key: dimensionField, + method: 'value', + value: 0 }); } + // Stack transform.push({ - type: 'impute', + type: 'stack', + groupby: this.getGroupbyFields().concat(facetby), field: stackField, - groupby: stackby, - key: dimensionField, - method: 'value', - value: 0 + sort, + as: [ + stackField + '_start', + stackField + '_end' + ], + offset }); - } - - // Stack - transform.push({ - type: 'stack', - groupby: this.getGroupbyFields().concat(facetby), - field: stackField, - sort, - as: [ - stackField + '_start', - stackField + '_end' - ], - offset - }); - return transform; + return transform; + } else { + const {stack, groupby, offset, sort, as} = this._stack; + let normalizedAs: Array; + if (isAsValidArray(as)) { + normalizedAs = as; + } else if(typeof as === 'string') { + normalizedAs = [as, as + '_end']; + } else { + normalizedAs = [this._stack + '_start', this._stack + '_end']; + } + return [{type: 'stack',groupby,field: stack, sort, as: normalizedAs, offset}]; + } } } diff --git a/src/transform.ts b/src/transform.ts index 40676f695b..162fff1cba 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -265,7 +265,11 @@ export function isAggregate(t: Transform): t is AggregateTransform { return t['aggregate'] !== undefined; } -export type Transform = FilterTransform | CalculateTransform | LookupTransform | BinTransform | TimeUnitTransform | AggregateTransform | WindowTransform; +export function isStack(t: Transform): t is StackTransform { + return t['stack'] !== undefined; +} + +export type Transform = FilterTransform | CalculateTransform | LookupTransform | BinTransform | TimeUnitTransform | AggregateTransform | WindowTransform | StackTransform; export function normalizeTransform(transform: Transform[]) { return transform.map(t => { From 6171fe194c7b4fb2a841a70276299d001f673a84 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Fri, 18 May 2018 23:58:21 +0530 Subject: [PATCH 04/19] Refactor to generate as property during make. --- src/compile/data/stack.ts | 49 ++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index 1fb8b0aecc..0842319f7f 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -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 /** * Faceted field. Used in makeFromEncoding. */ @@ -67,9 +67,9 @@ export interface StackComponent { */ groupby?: string[]; /** - * Output field names of each stack field. TODO write about the defaults + * Output field names of each stack field. */ - as?: string | string[]; + as: string[]; } @@ -92,8 +92,16 @@ export class StackNode extends DataFlowNode { public static makeFromTransform(parent: DataFlowNode, model: StackTransform) { const offset = model.offset || 'zero'; - const sort = model.sort || {'field': model.stack, 'order': 'ascending'}; // refactor possible? - + const sort = model.sort || {'field': model.stack, 'order': 'ascending'}; + const as = model.as; + let normalizedAs: Array | undefined; + if (isAsValidArray(as)) { + normalizedAs = as; + } else if(typeof as === 'string') { + normalizedAs = [as, as + '_end']; + } else { + normalizedAs = [model.stack + '_start', model.stack + '_end']; + } return new StackNode (parent, { @@ -101,8 +109,9 @@ export class StackNode extends DataFlowNode { groupby: model.groupby, offset, sort, - as: model.as + as: normalizedAs }); + } public static make(parent: DataFlowNode, model: UnitModel) { @@ -132,7 +141,13 @@ export class StackNode extends DataFlowNode { return s; }, {field:[], order: []}); } - + // Refactored to add as in the make phase so that we can get producedFields + // from the as property + const field = model.vgField(stackProperties.fieldChannel); + const as = [ + field + '_start', + field + '_end' + ]; return new StackNode(parent, { dimensionFieldDef, field: model.vgField(stackProperties.fieldChannel), @@ -141,6 +156,7 @@ export class StackNode extends DataFlowNode { sort, offset: stackProperties.offset, impute: stackProperties.impute, + as }); } @@ -166,11 +182,10 @@ export class StackNode extends DataFlowNode { } public producedFields() { - const out = {}; - - out[this._stack.field + '_start'] = true; - out[this._stack.field + '_end'] = true; - + const out = this._stack.as.reduce((result, item) => { + result[item] = true; + return result; + }, {}); return out; } @@ -243,15 +258,7 @@ export class StackNode extends DataFlowNode { return transform; } else { const {stack, groupby, offset, sort, as} = this._stack; - let normalizedAs: Array; - if (isAsValidArray(as)) { - normalizedAs = as; - } else if(typeof as === 'string') { - normalizedAs = [as, as + '_end']; - } else { - normalizedAs = [this._stack + '_start', this._stack + '_end']; - } - return [{type: 'stack',groupby,field: stack, sort, as: normalizedAs, offset}]; + return [{type: 'stack', groupby,field: stack, sort, as, offset}]; } } } From d1de3e8e73e6403360a13693912b4d719bfbca5c Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Fri, 18 May 2018 23:59:02 +0530 Subject: [PATCH 05/19] Added call to ancestorParse --- src/compile/data/parse.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compile/data/parse.ts b/src/compile/data/parse.ts index 57a3354f02..5f76471e48 100644 --- a/src/compile/data/parse.ts +++ b/src/compile/data/parse.ts @@ -87,7 +87,11 @@ export function parseTransformArray(head: DataFlowNode, model: Model, ancestorPa ancestorParse.set(field, 'derived', false); } } else if (isStack(t)) { - const stack= head = StackNode.makeFromTransform(head, t); + const stack = head = StackNode.makeFromTransform(head, t); + + for (const field of keys(stack.producedFields())) { + ancestorParse.set(field, 'derived', false); + } } else { log.warn(log.message.invalidTransformIgnored(t)); return; From d60db8e4976150394b563ec5c71997b2e764282e Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Sat, 19 May 2018 00:01:32 +0530 Subject: [PATCH 06/19] Add tests for StackTransform --- src/compile/data/stack.ts | 8 +- test/compile/data/stack.test.ts | 368 +++++++++++++++++++------------- 2 files changed, 228 insertions(+), 148 deletions(-) diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index 0842319f7f..6aa9854538 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -46,7 +46,7 @@ export interface StackComponent { * Field that determines order of levels in the stacked charts. * Used in both but optional in transform. */ - sort: VgSort | SortField; + sort: VgSort | SortField; // TODO Simplify the sort. make uses VgSort. /** Mode for stacking marks. Used in both but optional in transform * TODO write the values and default @@ -72,7 +72,7 @@ export interface StackComponent { as: string[]; } - +// 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; } @@ -211,6 +211,9 @@ export class StackNode extends DataFlowNode { public assemble(): VgTransform[] { const transform: VgTransform[] = []; + + // Detects whether assemble call is from Node created from make or MakeFromTransform + // TODO Create a stricter function to check that. if (this._stack.stackby) { const {facetby, field: stackField, dimensionFieldDef, impute, offset, sort, stackby} = this._stack; @@ -258,6 +261,7 @@ export class StackNode extends DataFlowNode { return transform; } else { const {stack, groupby, offset, sort, as} = this._stack; + // Wrapping in array. Alternative is to change assemble.ts to handle single object. return [{type: 'stack', groupby,field: stack, sort, as, offset}]; } } diff --git a/test/compile/data/stack.test.ts b/test/compile/data/stack.test.ts index aee5fe1314..a2ff22005d 100644 --- a/test/compile/data/stack.test.ts +++ b/test/compile/data/stack.test.ts @@ -5,6 +5,7 @@ import {assert} from 'chai'; import {StackComponent, StackNode} from '../../../src/compile/data/stack'; import {UnitModel} from '../../../src/compile/unit'; +import {Transform} from '../../../src/transform'; import {VgTransform} from '../../../src/vega.schema'; import {parseUnitModelWithScale} from '../../util'; @@ -15,79 +16,83 @@ function parse(model: UnitModel) { function assemble(model: UnitModel) { return StackNode.make(null, model).assemble(); } +describe ('compile/data/stack', () => { -describe('compile/data/stack', () => { - it('should produce correct stack component for bar with color', () => { - const model = parseUnitModelWithScale({ - "mark": "bar", - "encoding": { - "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, - "y": {"field": "b", "type": "nominal"}, - "color": {"field": "c", "type": "ordinal",} - } - }); + describe('StackNode.make', () => { + it('should produce correct stack component for bar with color', () => { + const model = parseUnitModelWithScale({ + "mark": "bar", + "encoding": { + "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, + "y": {"field": "b", "type": "nominal"}, + "color": {"field": "c", "type": "ordinal",} + } + }); - assert.deepEqual(parse(model), { - dimensionFieldDef: {field: 'b', type: 'nominal'}, - facetby: [], - field: 'sum_a', - stackby: ['c'], - sort: { - field: ['c'], - order: ['descending'] - }, - offset: 'zero', - impute: false + assert.deepEqual(parse(model), { + dimensionFieldDef: {field: 'b', type: 'nominal'}, + facetby: [], + field: 'sum_a', + stackby: ['c'], + sort: { + field: ['c'], + order: ['descending'] + }, + offset: 'zero', + impute: false, + as: ['sum_a_start', 'sum_a_end'] + }); }); - }); - it('should produce correct stack component with both start and end of the binned field for bar with color and binned y', () => { - const model = parseUnitModelWithScale({ - "mark": "bar", - "encoding": { - "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, - "y": {"bin": true, "field": "b", "type": "quantitative"}, - "color": {"field": "c", "type": "ordinal",} - } - }); + it('should produce correct stack component with both start and end of the binned field for bar with color and binned y', () => { + const model = parseUnitModelWithScale({ + "mark": "bar", + "encoding": { + "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, + "y": {"bin": true, "field": "b", "type": "quantitative"}, + "color": {"field": "c", "type": "ordinal",} + } + }); - assert.deepEqual(parse(model), { - dimensionFieldDef: {"bin": {maxbins: 10}, "field": "b", "type": "quantitative"}, - facetby: [], - field: 'sum_a', - stackby: ['c'], - sort: { - field: ['c'], - order: ['descending'] - }, - offset: 'zero', - impute: false + assert.deepEqual(parse(model), { + dimensionFieldDef: {"bin": {maxbins: 10}, "field": "b", "type": "quantitative"}, + facetby: [], + field: 'sum_a', + stackby: ['c'], + sort: { + field: ['c'], + order: ['descending'] + }, + offset: 'zero', + impute: false, + as: ['sum_a_start', 'sum_a_end'] + }); }); - }); - it('should produce correct stack component for 1D bar with color', () => { - const model = parseUnitModelWithScale({ - "mark": "bar", - "encoding": { - "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, - "color": {"field": "c", "type": "ordinal",} - } - }); + it('should produce correct stack component for 1D bar with color', () => { + const model = parseUnitModelWithScale({ + "mark": "bar", + "encoding": { + "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, + "color": {"field": "c", "type": "ordinal",} + } + }); - assert.deepEqual(parse(model), { - dimensionFieldDef: undefined, - facetby: [], - field: 'sum_a', - stackby: ['c'], - sort: { - field: ['c'], - order: ['descending'] - }, - offset: 'zero', - impute: false - }); + assert.deepEqual(parse(model), { + dimensionFieldDef: undefined, + facetby: [], + field: 'sum_a', + stackby: ['c'], + sort: { + field: ['c'], + order: ['descending'] + }, + offset: 'zero', + impute: false, + as: ['sum_a_start', 'sum_a_end'] + }); - assert.deepEqual(assemble(model), [{ + assert.deepEqual(assemble(model), [{ type: 'stack', groupby: [], field: 'sum_a', @@ -98,104 +103,175 @@ describe('compile/data/stack', () => { as: ['sum_a_start', 'sum_a_end'], offset: 'zero' } - ]); - }); - - it('should produce correct stack component for area with color and order', function() { - const model = parseUnitModelWithScale({ - "mark": "area", - "encoding": { - "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, - "y": {"field": "b", "type": "nominal"}, - "color": {"field": "c", "type": "nominal"}, - "order": {"aggregate": "mean", "field": "d", "type": "quantitative"} - } + ]); }); - assert.deepEqual(parse(model), { - dimensionFieldDef: {field: 'b', type: 'nominal'}, - facetby: [], - field: 'sum_a', - stackby: ['c'], - sort: { - field: ['mean_d'], - order: ['ascending'] - }, - offset: 'zero', - impute: true - }); + it('should produce correct stack component for area with color and order', function() { + const model = parseUnitModelWithScale({ + "mark": "area", + "encoding": { + "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, + "y": {"field": "b", "type": "nominal"}, + "color": {"field": "c", "type": "nominal"}, + "order": {"aggregate": "mean", "field": "d", "type": "quantitative"} + } + }); - assert.deepEqual(assemble(model), [ - { - type: 'impute', - field: 'sum_a', - groupby: ['c'], - key: 'b', - method: "value", - value: 0 - }, - { - type: 'stack', - groupby: ['b'], + assert.deepEqual(parse(model), { + dimensionFieldDef: {field: 'b', type: 'nominal'}, + facetby: [], field: 'sum_a', + stackby: ['c'], sort: { field: ['mean_d'], order: ['ascending'] }, - as: ['sum_a_start', 'sum_a_end'], - offset: 'zero' - } - ]); - }); + offset: 'zero', + impute: true, + as: ['sum_a_start', 'sum_a_end'] + }); - it('should produce correct stack component for area with color and binned dimension', function() { - const model = parseUnitModelWithScale({ - "mark": "area", - "encoding": { - "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, - "y": {"bin": true, "field": "b", "type": "quantitative"}, - "color": {"field": "c", "type": "nominal"} - } + assert.deepEqual(assemble(model), [ + { + type: 'impute', + field: 'sum_a', + groupby: ['c'], + key: 'b', + method: "value", + value: 0 + }, + { + type: 'stack', + groupby: ['b'], + field: 'sum_a', + sort: { + field: ['mean_d'], + order: ['ascending'] + }, + as: ['sum_a_start', 'sum_a_end'], + offset: 'zero' + } + ]); }); - assert.deepEqual(parse(model), { - dimensionFieldDef: {"bin": {maxbins: 10}, "field": "b", "type": "quantitative"}, - facetby: [], - field: 'sum_a', - stackby: ['c'], - sort: { - field: ['c'], - order: ['descending'] - }, - offset: 'zero', - impute: true - }); + it('should produce correct stack component for area with color and binned dimension', function() { + const model = parseUnitModelWithScale({ + "mark": "area", + "encoding": { + "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, + "y": {"bin": true, "field": "b", "type": "quantitative"}, + "color": {"field": "c", "type": "nominal"} + } + }); - assert.deepEqual(assemble(model), [ - { - type: 'formula', - expr: '(datum[\"bin_maxbins_10_b\"]+datum[\"bin_maxbins_10_b_end\"])/2', - as: 'bin_maxbins_10_b_mid' - }, - { - type: 'impute', - field: 'sum_a', - groupby: ['c'], - key: 'bin_maxbins_10_b_mid', - method: "value", - value: 0 - }, - { - type: 'stack', - groupby: ['bin_maxbins_10_b_mid'], + assert.deepEqual(parse(model), { + dimensionFieldDef: {"bin": {maxbins: 10}, "field": "b", "type": "quantitative"}, + facetby: [], field: 'sum_a', + stackby: ['c'], sort: { field: ['c'], order: ['descending'] }, - as: ['sum_a_start', 'sum_a_end'], - offset: 'zero' - } - ]); + offset: 'zero', + impute: true, + as: ['sum_a_start', 'sum_a_end'] + }); + + assert.deepEqual(assemble(model), [ + { + type: 'formula', + expr: '(datum[\"bin_maxbins_10_b\"]+datum[\"bin_maxbins_10_b_end\"])/2', + as: 'bin_maxbins_10_b_mid' + }, + { + type: 'impute', + field: 'sum_a', + groupby: ['c'], + key: 'bin_maxbins_10_b_mid', + method: "value", + value: 0 + }, + { + type: 'stack', + groupby: ['bin_maxbins_10_b_mid'], + field: 'sum_a', + sort: { + field: ['c'], + order: ['descending'] + }, + as: ['sum_a_start', 'sum_a_end'], + offset: 'zero' + } + ]); + }); + }); + + describe('StackNode.makeFromTransform', () => { + it('should fill in "as" field, offset and sort properly', () => { + const transform: Transform = { + stack : 'people', + groupby: ['age'] + }; + const stack = StackNode.makeFromTransform(null, transform); + assert.deepEqual(stack.assemble(), [{ + type: 'stack', + groupby: ['age'], + field: 'people', + sort: {field: 'people', order: 'ascending'}, + offset: 'zero', + as: ["people_start", "people_end"] + }]); + }); + + it('should fill in partial "as" field properly', () => { + const transform: Transform = { + stack : 'people', + groupby: ['age', 'gender'], + offset: 'normalize', + as: "val" + }; + const stack = StackNode.makeFromTransform(null, transform); + assert.deepEqual(stack.assemble(), [{ + type: 'stack', + groupby: ['age', 'gender'], + field: 'people', + sort: {field: 'people', order: 'ascending'}, + offset: 'normalize', + 'as': ["val", "val_end"] + }]); + + }); + + }); + describe('StackNode.producedFields', () => { + it('should give producedfields correctly', () => { + const transform: Transform = { + stack: 'people', + groupby: ['age'] + }; + const stack = StackNode.makeFromTransform(null, transform); + assert.deepEqual(stack.producedFields(), { + people_start: true, + people_end: true + }); + + }); + + it('should give producedFields correctly when in encoding channel', () => { + const model = parseUnitModelWithScale({ + "mark": "bar", + "encoding": { + "x": {"aggregate": "sum", "field": "a", "type": "quantitative"}, + "y": {"field": "b", "type": "nominal"}, + "color": {"field": "c", "type": "ordinal",} + } + }); + const stack = StackNode.make(null, model); + assert.deepEqual(stack.producedFields(), { + sum_a_start: true, + sum_a_end: true + }); + }); }); }); From bde6bdb06f37485579f3718b1ab99319de64fca9 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Sat, 19 May 2018 13:35:51 +0530 Subject: [PATCH 07/19] Rename model-> stackTransform, field & stack -> stackField, make -> makFromEncoding --- src/compile/data/stack.ts | 103 +++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 57 deletions(-) diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index 6aa9854538..c0ae69fc5f 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -34,7 +34,7 @@ export interface StackComponent { /** * Stack measure's field. Used in makeFromEncoding. */ - field?: string; + stackField: string; /** * Level of detail fields for each level in the stacked charts such as color or detail. @@ -58,10 +58,6 @@ export interface StackComponent { */ impute?: boolean; - /** - * Stack measure's field. Used in makeFromTransform - */ - stack?: string; /** * The data fields to group by. */ @@ -90,30 +86,30 @@ export class StackNode extends DataFlowNode { this._stack = stack; } - public static makeFromTransform(parent: DataFlowNode, model: StackTransform) { - const offset = model.offset || 'zero'; - const sort = model.sort || {'field': model.stack, 'order': 'ascending'}; - const as = model.as; - let normalizedAs: Array | undefined; + public static makeFromTransform(parent: DataFlowNode, stackTransform: StackTransform) { + + const {stack, groupby, as, offset='zero'} = stackTransform; + const sort = stackTransform.sort || {'field': stack, 'order': 'ascending'}; + + let normalizedAs: Array; if (isAsValidArray(as)) { normalizedAs = as; } else if(typeof as === 'string') { normalizedAs = [as, as + '_end']; } else { - normalizedAs = [model.stack + '_start', model.stack + '_end']; + normalizedAs = [stackTransform.stack + '_start', stackTransform.stack + '_end']; } - return new StackNode (parent, { - stack: model.stack, - groupby: model.groupby, + stackField: stackTransform.stack, + groupby, offset, sort, as: normalizedAs }); } - public static make(parent: DataFlowNode, model: UnitModel) { + public static makeFromEncoding(parent: DataFlowNode, model: UnitModel) { const stackProperties = model.stack; @@ -144,19 +140,16 @@ export class StackNode extends DataFlowNode { // Refactored to add as in the make phase so that we can get producedFields // from the as property const field = model.vgField(stackProperties.fieldChannel); - const as = [ - field + '_start', - field + '_end' - ]; + return new StackNode(parent, { dimensionFieldDef, - field: model.vgField(stackProperties.fieldChannel), + stackField:field, facetby: [], stackby, sort, offset: stackProperties.offset, impute: stackProperties.impute, - as + as: [field + '_start', field + '_end'] }); } @@ -171,7 +164,7 @@ export class StackNode extends DataFlowNode { public dependentFields() { const out = {}; - out[this._stack.field] = true; + out[this._stack.stackField] = true; this.getGroupbyFields().forEach(f => out[f] = true); this._stack.facetby.forEach(f => out[f] = true); @@ -211,58 +204,54 @@ export class StackNode extends DataFlowNode { public assemble(): VgTransform[] { const transform: VgTransform[] = []; - - // Detects whether assemble call is from Node created from make or MakeFromTransform - // TODO Create a stricter function to check that. - if (this._stack.stackby) { - const {facetby, field: stackField, dimensionFieldDef, impute, offset, sort, stackby} = this._stack; + const {facetby, dimensionFieldDef, stackField: field, stackby, sort, offset, impute, groupby, as} = this._stack; // Impute - if (impute && dimensionFieldDef) { - const dimensionField = dimensionFieldDef ? vgField(dimensionFieldDef, {binSuffix: 'mid'}): undefined; - - if (dimensionFieldDef.bin) { - // As we can only impute one field at a time, we need to calculate - // mid point for a binned field - transform.push({ - type: 'formula', - expr: '(' + - vgField(dimensionFieldDef, {expr: 'datum'}) + - '+' + - vgField(dimensionFieldDef, {expr: 'datum', binSuffix: 'end'}) + - ')/2', - as: dimensionField - }); - } + if (impute && dimensionFieldDef) { + const dimensionField = dimensionFieldDef ? vgField(dimensionFieldDef, {binSuffix: 'mid'}): undefined; + if (dimensionFieldDef.bin) { + // As we can only impute one field at a time, we need to calculate + // mid point for a binned field transform.push({ - type: 'impute', - field: stackField, - groupby: stackby, - key: dimensionField, - method: 'value', - value: 0 + type: 'formula', + expr: '(' + + vgField(dimensionFieldDef, {expr: 'datum'}) + + '+' + + vgField(dimensionFieldDef, {expr: 'datum', binSuffix: 'end'}) + + ')/2', + as: dimensionField }); } + transform.push({ + type: 'impute', + field, + groupby: stackby, + key: dimensionField, + method: 'value', + value: 0 + }); + } + if(facetby) { + // Stack transform.push({ type: 'stack', groupby: this.getGroupbyFields().concat(facetby), - field: stackField, + field, sort, - as: [ - stackField + '_start', - stackField + '_end' - ], + as, offset }); + } + if (transform.length !== 0) { return transform; } else { - const {stack, groupby, offset, sort, as} = this._stack; - // Wrapping in array. Alternative is to change assemble.ts to handle single object. - return [{type: 'stack', groupby,field: stack, sort, as, offset}]; + + return [{type: 'stack', groupby, field, sort, as, offset}]; } + } } From 6bbd2d63c51e7da9dfcaa5bb25925a5c76d1c8e8 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Sat, 19 May 2018 13:38:47 +0530 Subject: [PATCH 08/19] Replace Union(VgSort,SortField) -> VgSort --- src/compile/data/stack.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index c0ae69fc5f..65e6c89a14 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -1,6 +1,5 @@ import {isArray, isString} from 'vega-util'; import {FieldDef, isFieldDef, vgField} from '../../fielddef'; -import {SortField} from '../../sort'; import {StackOffset} from '../../stack'; import {StackTransform} from '../../transform'; import {duplicate} from '../../util'; @@ -22,8 +21,7 @@ function getStackByFields(model: UnitModel): string[] { } export interface StackComponent { - // TODO it would be cleaner to compose the two interfaces to create this one. - // TODO Rename make to makeFromTransform + /** * Faceted field. Used in makeFromEncoding. */ @@ -46,10 +44,9 @@ export interface StackComponent { * Field that determines order of levels in the stacked charts. * Used in both but optional in transform. */ - sort: VgSort | SortField; // TODO Simplify the sort. make uses VgSort. + sort: VgSort; - /** Mode for stacking marks. Used in both but optional in transform - * TODO write the values and default + /** Mode for stacking marks. */ offset: StackOffset; From 4e2c5477f69f25b1a2836ecf3246ca37d63579a8 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Sat, 19 May 2018 13:39:44 +0530 Subject: [PATCH 09/19] Return producedFields without creating local variable --- src/compile/data/stack.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index 65e6c89a14..0cd334374a 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -172,11 +172,10 @@ export class StackNode extends DataFlowNode { } public producedFields() { - const out = this._stack.as.reduce((result, item) => { + return this._stack.as.reduce((result, item) => { result[item] = true; return result; }, {}); - return out; } private getGroupbyFields() { From e4dc989f0576d6e6fc09db58c7a5e08d82a7207d Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Sat, 19 May 2018 13:40:38 +0530 Subject: [PATCH 10/19] Call to StackNode.make -> StackNode.makeFromEncoding --- src/compile/data/parse.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compile/data/parse.ts b/src/compile/data/parse.ts index 5f76471e48..0a80feb442 100644 --- a/src/compile/data/parse.ts +++ b/src/compile/data/parse.ts @@ -225,7 +225,7 @@ export function parseData(model: Model): DataComponent { } } - head = StackNode.make(head, model) || head; + head = StackNode.makeFromEncoding(head, model) || head; } if (isUnitModel(model)) { From 89d411a9f8982decf25808ff0796e2b064094ffd Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Sat, 19 May 2018 13:42:16 +0530 Subject: [PATCH 11/19] Make "as" compulsory field in StackTransform, Docstring Changes in StackTransform. --- src/transform.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/transform.ts b/src/transform.ts index 162fff1cba..1d9523465a 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -112,19 +112,21 @@ export interface StackTransform { */ groupby: string[]; /** - * Mode for stacking marks. Default is 'zero' + * Mode for stacking marks. + * __Default value:__ `"zero"` */ offset?: 'zero' | 'center' | 'normalize'; /** - * Field that determines the order of leves in the stacked charts. + * Field that determines the order of leaves in the stacked charts. */ sort?: SortField ; /** - * Output field names. - * y2 or x2 is optional if only one name is provided the end will be “_end” - * Otherwise as will be ["StackField_start","StackField_end"] + * 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". */ - as?: string | string[]; + as: string | string[]; } From 12833549d063a090705e7054104e582e15e914b3 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Sat, 19 May 2018 13:43:35 +0530 Subject: [PATCH 12/19] Change tests to adhere to new type signature of StackComponent --- test/compile/data/stack.test.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/test/compile/data/stack.test.ts b/test/compile/data/stack.test.ts index a2ff22005d..f2dbbcaff1 100644 --- a/test/compile/data/stack.test.ts +++ b/test/compile/data/stack.test.ts @@ -10,15 +10,15 @@ import {VgTransform} from '../../../src/vega.schema'; import {parseUnitModelWithScale} from '../../util'; function parse(model: UnitModel) { - return StackNode.make(null, model).stack; + return StackNode.makeFromEncoding(null, model).stack; } function assemble(model: UnitModel) { - return StackNode.make(null, model).assemble(); + return StackNode.makeFromEncoding(null, model).assemble(); } describe ('compile/data/stack', () => { - describe('StackNode.make', () => { + describe('StackNode.makeFromEncoding', () => { it('should produce correct stack component for bar with color', () => { const model = parseUnitModelWithScale({ "mark": "bar", @@ -32,7 +32,7 @@ describe ('compile/data/stack', () => { assert.deepEqual(parse(model), { dimensionFieldDef: {field: 'b', type: 'nominal'}, facetby: [], - field: 'sum_a', + stackField: 'sum_a', stackby: ['c'], sort: { field: ['c'], @@ -57,7 +57,7 @@ describe ('compile/data/stack', () => { assert.deepEqual(parse(model), { dimensionFieldDef: {"bin": {maxbins: 10}, "field": "b", "type": "quantitative"}, facetby: [], - field: 'sum_a', + stackField: 'sum_a', stackby: ['c'], sort: { field: ['c'], @@ -81,7 +81,7 @@ describe ('compile/data/stack', () => { assert.deepEqual(parse(model), { dimensionFieldDef: undefined, facetby: [], - field: 'sum_a', + stackField: 'sum_a', stackby: ['c'], sort: { field: ['c'], @@ -120,7 +120,7 @@ describe ('compile/data/stack', () => { assert.deepEqual(parse(model), { dimensionFieldDef: {field: 'b', type: 'nominal'}, facetby: [], - field: 'sum_a', + stackField: 'sum_a', stackby: ['c'], sort: { field: ['mean_d'], @@ -167,7 +167,7 @@ describe ('compile/data/stack', () => { assert.deepEqual(parse(model), { dimensionFieldDef: {"bin": {maxbins: 10}, "field": "b", "type": "quantitative"}, facetby: [], - field: 'sum_a', + stackField: 'sum_a', stackby: ['c'], sort: { field: ['c'], @@ -208,10 +208,11 @@ describe ('compile/data/stack', () => { }); describe('StackNode.makeFromTransform', () => { - it('should fill in "as" field, offset and sort properly', () => { + it('should fill in offset and sort properly', () => { const transform: Transform = { stack : 'people', - groupby: ['age'] + groupby: ['age'], + as: ['v1', 'v2'] }; const stack = StackNode.makeFromTransform(null, transform); assert.deepEqual(stack.assemble(), [{ @@ -220,7 +221,7 @@ describe ('compile/data/stack', () => { field: 'people', sort: {field: 'people', order: 'ascending'}, offset: 'zero', - as: ["people_start", "people_end"] + as: ['v1', 'v2'] }]); }); @@ -248,11 +249,13 @@ describe ('compile/data/stack', () => { it('should give producedfields correctly', () => { const transform: Transform = { stack: 'people', - groupby: ['age'] + groupby: ['age'], + as: 'people' + }; const stack = StackNode.makeFromTransform(null, transform); assert.deepEqual(stack.producedFields(), { - people_start: true, + people: true, people_end: true }); @@ -267,7 +270,7 @@ describe ('compile/data/stack', () => { "color": {"field": "c", "type": "ordinal",} } }); - const stack = StackNode.make(null, model); + const stack = StackNode.makeFromEncoding(null, model); assert.deepEqual(stack.producedFields(), { sum_a_start: true, sum_a_end: true From 7fc947ad104c728eeb75cbf7a41a626f167d772a Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Sat, 19 May 2018 13:44:30 +0530 Subject: [PATCH 13/19] Add New Spec --- build/vega-lite-schema.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/build/vega-lite-schema.json b/build/vega-lite-schema.json index 1d1d8ff035..13debef30f 100644 --- a/build/vega-lite-schema.json +++ b/build/vega-lite-schema.json @@ -6155,7 +6155,7 @@ "type": "array" } ], - "description": "Output field names.\ny2 or x2 is optional if only one name is provided the end will be “_end”\nOtherwise as will be [\"StackField_start\",\"StackField_end\"]" + "description": "Output field names. This can be either a string or an array of strings with\ntwo elements denoting the name for the fields for stack start and stack end\nrespectively.\nIf a single string(eg.\"val\") is provided, the end field will be \"val_end\"." }, "groupby": { "description": "The data fields to group by.", @@ -6165,7 +6165,7 @@ "type": "array" }, "offset": { - "description": "Mode for stacking marks. Default is 'zero'", + "description": "Mode for stacking marks.\n__Default value:__ `\"zero\"`", "enum": [ "zero", "center", @@ -6175,7 +6175,7 @@ }, "sort": { "$ref": "#/definitions/SortField", - "description": "Field that determines the order of leves in the stacked charts." + "description": "Field that determines the order of leaves in the stacked charts." }, "stack": { "description": "The field which is stacked.", @@ -6184,7 +6184,8 @@ }, "required": [ "stack", - "groupby" + "groupby", + "as" ], "type": "object" }, From 92a3ebfde32302cc5194a9cd113cf4991bb81b07 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Tue, 22 May 2018 22:45:17 +0530 Subject: [PATCH 14/19] Refactor to make StackTransform.sort of type SortField[] --- src/compile/data/stack.ts | 17 ++++++++++++++--- src/transform.ts | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index 0cd334374a..7b1925e2e5 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -3,7 +3,7 @@ import {FieldDef, isFieldDef, vgField} from '../../fielddef'; import {StackOffset} from '../../stack'; import {StackTransform} from '../../transform'; import {duplicate} from '../../util'; -import {VgSort, VgTransform} from '../../vega.schema'; +import {VgComparatorOrder, VgSort, VgTransform} from '../../vega.schema'; import {sortParams} from '../common'; import {UnitModel} from './../unit'; import {DataFlowNode} from './dataflow'; @@ -86,8 +86,19 @@ export class StackNode extends DataFlowNode { public static makeFromTransform(parent: DataFlowNode, stackTransform: StackTransform) { const {stack, groupby, as, offset='zero'} = stackTransform; - const sort = stackTransform.sort || {'field': stack, 'order': 'ascending'}; + const sortFields: string[] = []; + const sortOrder: VgComparatorOrder[] = []; + if (stackTransform.sort !== undefined) { + for (const sortField of stackTransform.sort) { + sortFields.push(sortField.field); + sortOrder.push(sortField.order === undefined ? 'ascending' : sortField.order as VgComparatorOrder); + } + } + const sort: VgSort = { + field: sortFields, + order: sortOrder, + }; let normalizedAs: Array; if (isAsValidArray(as)) { normalizedAs = as; @@ -98,7 +109,7 @@ export class StackNode extends DataFlowNode { } return new StackNode (parent, { - stackField: stackTransform.stack, + stackField: stack, groupby, offset, sort, diff --git a/src/transform.ts b/src/transform.ts index 1d9523465a..5a4b668835 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -119,7 +119,7 @@ export interface StackTransform { /** * Field that determines the order of leaves in the stacked charts. */ - sort?: SortField ; + sort?: SortField[]; /** * 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 From 65b927ac67a2843563457299e12a075d9fcf7d3e Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Tue, 22 May 2018 22:46:34 +0530 Subject: [PATCH 15/19] Change valid as field check, minor change to docstrings --- src/compile/data/stack.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index 7b1925e2e5..567b5fab3c 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -23,7 +23,7 @@ function getStackByFields(model: UnitModel): string[] { export interface StackComponent { /** - * Faceted field. Used in makeFromEncoding. + * Faceted field. */ facetby?: string[]; @@ -65,9 +65,9 @@ export interface StackComponent { as: string[]; } -// 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; + return isArray(as) && as.every(s => isString(s)) && as.length >1; } export class StackNode extends DataFlowNode { @@ -145,7 +145,7 @@ export class StackNode extends DataFlowNode { return s; }, {field:[], order: []}); } - // Refactored to add as in the make phase so that we can get producedFields + // Refactored to add "as" in the make phase so that we can get producedFields // from the as property const field = model.vgField(stackProperties.fieldChannel); From 6cf9b824b00879e4efb72a74561d8cd5786407f5 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Tue, 22 May 2018 22:48:01 +0530 Subject: [PATCH 16/19] Refactor tests to pass for sort type SortField[] --- test/compile/data/stack.test.ts | 48 +++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/test/compile/data/stack.test.ts b/test/compile/data/stack.test.ts index f2dbbcaff1..3189f938c6 100644 --- a/test/compile/data/stack.test.ts +++ b/test/compile/data/stack.test.ts @@ -3,10 +3,9 @@ import {assert} from 'chai'; import {StackComponent, StackNode} from '../../../src/compile/data/stack'; - import {UnitModel} from '../../../src/compile/unit'; import {Transform} from '../../../src/transform'; -import {VgTransform} from '../../../src/vega.schema'; +import {VgComparatorOrder, VgSort, VgTransform} from '../../../src/vega.schema'; import {parseUnitModelWithScale} from '../../util'; function parse(model: UnitModel) { @@ -219,8 +218,8 @@ describe ('compile/data/stack', () => { type: 'stack', groupby: ['age'], field: 'people', - sort: {field: 'people', order: 'ascending'}, offset: 'zero', + sort: {field: [] as string[], order: [] as VgComparatorOrder[]} as VgSort, as: ['v1', 'v2'] }]); }); @@ -237,11 +236,50 @@ describe ('compile/data/stack', () => { type: 'stack', groupby: ['age', 'gender'], field: 'people', - sort: {field: 'people', order: 'ascending'}, offset: 'normalize', - 'as': ["val", "val_end"] + sort: {field: [] as string[], order: [] as VgComparatorOrder[]} as VgSort, + as: ["val", "val_end"] }]); + }); + it('should handle complete "sort"', () => { + const transform: Transform = { + stack : 'people', + groupby: ['age', 'gender'], + offset: 'normalize', + sort: [{'field': 'height', 'order': 'ascending'}, + {'field': 'weight', 'order': 'descending'}], + as: 'val' + }; + const stack = StackNode.makeFromTransform(null, transform); + assert.deepEqual(stack.assemble(), [{ + type: 'stack', + groupby: ['age', 'gender'], + field: 'people', + offset: 'normalize', + sort: {field: ['height', 'weight'], order: ['ascending', 'descending']}, + as: ["val", "val_end"] + }]); + }); + + it('should handle incomplete "sort" field', () => { + const transform: Transform = { + stack : 'people', + groupby: ['age', 'gender'], + offset: 'normalize', + sort: [{'field': 'height'}], + as: 'val' + }; + const stack = StackNode.makeFromTransform(null, transform); + + assert.deepEqual(stack.assemble(), [{ + type: 'stack', + groupby: ['age', 'gender'], + field: 'people', + offset: 'normalize', + sort: {field: ['height'], order: ['ascending']}, + as: ["val", "val_end"] + }]); }); }); From 8c87a044d37a5f2d2909923a4b81534915aa2562 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Tue, 22 May 2018 22:49:41 +0530 Subject: [PATCH 17/19] New Schema with changes to sort field type --- build/vega-lite-schema.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build/vega-lite-schema.json b/build/vega-lite-schema.json index 13debef30f..0f729202f1 100644 --- a/build/vega-lite-schema.json +++ b/build/vega-lite-schema.json @@ -6174,8 +6174,11 @@ "type": "string" }, "sort": { - "$ref": "#/definitions/SortField", - "description": "Field that determines the order of leaves in the stacked charts." + "description": "Field that determines the order of leaves in the stacked charts.", + "items": { + "$ref": "#/definitions/SortField" + }, + "type": "array" }, "stack": { "description": "The field which is stacked.", From 9fd5c687a354f6d9150e96f72beb93a148f6fa78 Mon Sep 17 00:00:00 2001 From: Souvik Sen Date: Tue, 22 May 2018 23:57:34 +0530 Subject: [PATCH 18/19] Make assemble control flow linear --- src/compile/data/stack.ts | 43 ++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/compile/data/stack.ts b/src/compile/data/stack.ts index 567b5fab3c..5445df76c8 100644 --- a/src/compile/data/stack.ts +++ b/src/compile/data/stack.ts @@ -25,7 +25,7 @@ export interface StackComponent { /** * Faceted field. */ - facetby?: string[]; + facetby: string[]; dimensionFieldDef?: FieldDef; @@ -66,7 +66,7 @@ export interface StackComponent { } -function isAsValidArray(as: string[] | string): as is string[] { +function isValidAsArray(as: string[] | string): as is string[] { return isArray(as) && as.every(s => isString(s)) && as.length >1; } @@ -100,9 +100,9 @@ export class StackNode extends DataFlowNode { order: sortOrder, }; let normalizedAs: Array; - if (isAsValidArray(as)) { + if (isValidAsArray(as)) { normalizedAs = as; - } else if(typeof as === 'string') { + } else if(isString(as)) { normalizedAs = [as, as + '_end']; } else { normalizedAs = [stackTransform.stack + '_start', stackTransform.stack + '_end']; @@ -113,6 +113,7 @@ export class StackNode extends DataFlowNode { groupby, offset, sort, + facetby: [], as: normalizedAs }); @@ -190,7 +191,7 @@ export class StackNode extends DataFlowNode { } private getGroupbyFields() { - const {dimensionFieldDef, impute} = this._stack; + const {dimensionFieldDef, impute, groupby} = this._stack; if (dimensionFieldDef) { if (dimensionFieldDef.bin) { if (impute) { @@ -206,12 +207,12 @@ export class StackNode extends DataFlowNode { } return [vgField(dimensionFieldDef)]; } - return []; + return groupby || []; } public assemble(): VgTransform[] { const transform: VgTransform[] = []; - const {facetby, dimensionFieldDef, stackField: field, stackby, sort, offset, impute, groupby, as} = this._stack; + const {facetby, dimensionFieldDef, stackField: field, stackby, sort, offset, impute, as} = this._stack; // Impute if (impute && dimensionFieldDef) { @@ -240,25 +241,17 @@ export class StackNode extends DataFlowNode { value: 0 }); } - if(facetby) { - - // Stack - transform.push({ - type: 'stack', - groupby: this.getGroupbyFields().concat(facetby), - field, - sort, - as, - offset - }); - } - if (transform.length !== 0) { - return transform; - } else { - - return [{type: 'stack', groupby, field, sort, as, offset}]; - } + // Stack + transform.push({ + type: 'stack', + groupby: this.getGroupbyFields().concat(facetby), + field, + sort, + as, + offset + }); + return transform; } } From fe39dfabac81cc1e954c583e5bb0997a61f42d24 Mon Sep 17 00:00:00 2001 From: Kanit Wongsuphasawat Date: Tue, 22 May 2018 13:56:26 -0700 Subject: [PATCH 19/19] Hide stack transform from the schema --- build/vega-lite-schema.json | 55 ------------------------------------- src/transform.ts | 4 +++ 2 files changed, 4 insertions(+), 55 deletions(-) diff --git a/build/vega-lite-schema.json b/build/vega-lite-schema.json index 0f729202f1..ad30adf2c9 100644 --- a/build/vega-lite-schema.json +++ b/build/vega-lite-schema.json @@ -6140,58 +6140,6 @@ ], "type": "string" }, - "StackTransform": { - "additionalProperties": false, - "properties": { - "as": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } - ], - "description": "Output field names. This can be either a string or an array of strings with\ntwo elements denoting the name for the fields for stack start and stack end\nrespectively.\nIf a single string(eg.\"val\") is provided, the end field will be \"val_end\"." - }, - "groupby": { - "description": "The data fields to group by.", - "items": { - "type": "string" - }, - "type": "array" - }, - "offset": { - "description": "Mode for stacking marks.\n__Default value:__ `\"zero\"`", - "enum": [ - "zero", - "center", - "normalize" - ], - "type": "string" - }, - "sort": { - "description": "Field that determines the order of leaves in the stacked charts.", - "items": { - "$ref": "#/definitions/SortField" - }, - "type": "array" - }, - "stack": { - "description": "The field which is stacked.", - "type": "string" - } - }, - "required": [ - "stack", - "groupby", - "as" - ], - "type": "object" - }, "StyleConfigIndex": { "additionalProperties": { "$ref": "#/definitions/VgMarkConfig" @@ -7354,9 +7302,6 @@ }, { "$ref": "#/definitions/WindowTransform" - }, - { - "$ref": "#/definitions/StackTransform" } ] }, diff --git a/src/transform.ts b/src/transform.ts index 5a4b668835..4fd3c12296 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -102,6 +102,10 @@ export interface AggregatedFieldDef { as: string; } + +/** + * @hide + */ export interface StackTransform { /** * The field which is stacked.