diff --git a/examples/compiled/arc_facet.png b/examples/compiled/arc_facet.png
new file mode 100644
index 0000000000..8bdefdc831
Binary files /dev/null and b/examples/compiled/arc_facet.png differ
diff --git a/examples/compiled/arc_facet.svg b/examples/compiled/arc_facet.svg
new file mode 100644
index 0000000000..2627fb5bde
--- /dev/null
+++ b/examples/compiled/arc_facet.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/compiled/arc_facet.vg.json b/examples/compiled/arc_facet.vg.json
new file mode 100644
index 0000000000..358c80290f
--- /dev/null
+++ b/examples/compiled/arc_facet.vg.json
@@ -0,0 +1,132 @@
+{
+ "$schema": "https://vega.github.io/schema/vega/v5.json",
+ "background": "white",
+ "padding": 5,
+ "data": [
+ {
+ "name": "source_0",
+ "url": "data/barley.json",
+ "format": {"type": "json"},
+ "transform": [
+ {
+ "type": "aggregate",
+ "groupby": ["site", "year"],
+ "ops": ["sum"],
+ "fields": ["yield"],
+ "as": ["sum_yield"]
+ },
+ {
+ "type": "stack",
+ "groupby": ["year"],
+ "field": "sum_yield",
+ "sort": {"field": ["site"], "order": ["ascending"]},
+ "as": ["sum_yield_start", "sum_yield_end"],
+ "offset": "zero"
+ },
+ {
+ "type": "filter",
+ "expr": "isValid(datum[\"sum_yield\"]) && isFinite(+datum[\"sum_yield\"])"
+ }
+ ]
+ },
+ {
+ "name": "column_domain",
+ "source": "source_0",
+ "transform": [{"type": "aggregate", "groupby": ["year"]}]
+ }
+ ],
+ "signals": [
+ {"name": "child_width", "value": 200},
+ {"name": "child_height", "value": 200}
+ ],
+ "layout": {
+ "padding": 20,
+ "offset": {"columnTitle": 10},
+ "columns": {"signal": "length(data('column_domain'))"},
+ "bounds": "full",
+ "align": "all"
+ },
+ "marks": [
+ {
+ "name": "column-title",
+ "type": "group",
+ "role": "column-title",
+ "title": {"text": "year", "style": "guide-title", "offset": 10}
+ },
+ {
+ "name": "column_header",
+ "type": "group",
+ "role": "column-header",
+ "from": {"data": "column_domain"},
+ "sort": {"field": "datum[\"year\"]", "order": "ascending"},
+ "title": {
+ "text": {
+ "signal": "isValid(parent[\"year\"]) ? parent[\"year\"] : \"\"+parent[\"year\"]"
+ },
+ "style": "guide-label",
+ "frame": "group",
+ "offset": 10
+ },
+ "encode": {"update": {"width": {"signal": "child_width"}}}
+ },
+ {
+ "name": "cell",
+ "type": "group",
+ "style": "cell",
+ "from": {
+ "facet": {"name": "facet", "data": "source_0", "groupby": ["year"]}
+ },
+ "sort": {"field": ["datum[\"year\"]"], "order": ["ascending"]},
+ "encode": {
+ "update": {
+ "width": {"signal": "child_width"},
+ "height": {"signal": "child_height"},
+ "stroke": {"value": null}
+ }
+ },
+ "marks": [
+ {
+ "name": "child_marks",
+ "type": "arc",
+ "style": ["arc"],
+ "from": {"data": "facet"},
+ "encode": {
+ "update": {
+ "fill": {"scale": "color", "field": "site"},
+ "description": {
+ "signal": "\"Sum of yield: \" + (format(datum[\"sum_yield\"], \"\")) + \"; site: \" + (isValid(datum[\"site\"]) ? datum[\"site\"] : \"\"+datum[\"site\"])"
+ },
+ "x": {"signal": "child_width", "mult": 0.5},
+ "y": {"signal": "child_height", "mult": 0.5},
+ "outerRadius": {"signal": "min(child_width,child_height)/2"},
+ "innerRadius": {"value": 0},
+ "startAngle": {"scale": "child_theta", "field": "sum_yield_end"},
+ "endAngle": {"scale": "child_theta", "field": "sum_yield_start"}
+ }
+ }
+ }
+ ],
+ "scales": [
+ {
+ "name": "child_theta",
+ "type": "linear",
+ "domain": {
+ "data": "facet",
+ "fields": ["sum_yield_start", "sum_yield_end"]
+ },
+ "range": [0, 6.283185307179586],
+ "zero": true
+ }
+ ]
+ }
+ ],
+ "scales": [
+ {
+ "name": "color",
+ "type": "ordinal",
+ "domain": {"data": "source_0", "field": "site", "sort": true},
+ "range": "category"
+ }
+ ],
+ "legends": [{"fill": "color", "symbolType": "circle", "title": "site"}]
+}
diff --git a/examples/compiled/arc_params.vg.json b/examples/compiled/arc_params.vg.json
index 4bd4143459..ae235dc7d8 100644
--- a/examples/compiled/arc_params.vg.json
+++ b/examples/compiled/arc_params.vg.json
@@ -142,8 +142,8 @@
"y": {"signal": "height", "mult": 0.5},
"outerRadius": {"signal": "radius"},
"innerRadius": {"signal": "radius2"},
- "startAngle": {"scale": "theta", "field": "value_end"},
- "endAngle": {"scale": "theta", "field": "value_start"}
+ "startAngle": {"scale": "concat_1_theta", "field": "value_end"},
+ "endAngle": {"scale": "concat_1_theta", "field": "value_start"}
}
}
}
@@ -151,18 +151,18 @@
}
],
"scales": [
- {
- "name": "theta",
- "type": "linear",
- "domain": {"data": "data_0", "fields": ["value_start", "value_end"]},
- "range": [0, 6.283185307179586],
- "zero": true
- },
{
"name": "color",
"type": "ordinal",
"domain": {"data": "data_0", "field": "category", "sort": true},
"range": "category"
+ },
+ {
+ "name": "concat_1_theta",
+ "type": "linear",
+ "domain": {"data": "data_0", "fields": ["value_start", "value_end"]},
+ "range": [0, 6.283185307179586],
+ "zero": true
}
],
"legends": [{"fill": "color", "symbolType": "circle", "title": "category"}],
diff --git a/examples/specs/arc_facet.vl.json b/examples/specs/arc_facet.vl.json
new file mode 100644
index 0000000000..d2f71eb7a2
--- /dev/null
+++ b/examples/specs/arc_facet.vl.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
+ "data": {"url": "data/barley.json"},
+ "mark": "arc",
+ "encoding": {
+ "column": {"field": "year"},
+ "theta": {"field": "yield", "type": "quantitative", "aggregate": "sum"},
+ "color": {"field": "site", "type": "nominal"}
+ },
+ "view": {
+ "stroke": null
+ }
+}
diff --git a/examples/specs/normalized/arc_facet_normalized.vl.json b/examples/specs/normalized/arc_facet_normalized.vl.json
new file mode 100644
index 0000000000..ae6f196738
--- /dev/null
+++ b/examples/specs/normalized/arc_facet_normalized.vl.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
+ "data": {"url": "data/barley.json"},
+ "facet": {"column": {"field": "year"}},
+ "spec": {
+ "view": {"stroke": null},
+ "mark": "arc",
+ "encoding": {
+ "theta": {"field": "yield", "type": "quantitative", "aggregate": "sum"},
+ "color": {"field": "site", "type": "nominal"}
+ }
+ }
+}
\ No newline at end of file
diff --git a/site/docs/mark/arc.md b/site/docs/mark/arc.md
index 582cf305bf..54017f28e3 100644
--- a/site/docs/mark/arc.md
+++ b/site/docs/mark/arc.md
@@ -83,3 +83,9 @@ You can also add a text layer to add labels to a pie chart.
The `arc` property of the top-level [`config`](config.html) object sets the default properties for all arc marks. If [mark property encoding channels](encoding.html#mark-prop) are specified for marks, these config values will be overridden.
The arc config can contain any [arc mark properties](#properties) (except `type`, `style`, and `clip`).
+
+## Faceted Pie Charts
+
+By default, the theta channel in faceted charts [resolves](resolve.html) to independent scales so that the ratios are comparable.
+
+
diff --git a/src/compile/resolve.ts b/src/compile/resolve.ts
index 23de0f1c39..a45e8460f6 100644
--- a/src/compile/resolve.ts
+++ b/src/compile/resolve.ts
@@ -4,10 +4,12 @@ import {Resolve, ResolveMode} from '../resolve';
import {isConcatModel, isFacetModel, isLayerModel, Model} from './model';
export function defaultScaleResolve(channel: ScaleChannel, model: Model): ResolveMode {
- if (isLayerModel(model) || isFacetModel(model)) {
+ if (isFacetModel(model)) {
+ return channel === 'theta' ? 'independent' : 'shared';
+ } else if (isLayerModel(model)) {
return 'shared';
} else if (isConcatModel(model)) {
- return isXorY(channel) ? 'independent' : 'shared';
+ return isXorY(channel) || channel === 'theta' || channel === 'radius' ? 'independent' : 'shared';
}
/* istanbul ignore next: should never reach here. */
throw new Error('invalid model type for resolve');
diff --git a/test/compile/resolve.test.ts b/test/compile/resolve.test.ts
index f91278273c..24e2d6211a 100644
--- a/test/compile/resolve.test.ts
+++ b/test/compile/resolve.test.ts
@@ -9,6 +9,10 @@ describe('compile/resolve', () => {
layer: []
});
expect(defaultScaleResolve('x', model)).toBe('shared');
+ expect(defaultScaleResolve('y', model)).toBe('shared');
+ expect(defaultScaleResolve('color', model)).toBe('shared');
+ expect(defaultScaleResolve('theta', model)).toBe('shared');
+ expect(defaultScaleResolve('radius', model)).toBe('shared');
});
it('shares scales for facet model by default.', () => {
@@ -19,23 +23,39 @@ describe('compile/resolve', () => {
spec: {mark: 'point', encoding: {}}
});
expect(defaultScaleResolve('x', model)).toBe('shared');
+ expect(defaultScaleResolve('y', model)).toBe('shared');
+ expect(defaultScaleResolve('color', model)).toBe('shared');
+ expect(defaultScaleResolve('radius', model)).toBe('shared');
+ });
+
+ it('separates theta scales for facet model by default.', () => {
+ const model = parseFacetModel({
+ facet: {
+ row: {field: 'a', type: 'nominal'}
+ },
+ spec: {mark: 'arc', encoding: {}}
+ });
+ expect(defaultScaleResolve('theta', model)).toBe('independent');
});
- it('separates xy scales for concat model by default.', () => {
+ it('separates x, y, theta, and radius scales for concat model by default.', () => {
const model = parseConcatModel({
hconcat: []
});
expect(defaultScaleResolve('x', model)).toBe('independent');
+ expect(defaultScaleResolve('y', model)).toBe('independent');
+ expect(defaultScaleResolve('theta', model)).toBe('independent');
+ expect(defaultScaleResolve('radius', model)).toBe('independent');
});
- it('shares non-xy scales for concat model by default.', () => {
+ it('shares non-positional scales for concat model by default.', () => {
const model = parseConcatModel({
hconcat: []
});
expect(defaultScaleResolve('color', model)).toBe('shared');
});
- it('separates xy scales for repeat model by default.', () => {
+ it('separates x, y, theta, and radius scales for repeat model by default.', () => {
const model = parseModel({
repeat: {
row: ['a', 'b']
@@ -49,9 +69,12 @@ describe('compile/resolve', () => {
}
});
expect(defaultScaleResolve('x', model)).toBe('independent');
+ expect(defaultScaleResolve('y', model)).toBe('independent');
+ expect(defaultScaleResolve('theta', model)).toBe('independent');
+ expect(defaultScaleResolve('radius', model)).toBe('independent');
});
- it('shares non-xy scales for repeat model by default.', () => {
+ it('shares non-positional scales for repeat model by default.', () => {
const model = parseModel({
repeat: {
row: ['a', 'b']