Skip to content
Draft
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/compiled/arc_quantitative_stacked_theta.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 76 additions & 0 deletions examples/compiled/arc_quantitative_stacked_theta.vg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"background": "white",
"padding": 5,
"width": 200,
"height": 200,
"style": "view",
"data": [
{
"name": "source_0",
"values": [
{"segment": 1, "value": 8},
{"segment": 2, "value": 7},
{"segment": 3, "value": 6}
]
},
{
"name": "data_0",
"source": "source_0",
"transform": [
{
"type": "stack",
"groupby": ["value"],
"field": "segment",
"sort": {"field": [], "order": []},
"as": ["segment_start", "segment_end"],
"offset": "zero"
},
{
"type": "filter",
"expr": "isValid(datum[\"segment\"]) && isFinite(+datum[\"segment\"]) && isValid(datum[\"value\"]) && isFinite(+datum[\"value\"])"
}
]
}
],
"marks": [
{
"name": "marks",
"type": "arc",
"style": ["arc"],
"from": {"data": "data_0"},
"encode": {
"update": {
"opacity": {"value": 0.5},
"stroke": {"value": "#fff"},
"innerRadius": {"value": 20},
"fill": {"value": "#4c78a8"},
"description": {
"signal": "\"segment: \" + (format(datum[\"segment\"], \"\")) + \"; value: \" + (format(datum[\"value\"], \"\"))"
},
"x": {"signal": "width", "mult": 0.5},
"y": {"signal": "height", "mult": 0.5},
"outerRadius": {"scale": "radius", "field": "value"},
"startAngle": {"scale": "theta", "field": "segment_end"},
"endAngle": {"scale": "theta", "field": "segment_start"}
}
}
}
],
"scales": [
{
"name": "theta",
"type": "linear",
"domain": {"data": "data_0", "fields": ["segment_start", "segment_end"]},
"range": [0, 6.283185307179586],
"zero": true
},
{
"name": "radius",
"type": "sqrt",
"domain": {"data": "data_0", "field": "value"},
"range": [20, {"signal": "min(width,height)/2"}],
"zero": true
}
]
}
40 changes: 40 additions & 0 deletions examples/specs/arc_quantitative_stacked_theta.vl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {
"values": [
{
"segment": 1,
"value": 8
},
{
"segment": 2,
"value": 7
},
{
"segment": 3,
"value": 6
}
]
},
"mark": {
"type": "arc",
"innerRadius": 20,
"stroke": "#fff",
"opacity": 0.5
},
"encoding": {
"theta": {
"field": "segment",
"type": "quantitative",
"stack": true
},
"radius": {
"field": "value",
"scale": {
"type": "sqrt",
"zero": true,
"rangeMin": 20
}
}
}
}
23 changes: 23 additions & 0 deletions src/channeldef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,29 @@ export function vgField(
}
}

/**
* Return true if fieldDefs are equal in field name and all "fn" opt.nofn related properties
* Different from checking for full field def equivalence, since some (like axis) don't matter for positioning purposes.
*/
export function areFieldDefsWithInlineTransformsEquivalent(
fieldDef1: FieldDef<string> | WindowFieldDef | AggregatedFieldDef,
fieldDef2: FieldDef<string> | WindowFieldDef | AggregatedFieldDef,
) {
if (isOpFieldDef(fieldDef1) || isOpFieldDef(fieldDef2)) {
return isOpFieldDef(fieldDef1) && isOpFieldDef(fieldDef2) && fieldDef1.op === fieldDef2.op;
}

const {field: field1, aggregate: aggregate1, bin: bin1, timeUnit: timeUnit1} = fieldDef1;
const {field: field2, aggregate: aggregate2, bin: bin2, timeUnit: timeUnit2} = fieldDef2;

return (
field1 === field2 &&
JSON.stringify(aggregate1) === JSON.stringify(aggregate2) &&
JSON.stringify(bin1) === JSON.stringify(bin2) &&
timeUnit1 === timeUnit2
);
}

export function isDiscrete(def: TypedFieldDef<Field> | DatumDef<any, any>) {
switch (def.type) {
case 'nominal':
Expand Down
19 changes: 16 additions & 3 deletions src/stack.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {array, hasOwnProperty, isBoolean} from 'vega-util';
import {Aggregate, SUM_OPS} from './aggregate.js';
import {getSecondaryRangeChannel, NonPositionChannel, NONPOSITION_CHANNELS} from './channel.js';
import {
getSecondaryRangeChannel,
NonPositionChannel,
NONPOSITION_CHANNELS,
// isPolarPositionChannel
} from './channel.js';
import {
channelDefType,
FieldName,
Expand All @@ -12,6 +17,7 @@ import {
PositionFieldDef,
TypedFieldDef,
vgField,
areFieldDefsWithInlineTransformsEquivalent,
} from './channeldef.js';
import {CompositeAggregate} from './compositemark/index.js';
import {channelHasField, Encoding, isAggregate} from './encoding.js';
Expand Down Expand Up @@ -176,8 +182,16 @@ export function stack(m: Mark | MarkDef, encoding: Encoding<string>): StackPrope
if (encoding[dimensionChannel]) {
const dimensionDef = encoding[dimensionChannel];
const dimensionField = isFieldDef(dimensionDef) ? vgField(dimensionDef, {}) : undefined;
// const hasSameDimensionAndStackedField = dimensionField && dimensionField === stackedField;
const areDefsEquivalent =
isFieldDef(dimensionDef) && areFieldDefsWithInlineTransformsEquivalent(dimensionDef, stackedFieldDef);

// For polar coordinates, do not set a groupBy when working with quantitative fields.
// const isPolar = isPolarPositionChannel(fieldChannel) || isPolarPositionChannel(dimensionChannel);
// const shouldAddPolarGroupBy = !isUnbinnedQuantitative(dimensionDef);

if (dimensionField && dimensionField !== stackedField) {
if (!areDefsEquivalent) {
// if (isPolar ? shouldAddPolarGroupBy : !hasSameDimensionAndStackedField) {
// avoid grouping by the stacked field
groupbyChannels.push(dimensionChannel);
groupbyFields.add(dimensionField);
Expand Down Expand Up @@ -237,7 +251,6 @@ export function stack(m: Mark | MarkDef, encoding: Encoding<string>): StackPrope
if (!offset || !isStackOffset(offset)) {
return null;
}

if (isAggregate(encoding) && stackBy.length === 0) {
return null;
}
Expand Down
23 changes: 22 additions & 1 deletion test/stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@
expect(_stack.stackBy[0].channel).toBe('color');
});

it('should be correct for radial chart', () => {
it('should be correct for radial chart with same radius and theta quantitative fields', () => {
const spec: TopLevel<NormalizedUnitSpec> = {
data: {url: 'data/barley.json'},
mark: 'arc',
Expand All @@ -463,6 +463,27 @@
};
const _stack = stack(spec.mark, spec.encoding);
expect(_stack.fieldChannel).toBe('theta');
expect(_stack.groupbyChannels).toHaveLength(0);
expect(_stack.groupbyFields.size).toBe(0);
});

it('should be correct for radial chart with different radius and theta quantitative fields', () => {
const spec: TopLevel<NormalizedUnitSpec> = {
data: {url: 'data/barley.json'},
mark: 'arc',
encoding: {
theta: {field: 'year', type: 'quantitative', stack: true},
radius: {
field: 'variety',
type: 'quantitative',
scale: {type: 'sqrt', zero: true, range: [20, 100]},
},
},
};
const _stack = stack(spec.mark, spec.encoding);
expect(_stack.fieldChannel).toBe('theta');
expect(_stack.groupbyChannels).toHaveLength(0);

Check failure on line 485 in test/stack.test.ts

View workflow job for this annotation

GitHub Actions / Node

test/stack.test.ts > stack > stack().groupbyChannels, .fieldChannels > should be correct for radial chart with different radius and theta quantitative fields

AssertionError: expected [ 'radius' ] to have a length of +0 but got 1 - Expected + Received - 0 + 1 ❯ test/stack.test.ts:485:38

Check failure on line 485 in test/stack.test.ts

View workflow job for this annotation

GitHub Actions / Runtime, Linting, and Coverage

test/stack.test.ts > stack > stack().groupbyChannels, .fieldChannels > should be correct for radial chart with different radius and theta quantitative fields

AssertionError: expected [ 'radius' ] to have a length of +0 but got 1 - Expected + Received - 0 + 1 ❯ test/stack.test.ts:485:38
expect(_stack.groupbyFields.size).toBe(0);
});
});

Expand Down
Loading