diff --git a/build/vega-lite-schema.json b/build/vega-lite-schema.json
index e1c903ea02..7d986c3772 100644
--- a/build/vega-lite-schema.json
+++ b/build/vega-lite-schema.json
@@ -8580,7 +8580,14 @@
"description": "__Required.__ A string defining the name of the field from which to pull a data value or an object defining iterated values from the [`repeat`](https://vega.github.io/vega-lite/docs/repeat.html) operator.\n\n__See also:__ [`field`](https://vega.github.io/vega-lite/docs/field.html) documentation.\n\n__Notes:__ 1) Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`). If field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`). See more details about escaping in the [field documentation](https://vega.github.io/vega-lite/docs/field.html). 2) `field` is not required if `aggregate` is `count`."
},
"header": {
- "$ref": "#/definitions/Header",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/Header"
+ },
+ {
+ "type": "null"
+ }
+ ],
"description": "An object defining properties of a facet's header."
},
"sort": {
@@ -8672,7 +8679,14 @@
"description": "__Required.__ A string defining the name of the field from which to pull a data value or an object defining iterated values from the [`repeat`](https://vega.github.io/vega-lite/docs/repeat.html) operator.\n\n__See also:__ [`field`](https://vega.github.io/vega-lite/docs/field.html) documentation.\n\n__Notes:__ 1) Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`). If field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`). See more details about escaping in the [field documentation](https://vega.github.io/vega-lite/docs/field.html). 2) `field` is not required if `aggregate` is `count`."
},
"header": {
- "$ref": "#/definitions/Header",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/Header"
+ },
+ {
+ "type": "null"
+ }
+ ],
"description": "An object defining properties of a facet's header."
},
"sort": {
@@ -20688,7 +20702,14 @@
"description": "__Required.__ A string defining the name of the field from which to pull a data value or an object defining iterated values from the [`repeat`](https://vega.github.io/vega-lite/docs/repeat.html) operator.\n\n__See also:__ [`field`](https://vega.github.io/vega-lite/docs/field.html) documentation.\n\n__Notes:__ 1) Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`). If field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`). See more details about escaping in the [field documentation](https://vega.github.io/vega-lite/docs/field.html). 2) `field` is not required if `aggregate` is `count`."
},
"header": {
- "$ref": "#/definitions/Header",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/Header"
+ },
+ {
+ "type": "null"
+ }
+ ],
"description": "An object defining properties of a facet's header."
},
"sort": {
diff --git a/examples/compiled/trellis_bar_no_header.png b/examples/compiled/trellis_bar_no_header.png
new file mode 100644
index 0000000000..6ca4a64523
Binary files /dev/null and b/examples/compiled/trellis_bar_no_header.png differ
diff --git a/examples/compiled/trellis_bar_no_header.svg b/examples/compiled/trellis_bar_no_header.svg
new file mode 100644
index 0000000000..43dcbf90a4
--- /dev/null
+++ b/examples/compiled/trellis_bar_no_header.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/compiled/trellis_bar_no_header.vg.json b/examples/compiled/trellis_bar_no_header.vg.json
new file mode 100644
index 0000000000..1996c4b55d
--- /dev/null
+++ b/examples/compiled/trellis_bar_no_header.vg.json
@@ -0,0 +1,173 @@
+{
+ "$schema": "https://vega.github.io/schema/vega/v5.json",
+ "description": "A trellis bar chart showing the US population distribution of age groups and gender in 2000.",
+ "background": "white",
+ "padding": 5,
+ "data": [
+ {
+ "name": "source_0",
+ "url": "data/population.json",
+ "format": {"type": "json"},
+ "transform": [
+ {"type": "filter", "expr": "datum.year == 2000"},
+ {
+ "type": "formula",
+ "expr": "datum.sex == 2 ? 'Female' : 'Male'",
+ "as": "gender"
+ },
+ {
+ "type": "aggregate",
+ "groupby": ["age", "gender"],
+ "ops": ["sum"],
+ "fields": ["people"],
+ "as": ["sum_people"]
+ },
+ {
+ "type": "stack",
+ "groupby": ["age", "gender"],
+ "field": "sum_people",
+ "sort": {"field": ["gender"], "order": ["descending"]},
+ "as": ["sum_people_start", "sum_people_end"],
+ "offset": "zero"
+ },
+ {
+ "type": "filter",
+ "expr": "isValid(datum[\"sum_people\"]) && isFinite(+datum[\"sum_people\"])"
+ }
+ ]
+ },
+ {
+ "name": "row_domain",
+ "source": "source_0",
+ "transform": [{"type": "aggregate", "groupby": ["gender"]}]
+ }
+ ],
+ "signals": [
+ {"name": "x_step", "value": 17},
+ {
+ "name": "child_width",
+ "update": "bandspace(domain('x').length, 0.1, 0.05) * x_step"
+ },
+ {"name": "child_height", "value": 200}
+ ],
+ "layout": {"padding": 20, "columns": 1, "bounds": "full", "align": "all"},
+ "marks": [
+ {
+ "name": "row_header",
+ "type": "group",
+ "role": "row-header",
+ "from": {"data": "row_domain"},
+ "sort": {"field": "datum[\"gender\"]", "order": "ascending"},
+ "encode": {"update": {"height": {"signal": "child_height"}}},
+ "axes": [
+ {
+ "scale": "y",
+ "orient": "left",
+ "grid": false,
+ "title": "population",
+ "labelOverlap": true,
+ "tickCount": {"signal": "ceil(child_height/40)"},
+ "zindex": 0
+ }
+ ]
+ },
+ {
+ "name": "column_footer",
+ "type": "group",
+ "role": "column-footer",
+ "encode": {"update": {"width": {"signal": "child_width"}}},
+ "axes": [
+ {
+ "scale": "x",
+ "orient": "bottom",
+ "grid": false,
+ "title": "age",
+ "labelAlign": "right",
+ "labelAngle": 270,
+ "labelBaseline": "middle",
+ "zindex": 0
+ }
+ ]
+ },
+ {
+ "name": "cell",
+ "type": "group",
+ "style": "cell",
+ "from": {
+ "facet": {"name": "facet", "data": "source_0", "groupby": ["gender"]}
+ },
+ "sort": {"field": ["datum[\"gender\"]"], "order": ["ascending"]},
+ "encode": {
+ "update": {
+ "width": {"signal": "child_width"},
+ "height": {"signal": "child_height"}
+ }
+ },
+ "marks": [
+ {
+ "name": "child_marks",
+ "type": "rect",
+ "style": ["bar"],
+ "from": {"data": "facet"},
+ "encode": {
+ "update": {
+ "fill": {"scale": "color", "field": "gender"},
+ "ariaRoleDescription": {"value": "bar"},
+ "description": {
+ "signal": "\"population: \" + (format(datum[\"sum_people\"], \"\")) + \"; age: \" + (isValid(datum[\"age\"]) ? datum[\"age\"] : \"\"+datum[\"age\"]) + \"; gender: \" + (isValid(datum[\"gender\"]) ? datum[\"gender\"] : \"\"+datum[\"gender\"])"
+ },
+ "x": {"scale": "x", "field": "age"},
+ "width": {"scale": "x", "band": 1},
+ "y": {"scale": "y", "field": "sum_people_end"},
+ "y2": {"scale": "y", "field": "sum_people_start"}
+ }
+ }
+ }
+ ],
+ "axes": [
+ {
+ "scale": "y",
+ "orient": "left",
+ "gridScale": "x",
+ "grid": true,
+ "tickCount": {"signal": "ceil(child_height/40)"},
+ "domain": false,
+ "labels": false,
+ "aria": false,
+ "maxExtent": 0,
+ "minExtent": 0,
+ "ticks": false,
+ "zindex": 0
+ }
+ ]
+ }
+ ],
+ "scales": [
+ {
+ "name": "x",
+ "type": "band",
+ "domain": {"data": "source_0", "field": "age", "sort": true},
+ "range": {"step": {"signal": "x_step"}},
+ "paddingInner": 0.1,
+ "paddingOuter": 0.05
+ },
+ {
+ "name": "y",
+ "type": "linear",
+ "domain": {
+ "data": "source_0",
+ "fields": ["sum_people_start", "sum_people_end"]
+ },
+ "range": [{"signal": "child_height"}, 0],
+ "nice": true,
+ "zero": true
+ },
+ {
+ "name": "color",
+ "type": "ordinal",
+ "domain": {"data": "source_0", "field": "gender", "sort": true},
+ "range": ["#675193", "#ca8861"]
+ }
+ ],
+ "legends": [{"fill": "color", "symbolType": "square", "title": "gender"}]
+}
diff --git a/examples/specs/normalized/trellis_bar_no_header_normalized.vl.json b/examples/specs/normalized/trellis_bar_no_header_normalized.vl.json
new file mode 100644
index 0000000000..e0454e3cb3
--- /dev/null
+++ b/examples/specs/normalized/trellis_bar_no_header_normalized.vl.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://vega.github.io/schema/vega-lite/v4.json",
+ "description": "A trellis bar chart showing the US population distribution of age groups and gender in 2000.",
+ "data": {"url": "data/population.json"},
+ "transform": [
+ {"filter": "datum.year == 2000"},
+ {"calculate": "datum.sex == 2 ? 'Female' : 'Male'", "as": "gender"}
+ ],
+ "facet": {"row": {"field": "gender", "header": null}},
+ "spec": {
+ "width": {"step": 17},
+ "mark": "bar",
+ "encoding": {
+ "y": {"aggregate": "sum", "field": "people", "title": "population"},
+ "x": {"field": "age"},
+ "color": {"field": "gender", "scale": {"range": ["#675193", "#ca8861"]}}
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/specs/trellis_bar_no_header.vl.json b/examples/specs/trellis_bar_no_header.vl.json
new file mode 100644
index 0000000000..4eca493c57
--- /dev/null
+++ b/examples/specs/trellis_bar_no_header.vl.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://vega.github.io/schema/vega-lite/v4.json",
+ "description": "A trellis bar chart showing the US population distribution of age groups and gender in 2000.",
+ "data": { "url": "data/population.json"},
+ "transform": [
+ {"filter": "datum.year == 2000"},
+ {"calculate": "datum.sex == 2 ? 'Female' : 'Male'", "as": "gender"}
+ ],
+ "width": {"step": 17},
+ "mark": "bar",
+ "encoding": {
+ "row": {"field": "gender", "header": null},
+ "y": {
+ "aggregate": "sum", "field": "people",
+ "title": "population"
+ },
+ "x": {"field": "age"},
+ "color": {
+ "field": "gender",
+ "scale": {"range": ["#675193", "#ca8861"]}
+ }
+ }
+}
diff --git a/src/channeldef.ts b/src/channeldef.ts
index d90e40f8cc..8e4b45ffae 100644
--- a/src/channeldef.ts
+++ b/src/channeldef.ts
@@ -1154,16 +1154,18 @@ export function initFieldDef(
if (isFacetFieldDef(fieldDef)) {
const {header} = fieldDef;
- const {orient, ...rest} = header;
- if (orient) {
- return {
- ...fieldDef,
- header: {
- ...rest,
- labelOrient: header.labelOrient || orient,
- titleOrient: header.titleOrient || orient
- }
- };
+ if (header) {
+ const {orient, ...rest} = header;
+ if (orient) {
+ return {
+ ...fieldDef,
+ header: {
+ ...rest,
+ labelOrient: header.labelOrient || orient,
+ titleOrient: header.titleOrient || orient
+ }
+ };
+ }
}
}
diff --git a/src/compile/facet.ts b/src/compile/facet.ts
index a7c9f7d800..f8ba3361a7 100644
--- a/src/compile/facet.ts
+++ b/src/compile/facet.ts
@@ -79,12 +79,13 @@ export class FacetModel extends ModelWithField {
}
private initFacetFieldDef(fieldDef: FacetFieldDef, channel: FacetChannel) {
- const {header, ...rest} = fieldDef;
// Cast because we call initFieldDef, which assumes general FieldDef.
// However, FacetFieldDef is a bit more constrained than the general FieldDef
- const facetFieldDef = initFieldDef(rest, channel) as FacetFieldDef;
- if (header) {
- facetFieldDef.header = replaceExprRef(header);
+ const facetFieldDef = initFieldDef(fieldDef, channel) as FacetFieldDef;
+ if (facetFieldDef.header) {
+ facetFieldDef.header = replaceExprRef(facetFieldDef.header);
+ } else if (facetFieldDef.header === null) {
+ facetFieldDef.header = null;
}
return facetFieldDef;
}
diff --git a/src/compile/header/parse.ts b/src/compile/header/parse.ts
index b01302e72c..b54ebb83f5 100644
--- a/src/compile/header/parse.ts
+++ b/src/compile/header/parse.ts
@@ -46,14 +46,14 @@ function parseFacetHeader(model: FacetModel, channel: FacetChannel) {
child.component.layoutHeaders[channel].title = null;
}
- const labelOrient = getHeaderProperty('labelOrient', fieldDef, config, channel);
+ const labelOrient = getHeaderProperty('labelOrient', fieldDef.header, config, channel);
- const header = fieldDef.header ?? {};
- const labels = getFirstDefined(header.labels, config.header.labels, true);
+ const labels =
+ fieldDef.header !== null ? getFirstDefined(fieldDef.header?.labels, config.header.labels, true) : false;
const headerType = contains(['bottom', 'right'], labelOrient) ? 'footer' : 'header';
component.layoutHeaders[channel] = {
- title,
+ title: fieldDef.header !== null ? title : null,
facetFieldDef: fieldDef,
[headerType]: channel === 'facet' ? [] : [makeHeaderComponent(model, channel, labels)]
};
diff --git a/src/spec/facet.ts b/src/spec/facet.ts
index f9af78db43..807bf0b541 100644
--- a/src/spec/facet.ts
+++ b/src/spec/facet.ts
@@ -14,7 +14,7 @@ export interface FacetFieldDef;
+ header?: Header | null;
// Note: `"sort"` for facet field def is different from encoding field def as it does not support `SortByEncoding`
diff --git a/test/compile/facet.test.ts b/test/compile/facet.test.ts
index d53f234db3..334559fb9c 100644
--- a/test/compile/facet.test.ts
+++ b/test/compile/facet.test.ts
@@ -43,6 +43,36 @@ describe('FacetModel', () => {
expect(localLogger.warns[0]).toEqual(log.message.facetChannelShouldBeDiscrete(ROW));
})
);
+
+ it('converts orient to titleOrient and labelOrient', () => {
+ const model = parseFacetModel({
+ facet: {
+ row: {field: 'a', type: 'nominal', header: {orient: 'right'}}
+ },
+ spec: {
+ mark: 'point',
+ encoding: {}
+ }
+ });
+ expect(model.facet).toEqual({
+ row: {field: 'a', type: 'nominal', header: {titleOrient: 'right', labelOrient: 'right'}}
+ });
+ });
+
+ it('keeps header: null', () => {
+ const model = parseFacetModel({
+ facet: {
+ row: {field: 'a', type: 'nominal', header: null}
+ },
+ spec: {
+ mark: 'point',
+ encoding: {}
+ }
+ });
+ expect(model.facet).toEqual({
+ row: {field: 'a', type: 'nominal', header: null}
+ });
+ });
});
describe('parseAxisAndHeader', () => {