Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: recursively substitute signals for expressions #9301

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/compile/buildmodel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {SignalRef} from 'vega';
import type {ExprRef, SignalRef} from 'vega';
import {Config} from '../config';
import * as log from '../log';
import {isAnyConcatSpec, isFacetSpec, isLayerSpec, isUnitSpec, LayoutSizeMixins, NormalizedSpec} from '../spec';
Expand All @@ -7,9 +7,10 @@ import {FacetModel} from './facet';
import {LayerModel} from './layer';
import {Model} from './model';
import {UnitModel} from './unit';
import {SubstituteType} from '../vega.schema';

export function buildModel(
spec: NormalizedSpec,
spec: SubstituteType<NormalizedSpec, ExprRef, SignalRef>,
parent: Model,
parentGivenName: string,
unitSize: LayoutSizeMixins,
Expand All @@ -19,7 +20,7 @@ export function buildModel(
return new FacetModel(spec, parent, parentGivenName, config);
} else if (isLayerSpec(spec)) {
return new LayerModel(spec, parent, parentGivenName, unitSize, config);
} else if (isUnitSpec(spec)) {
} else if (isUnitSpec<SignalRef>(spec)) {
return new UnitModel(spec, parent, parentGivenName, unitSize, config);
} else if (isAnyConcatSpec(spec)) {
return new ConcatModel(spec, parent, parentGivenName, config);
Expand Down
6 changes: 4 additions & 2 deletions src/compile/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {buildModel} from './buildmodel';
import {assembleRootData} from './data/assemble';
import {optimizeDataflow} from './data/optimize';
import {Model} from './model';
import {deepReplaceExprRef} from '../expr';

export interface CompileOptions {
/**
Expand Down Expand Up @@ -89,7 +90,8 @@ export function compile(inputSpec: TopLevelSpec, opt: CompileOptions = {}) {

// - Decompose all extended unit specs into composition of unit spec. For example, a box plot get expanded into multiple layers of bars, ticks, and rules. The shorthand row/column channel is also expanded to a facet spec.
// - Normalize autosize and width or height spec
const spec = normalize(inputSpec, config);
const normalized = normalize(inputSpec, config);
const spec = deepReplaceExprRef(normalized) as any;

// 3. Build Model: normalized spec -> Model (a tree structure)

Expand Down Expand Up @@ -128,7 +130,7 @@ export function compile(inputSpec: TopLevelSpec, opt: CompileOptions = {}) {

return {
spec: vgSpec,
normalized: spec
normalized
};
} finally {
// Reset the singleton logger if a logger is provided
Expand Down
15 changes: 11 additions & 4 deletions src/compile/concat.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {NewSignal, SignalRef} from 'vega';
import {ExprRef, NewSignal, SignalRef} from 'vega';
import {Config} from '../config';
import * as log from '../log';
import {isHConcatSpec, isVConcatSpec, NormalizedConcatSpec, NormalizedSpec} from '../spec';
import {keys} from '../util';
import {VgData, VgLayout} from '../vega.schema';
import {SubstituteType, VgData, VgLayout} from '../vega.schema';
import {buildModel} from './buildmodel';
import {parseData} from './data/parse';
import {assembleLayoutSignals} from './layoutsize/assemble';
Expand All @@ -13,7 +13,12 @@ import {Model} from './model';
export class ConcatModel extends Model {
public readonly children: Model[];

constructor(spec: NormalizedConcatSpec, parent: Model, parentGivenName: string, config: Config<SignalRef>) {
constructor(
spec: SubstituteType<NormalizedConcatSpec, ExprRef, SignalRef>,
parent: Model,
parentGivenName: string,
config: Config<SignalRef>
) {
super(spec, 'concat', parent, parentGivenName, config, spec.resolve);

if (spec.resolve?.axis?.x === 'shared' || spec.resolve?.axis?.y === 'shared') {
Expand Down Expand Up @@ -59,7 +64,9 @@ export class ConcatModel extends Model {
// TODO(#2415): support shared axes
}

private getChildren(spec: NormalizedConcatSpec): NormalizedSpec[] {
private getChildren(
spec: SubstituteType<NormalizedConcatSpec, ExprRef, SignalRef>
): SubstituteType<NormalizedSpec, ExprRef, SignalRef>[] {
if (isVConcatSpec(spec)) {
return spec.vconcat;
} else if (isHConcatSpec(spec)) {
Expand Down
15 changes: 9 additions & 6 deletions src/compile/facet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import {isBinning} from '../bin';
import {COLUMN, ExtendedChannel, FacetChannel, FACET_CHANNELS, POSITION_SCALE_CHANNELS, ROW} from '../channel';
import {FieldName, FieldRefOption, initFieldDef, TypedFieldDef, vgField} from '../channeldef';
import {Config} from '../config';
import {ExprRef, replaceExprRef} from '../expr';
import {ExprRef} from '../expr';
import * as log from '../log';
import {hasDiscreteDomain} from '../scale';
import {DEFAULT_SORT_OP, EncodingSortField, isSortField, SortOrder} from '../sort';
import {NormalizedFacetSpec} from '../spec';
import {EncodingFacetMapping, FacetFieldDef, FacetMapping, isFacetMapping} from '../spec/facet';
import {keys} from '../util';
import {isVgRangeStep, VgData, VgLayout, VgMarkGroup} from '../vega.schema';
import {isVgRangeStep, SubstituteType, VgData, VgLayout, VgMarkGroup} from '../vega.schema';
import {buildModel} from './buildmodel';
import {assembleFacetData} from './data/assemble';
import {sortArrayIndexField} from './data/calculate';
Expand Down Expand Up @@ -40,7 +40,12 @@ export class FacetModel extends ModelWithField {

public readonly children: Model[];

constructor(spec: NormalizedFacetSpec, parent: Model, parentGivenName: string, config: Config<SignalRef>) {
constructor(
spec: SubstituteType<NormalizedFacetSpec, ExprRef, SignalRef>,
parent: Model,
parentGivenName: string,
config: Config<SignalRef>
) {
super(spec, 'facet', parent, parentGivenName, config, spec.resolve);

this.child = buildModel(spec.spec, this, this.getName('child'), undefined, config);
Expand Down Expand Up @@ -82,9 +87,7 @@ export class FacetModel extends ModelWithField {
// Cast because we call initFieldDef, which assumes general FieldDef.
// However, FacetFieldDef is a bit more constrained than the general FieldDef
const facetFieldDef = initFieldDef(fieldDef, channel) as FacetFieldDef<FieldName, SignalRef>;
if (facetFieldDef.header) {
facetFieldDef.header = replaceExprRef(facetFieldDef.header);
} else if (facetFieldDef.header === null) {
if (facetFieldDef.header === null) {
facetFieldDef.header = null;
}
return facetFieldDef;
Expand Down
8 changes: 4 additions & 4 deletions src/compile/layer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {Legend as VgLegend, NewSignal, SignalRef, Title as VgTitle} from 'vega';
import {Legend as VgLegend, NewSignal, SignalRef, Title as VgTitle, ExprRef} from 'vega';
import {array} from 'vega-util';
import {Config} from '../config';
import * as log from '../log';
import {isLayerSpec, isUnitSpec, LayoutSizeMixins, NormalizedLayerSpec} from '../spec';
import {keys} from '../util';
import {VgData, VgLayout} from '../vega.schema';
import {SubstituteType, VgData, VgLayout} from '../vega.schema';
import {assembleAxisSignals} from './axis/assemble';
import {parseLayerAxes} from './axis/parse';
import {parseData} from './data/parse';
Expand All @@ -21,7 +21,7 @@
public readonly children: Model[];

constructor(
spec: NormalizedLayerSpec,
spec: SubstituteType<NormalizedLayerSpec, ExprRef, SignalRef>,
parent: Model,
parentGivenName: string,
parentGivenSize: LayoutSizeMixins,
Expand All @@ -39,7 +39,7 @@
if (isLayerSpec(layer)) {
return new LayerModel(layer, this, this.getName(`layer_${i}`), layoutSize, config);
} else if (isUnitSpec(layer)) {
return new UnitModel(layer, this, this.getName(`layer_${i}`), layoutSize, config);
return new UnitModel(layer, this, this.getName(`layer_${i}`), layoutSize, config) as UnitModel;

Check failure on line 42 in src/compile/layer.ts

View workflow job for this annotation

GitHub Actions / Format, Schema, and Examples

Expression produces a union type that is too complex to represent.

Check failure on line 42 in src/compile/layer.ts

View workflow job for this annotation

GitHub Actions / Runtime, Linting, and Coverage

Expression produces a union type that is too complex to represent.

Check failure on line 42 in src/compile/layer.ts

View workflow job for this annotation

GitHub Actions / CLI (ubuntu-latest)

Expression produces a union type that is too complex to represent.
}

throw new Error(log.message.invalidSpec(layer));
Expand Down
6 changes: 1 addition & 5 deletions src/compile/mark/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {isBinned, isBinning} from '../../bin';
import {isFieldDef, isNumericDataDef, isUnbinnedQuantitativeFieldOrDatumDef, isTypedFieldDef} from '../../channeldef';
import {Config} from '../../config';
import {Encoding, isAggregate} from '../../encoding';
import {replaceExprRef} from '../../expr';
import * as log from '../../log';
import {
AREA,
Expand All @@ -25,10 +24,7 @@ import {QUANTITATIVE, TEMPORAL} from '../../type';
import {contains, getFirstDefined} from '../../util';
import {getMarkConfig, getMarkPropOrConfig} from '../common';

export function initMarkdef(originalMarkDef: MarkDef, encoding: Encoding<string>, config: Config<SignalRef>) {
// FIXME: markDef expects that exprRefs are replaced recursively but replaceExprRef only replaces the top level
const markDef: MarkDef<Mark, SignalRef> = replaceExprRef(originalMarkDef) as any;

export function initMarkdef(markDef: MarkDef<Mark, SignalRef>, encoding: Encoding<string>, config: Config<SignalRef>) {
// set orient, which can be overridden by rules as sometimes the specified orient is invalid.
const specifiedOrient = getMarkPropOrConfig('orient', markDef, config);
markDef.orient = orient(markDef.type, encoding, specifiedOrient);
Expand Down
12 changes: 6 additions & 6 deletions src/compile/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {ChannelDef, FieldDef, FieldRefOption, getFieldDef, vgField} from '../cha
import {Config} from '../config';
import {Data, DataSourceType} from '../data';
import {forEach, reduce} from '../encoding';
import {ExprRef, replaceExprRef} from '../expr';
import {ExprRef} from '../expr';
import * as log from '../log';
import {Resolve} from '../resolve';
import {hasDiscreteDomain} from '../scale';
Expand All @@ -38,7 +38,7 @@ import {NormalizedSpec} from '../spec/index';
import {extractTitleConfig, isText, TitleParams} from '../title';
import {normalizeTransform, Transform} from '../transform';
import {contains, Dict, duplicate, isEmpty, keys, varName} from '../util';
import {isVgRangeStep, VgData, VgEncodeEntry, VgLayout, VgMarkGroup} from '../vega.schema';
import {isVgRangeStep, SubstituteType, VgData, VgEncodeEntry, VgLayout, VgMarkGroup} from '../vega.schema';
import {assembleAxes} from './axis/assemble';
import {AxisComponentIndex} from './axis/component';
import {signalOrValueRef} from './common';
Expand Down Expand Up @@ -187,21 +187,21 @@ export abstract class Model {
public abstract readonly children: Model[];

constructor(
spec: NormalizedSpec,
spec: SubstituteType<NormalizedSpec, ExprRef, SignalRef>,
public readonly type: SpecType,
public readonly parent: Model,
parentGivenName: string,
public readonly config: Config<SignalRef>,
resolve: Resolve,
view?: ViewBackground<ExprRef | SignalRef>
view?: ViewBackground<SignalRef>
) {
this.parent = parent;
this.config = config;
this.view = replaceExprRef(view);
this.view = view;

// If name is not provided, always use parent's givenName to avoid name conflicts.
this.name = spec.name ?? parentGivenName;
this.title = isText(spec.title) ? {text: spec.title} : spec.title ? replaceExprRef(spec.title) : undefined;
this.title = isText(spec.title) ? {text: spec.title} : spec.title;

// Shared name maps
this.scaleNameMap = parent ? parent.scaleNameMap : new NameMap();
Expand Down
5 changes: 2 additions & 3 deletions src/compile/projection/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {hasOwnProperty} from 'vega-util';
import {LATITUDE, LATITUDE2, LONGITUDE, LONGITUDE2, SHAPE} from '../../channel';
import {getFieldOrDatumDef} from '../../channeldef';
import {DataSourceType} from '../../data';
import {replaceExprRef} from '../../expr';
import {PROJECTION_PROPERTIES} from '../../projection';
import {GEOJSON} from '../../type';
import {deepEqual, duplicate, every} from '../../util';
Expand All @@ -17,15 +16,15 @@ export function parseProjection(model: Model) {

function parseUnitProjection(model: UnitModel): ProjectionComponent {
if (model.hasProjection) {
const proj = replaceExprRef(model.specifiedProjection);
const proj = model.specifiedProjection;
const fit = !(proj && (proj.scale != null || proj.translate != null));
const size = fit ? [model.getSizeSignalRef('width'), model.getSizeSignalRef('height')] : undefined;
const data = fit ? gatherFitData(model) : undefined;

const projComp = new ProjectionComponent(
model.projectionName(true),
{
...replaceExprRef(model.config.projection),
...(model.config.projection ?? {}),
...proj
},
size,
Expand Down
29 changes: 7 additions & 22 deletions src/compile/unit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {NewSignal, SignalRef} from 'vega';
import {isArray} from 'vega-util';
import {Axis, AxisInternal, isConditionalAxisValue} from '../axis';
import {
Channel,
Expand Down Expand Up @@ -27,7 +26,7 @@ import {Config} from '../config';
import {isGraticuleGenerator} from '../data';
import * as vlEncoding from '../encoding';
import {Encoding, initEncoding} from '../encoding';
import {ExprRef, replaceExprRef} from '../expr';
import {ExprRef} from '../expr';
import {LegendInternal} from '../legend';
import {GEOSHAPE, isMarkDef, Mark, MarkDef} from '../mark';
import {Projection} from '../projection';
Expand All @@ -37,7 +36,7 @@ import {LayoutSizeMixins, NormalizedUnitSpec} from '../spec';
import {isFrameMixins} from '../spec/base';
import {stack, StackProperties} from '../stack';
import {keys} from '../util';
import {VgData, VgLayout} from '../vega.schema';
import {SubstituteType, VgData, VgLayout} from '../vega.schema';
import {assembleAxisSignals} from './axis/assemble';
import {AxisInternalIndex} from './axis/component';
import {parseUnitAxes} from './axis/parse';
Expand Down Expand Up @@ -74,13 +73,13 @@ export class UnitModel extends ModelWithField {

protected specifiedLegends: LegendInternalIndex = {};

public specifiedProjection: Projection<ExprRef | SignalRef> = {};
public specifiedProjection: Projection<SignalRef> = {};

public readonly selection: SelectionParameter[] = [];
public children: Model[] = [];

constructor(
spec: NormalizedUnitSpec,
spec: SubstituteType<NormalizedUnitSpec, ExprRef, SignalRef>,
parent: Model,
parentGivenName: string,
parentGivenSize: LayoutSizeMixins = {},
Expand Down Expand Up @@ -154,25 +153,13 @@ export class UnitModel extends ModelWithField {
| PositionFieldDef<string>
| MarkPropFieldOrDatumDef<string>;
if (fieldOrDatumDef) {
scales[channel] = this.initScale(fieldOrDatumDef.scale ?? {});
// TODO: remove as Scale<SignalRef> when we have better types for scale
scales[channel] = fieldOrDatumDef.scale as Scale<SignalRef>;
}
return scales;
}, {} as ScaleIndex);
}

private initScale(scale: Scale<ExprRef | SignalRef>): Scale<SignalRef> {
const {domain, range} = scale;
// TODO: we could simplify this function if we had a recursive replace function
const scaleInternal = replaceExprRef(scale);
if (isArray(domain)) {
scaleInternal.domain = domain.map(signalRefOrValue);
}
if (isArray(range)) {
scaleInternal.range = range.map(signalRefOrValue);
}
return scaleInternal as Scale<SignalRef>;
}

private initAxes(encoding: Encoding<string>): AxisInternalIndex {
return POSITION_SCALE_CHANNELS.reduce((_axis, channel) => {
// Position Axis
Expand Down Expand Up @@ -212,9 +199,7 @@ export class UnitModel extends ModelWithField {

if (fieldOrDatumDef && supportLegend(channel)) {
const legend = fieldOrDatumDef.legend;
_legend[channel] = legend
? replaceExprRef(legend) // convert truthy value to object
: legend;
_legend[channel] = legend;
}

return _legend;
Expand Down
Loading
Loading