Skip to content

Commit

Permalink
feat: support repeat layer
Browse files Browse the repository at this point in the history
part of #1274

Note that repeat layer isn't very useful yet until we have `datum` support (#1601)
  • Loading branch information
kanitw committed Mar 22, 2020
1 parent a5d9766 commit 556d175
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 59 deletions.
48 changes: 48 additions & 0 deletions examples/specs/repeat_child_layer.vl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"description": "Summarized and per year weather information for Seatle and New York.",
"data": {"url": "data/weather.csv"},
"repeat": {"column": ["temp_max", "precipitation", "wind"]},
"spec": {
"layer": [
{
"mark": "line",
"encoding": {
"y": {
"aggregate": "mean",
"field": {"repeat": "column"},
"type": "quantitative"
},
"x": {
"timeUnit": "month",
"field": "date",
"type": "ordinal"
},
"detail": {
"timeUnit": "year",
"type": "temporal",
"field": "date"
},
"color": {"type": "nominal", "field": "location"},
"opacity": {"value": 0.2}
}
},
{
"mark": "line",
"encoding": {
"y": {
"aggregate": "mean",
"field": {"repeat": "column"},
"type": "quantitative"
},
"x": {
"timeUnit": "month",
"field": "date",
"type": "ordinal"
},
"color": {"type": "nominal", "field": "location"}
}
}
]
}
}
58 changes: 17 additions & 41 deletions examples/specs/repeat_layer.vl.json
Original file line number Diff line number Diff line change
@@ -1,48 +1,24 @@
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"description": "Summarized and per year weather information for Seatle and New York.",
"data": {"url": "data/weather.csv"},
"repeat": {"column": ["temp_max", "precipitation", "wind"]},
"data": {
"url": "data/movies.json"
},
"repeat": {
"layer": ["US_Gross", "Worldwide_Gross"]
},
"spec": {
"layer": [
{
"mark": "line",
"encoding": {
"y": {
"aggregate": "mean",
"field": {"repeat": "column"},
"type": "quantitative"
},
"x": {
"timeUnit": "month",
"field": "date",
"type": "ordinal"
},
"detail": {
"timeUnit": "year",
"type": "temporal",
"field": "date"
},
"color": {"type": "nominal", "field": "location"},
"opacity": {"value": 0.2}
}
"mark": "line",
"encoding": {
"x": {
"bin": true,
"field": "IMDB_Rating",
"type": "quantitative"
},
{
"mark": "line",
"encoding": {
"y": {
"aggregate": "mean",
"field": {"repeat": "column"},
"type": "quantitative"
},
"x": {
"timeUnit": "month",
"field": "date",
"type": "ordinal"
},
"color": {"type": "nominal", "field": "location"}
}
"y": {
"aggregate": "mean",
"field": {"repeat": "layer"},
"type": "quantitative"
}
]
}
}
}
2 changes: 1 addition & 1 deletion site/_includes/docs_toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@
- [Repeat]({{site.baseurl}}/docs/repeat.html)
- [Documentation Overview]({{site.baseurl}}/docs/repeat.html#documentation-overview)
- [Repeat Operator]({{site.baseurl}}/docs/repeat.html#repeat-operator)
- [Row/Column Repeat Mapping]({{site.baseurl}}/docs/repeat.html#repeat-mapping)
- [Row/Column/Layer Repeat Mapping]({{site.baseurl}}/docs/repeat.html#repeat-mapping)
- [Examples]({{site.baseurl}}/docs/repeat.html#examples)
- [Resolve]({{site.baseurl}}/docs/repeat.html#resolve)
- [Repeat Configuration]({{site.baseurl}}/docs/repeat.html#config)
Expand Down
4 changes: 2 additions & 2 deletions site/docs/composition/repeat.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ In addition to [common properties of a view specification](spec.html#common), a

{: #repeat-mapping}

## Row/Column Repeat Mapping
## Row/Column/Layer Repeat Mapping

The `repeat` property can be an object with two optional properties (`"row"` and `"column"`), which define the list of fields that should be repeated into a row or column.
The `repeat` property can be an object with at least one of `"row"`, `"column"` and `"layer"` properties, which define the list of fields that should be repeated into a row, a column, or a layer.

{% include table.html props="row,column" source="RepeatMapping" %}

Expand Down
2 changes: 1 addition & 1 deletion src/channeldef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export type StringFieldDefWithCondition<F extends Field> = FieldDefWithCondition
* Reference to a repeated value.
*/
export interface RepeatRef {
repeat: 'row' | 'column' | 'repeat';
repeat: 'row' | 'column' | 'repeat' | 'layer';
}

export type FieldName = string;
Expand Down
72 changes: 64 additions & 8 deletions src/normalize/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
import {NormalizedSpec} from '../spec/index';
import {NormalizedLayerSpec} from '../spec/layer';
import {SpecMapper} from '../spec/map';
import {RepeatSpec} from '../spec/repeat';
import {isLayerRepeatSpec, LayerRepeatSpec, NonLayerRepeatSpec, RepeatSpec} from '../spec/repeat';
import {isUnitSpec, NormalizedUnitSpec} from '../spec/unit';
import {keys, omit, varName} from '../util';
import {NonFacetUnitNormalizer, NormalizerParams} from './base';
Expand Down Expand Up @@ -76,7 +76,62 @@ export class CoreNormalizer extends SpecMapper<NormalizerParams, FacetedUnitSpec
return specWithReplacedEncoding as NormalizedUnitSpec;
}

protected mapRepeat(spec: RepeatSpec, params: NormalizerParams): GenericConcatSpec<NormalizedSpec> {
protected mapRepeat(
spec: RepeatSpec,
params: NormalizerParams
): GenericConcatSpec<NormalizedSpec> | NormalizedLayerSpec {
if (isLayerRepeatSpec(spec)) {
return this.mapLayerRepeat(spec, params);
} else {
return this.mapNonLayerRepeat(spec, params);
}
}

private mapLayerRepeat(
spec: LayerRepeatSpec,
params: NormalizerParams
): GenericConcatSpec<NormalizedSpec> | NormalizedLayerSpec {
const {repeat, spec: childSpec, ...rest} = spec;
const {row, column, layer} = repeat;

const {repeater = {}, repeaterPrefix = ''} = params;

if (row || column) {
return this.mapRepeat(
{
...spec,
repeat: {
...(row ? {row} : {}),
...(column ? {column} : {})
},
spec: {
repeat: {layer},
spec: childSpec
}
},
params
);
} else {
return {
...rest,
layer: layer.map(layerValue => {
const childRepeater = {
...repeater,
layer: layerValue
};

const childName = (childSpec.name || '') + repeaterPrefix + `child__layer_${varName(layerValue)}`;

const child = this.mapLayerOrUnit(childSpec, {...params, repeater: childRepeater, repeaterPrefix: childName});
child.name = childName;

return child;
})
};
}
}

private mapNonLayerRepeat(spec: NonLayerRepeatSpec, params: NormalizerParams): GenericConcatSpec<NormalizedSpec> {
const {repeat, spec: childSpec, data, ...remainingProperties} = spec;

if (!isArray(repeat) && spec.columns) {
Expand All @@ -85,12 +140,13 @@ export class CoreNormalizer extends SpecMapper<NormalizerParams, FacetedUnitSpec
log.warn(log.message.columnsNotSupportByRowCol('repeat'));
}

const children: NormalizedSpec[] = [];
const concat: NormalizedSpec[] = [];

const {repeater, repeaterPrefix = ''} = params;
const {repeater = {}, repeaterPrefix = ''} = params;

const row = (!isArray(repeat) && repeat.row) || [repeater ? repeater.row : null];
const column = (!isArray(repeat) && repeat.column) || [repeater ? repeater.column : null];

const repeatValues = (isArray(repeat) && repeat) || [repeater ? repeater.repeat : null];

// cross product
Expand All @@ -100,7 +156,8 @@ export class CoreNormalizer extends SpecMapper<NormalizerParams, FacetedUnitSpec
const childRepeater = {
repeat: repeatValue,
row: rowValue,
column: columnValue
column: columnValue,
layer: repeater.layer
};

const childName =
Expand All @@ -116,19 +173,18 @@ export class CoreNormalizer extends SpecMapper<NormalizerParams, FacetedUnitSpec
child.name = childName;

// we move data up
children.push(omit(child, ['data']) as NormalizedSpec);
concat.push(omit(child, ['data']) as NormalizedSpec);
}
}
}

const columns = isArray(repeat) ? spec.columns : repeat.column ? repeat.column.length : 1;

return {
data: childSpec.data ?? data, // data from child spec should have precedence
align: 'all',
...remainingProperties,
columns,
concat: children
concat
};
}

Expand Down
6 changes: 4 additions & 2 deletions src/normalize/repeater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import {
ChannelDef,
Field,
FieldDef,
FieldName,
hasConditionalFieldDef,
isConditionalDef,
isFieldDef,
isRepeatRef,
isSortableFieldDef,
ScaleFieldDef,
ValueDef,
FieldName
ValueDef
} from '../channeldef';
import {Encoding} from '../encoding';
import * as log from '../log';
Expand All @@ -22,6 +22,8 @@ export interface RepeaterValue {
column?: string;

repeat?: string;

layer?: string;
}

export function replaceRepeaterInFacet(
Expand Down
34 changes: 31 additions & 3 deletions src/spec/repeat.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {FieldName} from '../channeldef';
import {isArray} from 'vega-util';
import {GenericSpec, LayerSpec} from '.';
import {FacetedUnitSpec} from './unit';
import {FieldName} from '../channeldef';
import {BaseSpec, GenericCompositionLayoutWithColumns, ResolveMixins} from './base';
import {FacetedUnitSpec, UnitSpec} from './unit';

export interface RepeatMapping {
/**
Expand All @@ -15,10 +16,19 @@ export interface RepeatMapping {
column?: string[];
}

export interface LayerRepeatMapping extends RepeatMapping {
/**
* An array of fields to be repeated layers.
*/
layer: string[];
}

export type RepeatSpec = NonLayerRepeatSpec | LayerRepeatSpec;

/**
* Base interface for a repeat specification.
*/
export interface RepeatSpec extends BaseSpec, GenericCompositionLayoutWithColumns, ResolveMixins {
export interface NonLayerRepeatSpec extends BaseSpec, GenericCompositionLayoutWithColumns, ResolveMixins {
/**
* Definition for fields to be repeated. One of:
* 1) An array of fields to be repeated. If `"repeat"` is an array, the field can be referred to as `{"repeat": "repeat"}`. The repeated views are laid out in a wrapped row. You can set the number of columns to control the wrapping.
Expand All @@ -32,6 +42,24 @@ export interface RepeatSpec extends BaseSpec, GenericCompositionLayoutWithColumn
spec: GenericSpec<FacetedUnitSpec, LayerSpec, RepeatSpec, FieldName>;
}

export interface LayerRepeatSpec extends BaseSpec, GenericCompositionLayoutWithColumns, ResolveMixins {
/**
* Definition for fields to be repeated. One of:
* 1) An array of fields to be repeated. If `"repeat"` is an array, the field can be referred to as `{"repeat": "repeat"}`. The repeated views are laid out in a wrapped row. You can set the number of columns to control the wrapping.
* 2) An object that maps `"row"` and/or `"column"` to the listed fields to be repeated along the particular orientations. The objects `{"repeat": "row"}` and `{"repeat": "column"}` can be used to refer to the repeated field respectively.
*/
repeat: LayerRepeatMapping;

/**
* A specification of the view that gets repeated.
*/
spec: LayerSpec | UnitSpec;
}

export function isRepeatSpec(spec: BaseSpec): spec is RepeatSpec {
return spec['repeat'] !== undefined;
}

export function isLayerRepeatSpec(spec: RepeatSpec): spec is LayerRepeatSpec {
return !isArray(spec.repeat) && spec.repeat['layer'];
}
28 changes: 28 additions & 0 deletions test/normalize/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@ describe('normalize()', () => {
expect(localLogger.warns[0]).toEqual(log.message.columnsNotSupportByRowCol('repeat'));
})
);

it('normalizes repeat layer', () => {
const spec: TopLevelSpec = {
data: {
url: 'data/movies.json'
},
repeat: {
layer: ['US_Gross', 'Worldwide_Gross']
},
spec: {
mark: 'line',
encoding: {
x: {
bin: true,
field: 'IMDB_Rating',
type: 'quantitative'
},
y: {
aggregate: 'mean',
field: {repeat: 'layer'},
type: 'quantitative'
}
}
}
};
const normalized = normalize(spec);
expect(normalized['layer'].length).toBe(2);
});
});

describe('normalizeFacetedUnit', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/transformextract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe('extractTransforms()', () => {
'repeat_histogram_flights.vl.json',
'repeat_histogram.vl.json',
'repeat_histogram.vl.json',
'repeat_layer.vl.json',
'repeat_child_layer.vl.json',
'repeat_line_weather.vl.json',
'rule_extent.vl.json',
'selection_brush_timeunit.vl.json',
Expand Down

0 comments on commit 556d175

Please sign in to comment.