Skip to content

Commit

Permalink
Correctly generate as for nested fields. Note that in Vega as can…
Browse files Browse the repository at this point in the history
…not be nested so you have to provide a field name, not a field accessor. Fixes #3744.
  • Loading branch information
domoritz committed Jul 15, 2018
1 parent 3567c71 commit 52d132d
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 40 deletions.
36 changes: 36 additions & 0 deletions examples/specs/test_aggregate_nested.vl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{

"$schema": "https://vega.github.io/schema/vega-lite/v2.json",
"mark": "bar",
"encoding": {
"y": { "field": "properties.variety", "type": "nominal"},
"x": {
"aggregate": "sum",
"field": "properties.yield",
"type": "quantitative"
},
"color": {"title": "site", "field": "properties.site", "type": "nominal"}
},
"data": {
"values": {
"features": [
{
"properties": {
"variety": "Manchuria",
"yield": 27,
"site": "University Farm"
}
},
{
"properties": {
"variety": "Wisconsin No. 38",
"yield": 29.33333,
"site": "Duluth"
}
}
],
"type": "FeatureCollection"
},
"format": {"type": "json", "property": "features"}
}
}
18 changes: 9 additions & 9 deletions src/compile/data/aggregate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {AggregateOp} from 'vega';
import {isBinning} from '../../bin';
import {Channel, isScaleChannel} from '../../channel';
import {FieldDef, vgField} from '../../fielddef';
import {FieldDef, vgField, vgFieldName} from '../../fielddef';
import * as log from '../../log';
import {AggregateTransform} from '../../transform';
import {Dict, differ, duplicate, keys, StringSet} from '../../util';
import {Dict, differ, duplicate, keys, replacePathInField, StringSet} from '../../util';
import {VgAggregateTransform} from '../../vega.schema';
import {binRequiresRange} from '../common';
import {UnitModel} from './../unit';
Expand Down Expand Up @@ -81,15 +81,15 @@ export class AggregateNode extends DataFlowNode {
if (aggregate) {
if (aggregate === 'count') {
meas['*'] = meas['*'] || {};
meas['*']['count'] = vgField(fieldDef);
meas['*']['count'] = vgFieldName(fieldDef);
} else {
meas[field] = meas[field] || {};
meas[field][aggregate] = vgField(fieldDef);
meas[field][aggregate] = vgFieldName(fieldDef);

// For scale channel with domain === 'unaggregated', add min/max so we can use their union as unaggregated domain
if (isScaleChannel(channel) && model.scaleDomain(channel) === 'unaggregated') {
meas[field]['min'] = vgField({field, aggregate: 'min'});
meas[field]['max'] = vgField({field, aggregate: 'max'});
meas[field]['min'] = vgFieldName({field, aggregate: 'min'});
meas[field]['max'] = vgFieldName({field, aggregate: 'max'});
}
}
} else {
Expand All @@ -113,10 +113,10 @@ export class AggregateNode extends DataFlowNode {
if (op) {
if (op === 'count') {
meas['*'] = meas['*'] || {};
meas['*']['count'] = as || vgField(s);
meas['*']['count'] = as || vgFieldName(s);
} else {
meas[field] = meas[field] || {};
meas[field][op] = as || vgField(s);
meas[field][op] = as || vgFieldName(s);
}
}
}
Expand Down Expand Up @@ -175,7 +175,7 @@ export class AggregateNode extends DataFlowNode {
for (const op of keys(this.measures[field])) {
as.push(this.measures[field][op]);
ops.push(op);
fields.push(field);
fields.push(replacePathInField(field));
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/compile/data/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {isString} from 'vega-util';
import {BinParams, binToString, isBinning} from '../../bin';
import {Channel} from '../../channel';
import {Config} from '../../config';
import {FieldDef, normalizeBin, vgField} from '../../fielddef';
import {FieldDef, normalizeBin, vgField, vgFieldName} from '../../fielddef';
import {BinTransform} from '../../transform';
import {Dict, duplicate, flatten, keys, vals} from '../../util';
import {VgBinTransform, VgTransform} from '../../vega.schema';
Expand All @@ -20,7 +20,7 @@ function rangeFormula(model: ModelWithField, fieldDef: FieldDef<string>, channel
const endField = vgField(fieldDef, {expr: 'datum', binSuffix: 'end'});

return {
formulaAs: vgField(fieldDef, {binSuffix: 'range'}),
formulaAs: vgFieldName(fieldDef, {binSuffix: 'range'}),
formula: binFormatExpression(startField, endField, guide.format, config)
};
}
Expand Down Expand Up @@ -48,7 +48,7 @@ function createBinComponent(t: FieldDef<string> | BinTransform, bin: boolean | B
if (isBinTransform(t)) {
as = isString(t.as) ? [t.as, `${t.as}_end`] : [t.as[0], t.as[1]];
} else {
as = [vgField(t, {}), vgField(t, {binSuffix: 'end'})];
as = [vgFieldName(t, {}), vgFieldName(t, {binSuffix: 'end'})];
}

const normalizedBin = normalizeBin(bin, undefined) || {};
Expand Down
13 changes: 9 additions & 4 deletions src/compile/data/calculate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {DateTime} from '../../datetime';
import {FieldDef, isScaleFieldDef, vgField} from '../../fielddef';
import {FieldDef, isScaleFieldDef, vgField, vgFieldName} from '../../fielddef';
import {fieldFilterExpression} from '../../predicate';
import {isSortArray} from '../../sort';
import {duplicate} from '../../util';
Expand Down Expand Up @@ -40,7 +40,7 @@ export class CalculateNode extends DataFlowNode {

parent = new CalculateNode(parent, {
calculate,
as: sortArrayIndexField(fieldDef, channel)
as: sortArrayIndexField(fieldDef, channel, undefined, true)
});
}
});
Expand All @@ -62,6 +62,11 @@ export class CalculateNode extends DataFlowNode {
}
}

export function sortArrayIndexField(fieldDef: FieldDef<string>, channel: SingleDefChannel, expr?: 'datum') {
return vgField(fieldDef, {prefix: channel, suffix: 'sort_index', expr});
export function sortArrayIndexField(
fieldDef: FieldDef<string>,
channel: SingleDefChannel,
expr?: 'datum',
forAs = false
) {
return (forAs ? vgFieldName : vgField)(fieldDef, {prefix: channel, suffix: 'sort_index', expr});
}
4 changes: 2 additions & 2 deletions src/compile/data/facet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {AggregateOp} from 'vega';
import {isArray} from 'vega-util';
import {isBinning} from '../../bin';
import {COLUMN, ROW, ScaleChannel} from '../../channel';
import {vgField} from '../../fielddef';
import {vgField, vgFieldName} from '../../fielddef';
import * as log from '../../log';
import {hasDiscreteDomain} from '../../scale';
import {EncodingSortField, isSortField} from '../../sort';
Expand Down Expand Up @@ -133,7 +133,7 @@ export class FacetNode extends DataFlowNode {
const {op, field} = sortField;
fields.push(field);
ops.push(op);
as.push(vgField(sortField));
as.push(vgFieldName(sortField));
} else if (sortIndexField) {
fields.push(sortIndexField);
ops.push('max');
Expand Down
15 changes: 8 additions & 7 deletions src/compile/data/stack.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {isArray, isString} from 'vega-util';
import {FieldDef, isFieldDef, vgField} from '../../fielddef';
import {FieldDef, isFieldDef, vgField, vgFieldName} from '../../fielddef';
import {StackOffset} from '../../stack';
import {StackTransform} from '../../transform';
import {duplicate} from '../../util';
Expand Down Expand Up @@ -147,19 +147,19 @@ export class StackNode extends DataFlowNode {
{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);

return new StackNode(parent, {
dimensionFieldDef,
stackField: field,
stackField: model.vgField(stackProperties.fieldChannel),
facetby: [],
stackby,
sort,
offset: stackProperties.offset,
impute: stackProperties.impute,
as: [field + '_start', field + '_end']
as: [
model.vgFieldName(stackProperties.fieldChannel, {suffix: 'start'}),
model.vgFieldName(stackProperties.fieldChannel, {suffix: 'end'})
]
});
}

Expand Down Expand Up @@ -218,6 +218,7 @@ export class StackNode extends DataFlowNode {
// Impute
if (impute && dimensionFieldDef) {
const dimensionField = dimensionFieldDef ? vgField(dimensionFieldDef, {binSuffix: 'mid'}) : undefined;
const dimensionAs = dimensionFieldDef ? vgFieldName(dimensionFieldDef, {binSuffix: 'mid'}) : undefined;

if (dimensionFieldDef.bin) {
// As we can only impute one field at a time, we need to calculate
Expand All @@ -230,7 +231,7 @@ export class StackNode extends DataFlowNode {
'+' +
vgField(dimensionFieldDef, {expr: 'datum', binSuffix: 'end'}) +
')/2',
as: dimensionField
as: dimensionAs
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/compile/data/timeunit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {vgField} from '../../fielddef';
import {vgFieldName} from '../../fielddef';
import {fieldExpr, TimeUnit} from '../../timeunit';
import {TimeUnitTransform} from '../../transform';
import {Dict, duplicate, keys, vals} from '../../util';
Expand All @@ -25,7 +25,7 @@ export class TimeUnitNode extends DataFlowNode {
const formula = model.reduceFieldDef(
(timeUnitComponent: TimeUnitComponent, fieldDef) => {
if (fieldDef.timeUnit) {
const f = vgField(fieldDef);
const f = vgFieldName(fieldDef);
timeUnitComponent[f] = {
as: f,
timeUnit: fieldDef.timeUnit,
Expand Down
2 changes: 1 addition & 1 deletion src/compile/data/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class WindowTransformNode extends DataFlowNode {
{
op,
field,
as: facetSortFieldName(fieldDef, fieldDef.sort)
as: facetSortFieldName(fieldDef, fieldDef.sort, undefined, true)
}
],
groupby: [vgField(fieldDef)],
Expand Down
11 changes: 8 additions & 3 deletions src/compile/facet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Channel, COLUMN, ROW, ScaleChannel} from '../channel';
import {Config} from '../config';
import {reduce} from '../encoding';
import {FacetFieldDef, FacetMapping} from '../facet';
import {FieldDef, normalize, title as fieldDefTitle, vgField} from '../fielddef';
import {FieldDef, normalize, title as fieldDefTitle, vgField, vgFieldName} from '../fielddef';
import * as log from '../log';
import {hasDiscreteDomain} from '../scale';
import {EncodingSortField, isSortField, SortOrder} from '../sort';
Expand All @@ -23,8 +23,13 @@ import {RepeaterValue, replaceRepeaterInFacet} from './repeater';
import {parseGuideResolve} from './resolve';
import {assembleDomain, getFieldFromDomain} from './scale/domain';

export function facetSortFieldName(fieldDef: FacetFieldDef<string>, sort: EncodingSortField<string>, expr?: 'datum') {
return vgField(sort, {expr, suffix: `by_${vgField(fieldDef)}`});
export function facetSortFieldName(
fieldDef: FacetFieldDef<string>,
sort: EncodingSortField<string>,
expr?: 'datum',
forAs = false
) {
return (forAs ? vgFieldName : vgField)(sort, {expr, suffix: `by_${vgField(fieldDef)}`});
}

export class FacetModel extends ModelWithField {
Expand Down
12 changes: 11 additions & 1 deletion src/compile/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Channel, isChannel, isScaleChannel, ScaleChannel, SingleDefChannel} from
import {Config} from '../config';
import {Data, DataSourceType} from '../data';
import {forEach, reduce} from '../encoding';
import {ChannelDef, FieldDef, FieldRefOption, getFieldDef, vgField} from '../fielddef';
import {ChannelDef, FieldDef, FieldRefOption, getFieldDef, vgField, vgFieldName} from '../fielddef';
import * as log from '../log';
import {Resolve} from '../resolve';
import {hasDiscreteDomain} from '../scale';
Expand Down Expand Up @@ -633,6 +633,16 @@ export abstract class ModelWithField extends Model {
return vgField(fieldDef, opt);
}

public vgFieldName(channel: SingleDefChannel, opt: FieldRefOption = {}) {
const fieldDef = this.fieldDef(channel);

if (!fieldDef) {
return undefined;
}

return vgFieldName(fieldDef, opt);
}

protected abstract getMapping(): {[key in Channel]?: any};

public reduceFieldDef<T, U>(f: (acc: U, fd: FieldDef<string>, c: Channel) => U, init: T, t?: any) {
Expand Down
5 changes: 3 additions & 2 deletions src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
title,
ValueDef,
ValueDefWithCondition,
vgField
vgField,
vgFieldName
} from './fielddef';
import * as log from './log';
import {Mark} from './mark';
Expand Down Expand Up @@ -203,7 +204,7 @@ export function extractTransformsFromEncoding(oldEncoding: Encoding<string>, con

forEach(oldEncoding, (channelDef, channel) => {
if (isFieldDef(channelDef)) {
const transformedField = vgField(channelDef);
const transformedField = vgFieldName(channelDef);
if (channelDef.aggregate && isAggregateOp(channelDef.aggregate)) {
aggregate.push({
op: channelDef.aggregate,
Expand Down
24 changes: 19 additions & 5 deletions src/fielddef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,15 +339,15 @@ export function isScaleFieldDef<F>(channelDef: ChannelDef<F>): channelDef is Sca
}

export interface FieldRefOption {
/** exclude bin, aggregate, timeUnit */
/** Exclude bin, aggregate, timeUnit */
nofn?: boolean;
/** Wrap the field with datum or parent (e.g., datum['...'] for Vega Expression */
expr?: 'datum' | 'parent';
/** prepend fn with custom function prefix */
/** Prepend fn with custom function prefix */
prefix?: string;
/** append suffix to the field ref for bin (default='start') */
/** Append suffix to the field ref for bin (default='start') */
binSuffix?: 'end' | 'range' | 'mid';
/** append suffix to the field ref (general) */
/** Append suffix to the field ref (general) */
suffix?: string;
}

Expand All @@ -357,7 +357,10 @@ function isOpFieldDef(
return !!fieldDef['op'];
}

export function vgField(
/**
* Use when Vega expects a field name for example as the output of a transform.
*/
export function vgFieldName(
fieldDef: FieldDefBase<string> | WindowFieldDef | AggregatedFieldDef,
opt: FieldRefOption = {}
): string {
Expand Down Expand Up @@ -396,6 +399,17 @@ export function vgField(
field = `${prefix}_${field}`;
}

return field;
}

/**
* Get a vega field reference from a Vega-Lite field def. Use when Vega expects a field that can be nested.
*/
export function vgField(
fieldDef: FieldDefBase<string> | WindowFieldDef | AggregatedFieldDef,
opt: FieldRefOption = {}
): string {
const field = vgFieldName(fieldDef, opt);
if (opt.expr) {
// Expression to access flattened field. No need to escape dots.
return flatAccessWithDatum(field, opt.expr);
Expand Down
3 changes: 2 additions & 1 deletion src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ export function accessPathWithDatum(path: string, datum = 'datum') {
}

/**
* Return access with datum to the falttened field.
* Return access with datum to the flattened field.
*
* @param path The field name.
* @param datum The string to use for `datum`.
*/
Expand Down

0 comments on commit 52d132d

Please sign in to comment.