Skip to content

Commit

Permalink
refactor: implement repeat as a normalization to reduce number of models
Browse files Browse the repository at this point in the history
Fixes #4947
  • Loading branch information
domoritz committed Mar 2, 2020
1 parent abfdc51 commit 449e1af
Show file tree
Hide file tree
Showing 61 changed files with 1,165 additions and 837 deletions.
966 changes: 742 additions & 224 deletions build/vega-lite-schema.json

Large diffs are not rendered by default.

96 changes: 0 additions & 96 deletions src/compile/baseconcat.ts

This file was deleted.

23 changes: 5 additions & 18 deletions src/compile/buildmodel.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
import {Config} from '../config';
import * as log from '../log';
import {
isAnyConcatSpec,
isFacetSpec,
isLayerSpec,
isRepeatSpec,
isUnitSpec,
LayoutSizeMixins,
NormalizedSpec
} from '../spec';
import {isAnyConcatSpec, isFacetSpec, isLayerSpec, isUnitSpec, LayoutSizeMixins, NormalizedSpec} from '../spec';
import {ConcatModel} from './concat';
import {FacetModel} from './facet';
import {LayerModel} from './layer';
import {Model} from './model';
import {RepeatModel} from './repeat';
import {RepeaterValue} from './repeater';
import {UnitModel} from './unit';

export function buildModel(
spec: NormalizedSpec,
parent: Model,
parentGivenName: string,
unitSize: LayoutSizeMixins,
repeater: RepeaterValue,
config: Config
): Model {
if (isFacetSpec(spec)) {
return new FacetModel(spec, parent, parentGivenName, repeater, config);
return new FacetModel(spec, parent, parentGivenName, config);
} else if (isLayerSpec(spec)) {
return new LayerModel(spec, parent, parentGivenName, unitSize, repeater, config);
return new LayerModel(spec, parent, parentGivenName, unitSize, config);
} else if (isUnitSpec(spec)) {
return new UnitModel(spec, parent, parentGivenName, unitSize, repeater, config);
} else if (isRepeatSpec(spec)) {
return new RepeatModel(spec, parent, parentGivenName, repeater, config);
return new UnitModel(spec, parent, parentGivenName, unitSize, config);
} else if (isAnyConcatSpec(spec)) {
return new ConcatModel(spec, parent, parentGivenName, repeater, config);
return new ConcatModel(spec, parent, parentGivenName, config);
}
throw new Error(log.message.invalidSpec(spec));
}
3 changes: 1 addition & 2 deletions src/compile/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ import {SortFields} from '../sort';
import {formatExpression, normalizeTimeUnit, TimeUnit} from '../timeunit';
import {isText} from '../title';
import {QUANTITATIVE} from '../type';
import {getFirstDefined} from '../util';
import {deepEqual, getFirstDefined} from '../util';
import {isSignalRef, VgEncodeEntry} from '../vega.schema';
import {deepEqual} from './../util';
import {AxisComponentProps} from './axis/component';
import {Explicit} from './split';
import {UnitModel} from './unit';
Expand Down
6 changes: 3 additions & 3 deletions src/compile/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
TopLevelProperties
} from '../spec/toplevel';
import {keys} from '../util';
import {Config} from './../config';
import {Config} from '../config';
import {buildModel} from './buildmodel';
import {assembleRootData} from './data/assemble';
import {optimizeDataflow} from './data/optimize';
Expand Down Expand Up @@ -96,8 +96,8 @@ export function compile(inputSpec: TopLevelSpec, opt: CompileOptions = {}) {
// 3. Build Model: normalized spec -> Model (a tree structure)

// This phases instantiates the models with default config by doing a top-down traversal. This allows us to pass properties that child models derive from their parents via their constructors.
// See the abstract `Model` class and its children (UnitModel, LayerModel, FacetModel, RepeatModel, ConcatModel) for different types of models.
const model: Model = buildModel(spec, null, '', undefined, undefined, config);
// See the abstract `Model` class and its children (UnitModel, LayerModel, FacetModel, ConcatModel) for different types of models.
const model: Model = buildModel(spec, null, '', undefined, config);

// 4 Parse: Model --> Model with components

Expand Down
95 changes: 81 additions & 14 deletions src/compile/concat.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import {NewSignal} from 'vega';
import {Config} from '../config';
import * as log from '../log';
import {isHConcatSpec, isVConcatSpec, NormalizedConcatSpec} from '../spec';
import {NormalizedSpec} from '../spec';
import {isHConcatSpec, isVConcatSpec, NormalizedConcatSpec, NormalizedSpec} from '../spec';
import {VgLayout} from '../vega.schema';
import {BaseConcatModel} from './baseconcat';
import {keys} from './../util';
import {VgData} from './../vega.schema';
import {buildModel} from './buildmodel';
import {parseData} from './data/parse';
import {assembleLayoutSignals} from './layoutsize/assemble';
import {parseConcatLayoutSize} from './layoutsize/parse';
import {Model} from './model';
import {RepeaterValue} from './repeater';

export class ConcatModel extends BaseConcatModel {
export class ConcatModel extends Model {
public readonly children: Model[];

public readonly concatType: 'vconcat' | 'hconcat' | 'concat';

constructor(
spec: NormalizedConcatSpec,
parent: Model,
parentGivenName: string,
repeater: RepeaterValue,
config: Config
) {
super(spec, 'concat', parent, parentGivenName, config, repeater, spec.resolve);
constructor(spec: NormalizedConcatSpec, parent: Model, parentGivenName: string, config: Config) {
super(spec, 'concat', parent, parentGivenName, config, spec.resolve);

if (spec.resolve?.axis?.x === 'shared' || spec.resolve?.axis?.y === 'shared') {
log.warn(log.message.CONCAT_CANNOT_SHARE_AXIS);
Expand All @@ -30,10 +26,44 @@ export class ConcatModel extends BaseConcatModel {
this.concatType = isVConcatSpec(spec) ? 'vconcat' : isHConcatSpec(spec) ? 'hconcat' : 'concat';

this.children = this.getChildren(spec).map((child, i) => {
return buildModel(child, this, this.getName('concat_' + i), undefined, repeater, config);
return buildModel(child, this, this.getName('concat_' + i), undefined, config);
});
}

public parseData() {
this.component.data = parseData(this);
this.children.forEach(child => {
child.parseData();
});
}

public parseSelections() {
// Merge selections up the hierarchy so that they may be referenced
// across unit specs. Persist their definitions within each child
// to assemble signals which remain within output Vega unit groups.
this.component.selection = {};
for (const child of this.children) {
child.parseSelections();
for (const key of keys(child.component.selection)) {
this.component.selection[key] = child.component.selection[key];
}
}
}

public parseMarkGroup() {
for (const child of this.children) {
child.parseMarkGroup();
}
}

public parseAxesAndHeaders() {
for (const child of this.children) {
child.parseAxesAndHeaders();
}

// TODO(#2415): support shared axes
}

private getChildren(spec: NormalizedConcatSpec): NormalizedSpec[] {
if (isVConcatSpec(spec)) {
return spec.vconcat;
Expand All @@ -51,6 +81,43 @@ export class ConcatModel extends BaseConcatModel {
return null;
}

public assembleSelectionTopLevelSignals(signals: NewSignal[]): NewSignal[] {
return this.children.reduce((sg, child) => child.assembleSelectionTopLevelSignals(sg), signals);
}

public assembleSignals(): NewSignal[] {
this.children.forEach(child => child.assembleSignals());
return [];
}

public assembleLayoutSignals(): NewSignal[] {
return this.children.reduce((signals, child) => {
return [...signals, ...child.assembleLayoutSignals()];
}, assembleLayoutSignals(this));
}

public assembleSelectionData(data: readonly VgData[]): readonly VgData[] {
return this.children.reduce((db, child) => child.assembleSelectionData(db), data);
}

public assembleMarks(): any[] {
// only children have marks
return this.children.map(child => {
const title = child.assembleTitle();
const style = child.assembleGroupStyle();
const encodeEntry = child.assembleGroupEncodeEntry(false);

return {
type: 'group',
name: child.getName('group'),
...(title ? {title} : {}),
...(style ? {style} : {}),
...(encodeEntry ? {encode: {update: encodeEntry}} : {}),
...child.assembleGroup()
};
});
}

protected assembleDefaultLayout(): VgLayout {
return {
...(this.concatType === 'vconcat' ? {columns: 1} : {}),
Expand Down
2 changes: 1 addition & 1 deletion src/compile/data/debug.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {entries, uniqueId} from './../../util';
import {entries, uniqueId} from '../../util';
import {DataFlowNode, OutputNode} from './dataflow';
import {SourceNode} from './source';

Expand Down
2 changes: 1 addition & 1 deletion src/compile/data/filterinvalid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Dict, hash, keys} from '../../util';
import {FilterTransform as VgFilterTransform} from 'vega';
import {getMarkPropOrConfig} from '../common';
import {UnitModel} from '../unit';
import {TypedFieldDef} from './../../channeldef';
import {TypedFieldDef} from '../../channeldef';
import {DataFlowNode} from './dataflow';

export class FilterInvalidNode extends DataFlowNode {
Expand Down
4 changes: 2 additions & 2 deletions src/compile/data/joinaggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {vgField} from '../../channeldef';
import {JoinAggregateTransform} from '../../transform';
import {duplicate, hash} from '../../util';
import {VgJoinAggregateTransform} from '../../vega.schema';
import {JoinAggregateFieldDef} from './../../transform';
import {unique} from './../../util';
import {JoinAggregateFieldDef} from '../../transform';
import {unique} from '../../util';
import {DataFlowNode} from './dataflow';

/**
Expand Down
2 changes: 1 addition & 1 deletion src/compile/data/source.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Data, DataFormatType, isGenerator, isInlineData, isNamedData, isSphereGenerator, isUrlData} from '../../data';
import {contains, keys, omit} from '../../util';
import {VgData} from '../../vega.schema';
import {DataFormat} from './../../data';
import {DataFormat} from '../../data';
import {DataFlowNode} from './dataflow';

export class SourceNode extends DataFlowNode {
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 @@ -5,7 +5,7 @@ import {SortOrder} from '../../sort';
import {WindowFieldDef, WindowOnlyOp, WindowTransform} from '../../transform';
import {duplicate, hash} from '../../util';
import {VgComparator, VgJoinAggregateTransform} from '../../vega.schema';
import {unique} from './../../util';
import {unique} from '../../util';
import {DataFlowNode} from './dataflow';

/**
Expand Down
20 changes: 6 additions & 14 deletions src/compile/facet.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {FieldName} from '../channeldef';
import {AggregateOp, LayoutAlign, NewSignal} from 'vega';
import {isArray} from 'vega-util';
import {isBinning} from '../bin';
Expand All @@ -22,7 +23,6 @@ import {HEADER_CHANNELS, HEADER_TYPES} from './header/component';
import {parseFacetHeaders} from './header/parse';
import {parseChildrenLayoutSize} from './layoutsize/parse';
import {Model, ModelWithField} from './model';
import {RepeaterValue, replaceRepeaterInFacet} from './repeater';
import {assembleDomain, getFieldFromDomain} from './scale/domain';
import {assembleFacetSignals} from './selection/assemble';

Expand All @@ -41,24 +41,16 @@ export class FacetModel extends ModelWithField {

public readonly children: Model[];

constructor(
spec: NormalizedFacetSpec,
parent: Model,
parentGivenName: string,
repeater: RepeaterValue,
config: Config
) {
super(spec, 'facet', parent, parentGivenName, config, repeater, spec.resolve);
constructor(spec: NormalizedFacetSpec, parent: Model, parentGivenName: string, config: Config) {
super(spec, 'facet', parent, parentGivenName, config, spec.resolve);

this.child = buildModel(spec.spec, this, this.getName('child'), undefined, repeater, config);
this.child = buildModel(spec.spec, this, this.getName('child'), undefined, config);
this.children = [this.child];

const facet = replaceRepeaterInFacet(spec.facet, repeater);

this.facet = this.initFacet(facet);
this.facet = this.initFacet(spec.facet);
}

private initFacet(facet: FacetFieldDef<string> | FacetMapping<string>): EncodingFacetMapping<string> {
private initFacet(facet: FacetFieldDef<FieldName> | FacetMapping<FieldName>): EncodingFacetMapping<FieldName> {
// clone to prevent side effect to the original spec
if (!isFacetMapping(facet)) {
return {facet: normalize(facet, 'facet')};
Expand Down
Loading

0 comments on commit 449e1af

Please sign in to comment.