diff --git a/src/compile/data/index.ts b/src/compile/data/index.ts index 61e9a238c5..68c62d7598 100644 --- a/src/compile/data/index.ts +++ b/src/compile/data/index.ts @@ -35,6 +35,13 @@ export interface DataComponent { */ facetRoot?: FacetNode; + /** + * True if the data for this model is faceted. + * A dataset is faceted if a parent model is a facet and no new dataset is + * defined (which would make the data unfaceted again). + */ + isFaceted: boolean; + /** * Parse properties passed down from ancestors. */ diff --git a/src/compile/model.ts b/src/compile/model.ts index 4adc191f01..f873188fd9 100644 --- a/src/compile/model.ts +++ b/src/compile/model.ts @@ -8,7 +8,7 @@ import {ChannelDef, FieldDef, FieldRefOption, getFieldDef, vgField} from '../fie import * as log from '../log'; import {Resolve} from '../resolve'; import {hasDiscreteDomain} from '../scale'; -import {BaseSpec} from '../spec'; +import {BaseSpec, isFacetSpec} from '../spec'; import {extractTitleConfig, TitleParams} from '../title'; import {normalizeTransform, Transform} from '../transform'; import {contains, Dict, keys, varName} from '../util'; @@ -27,7 +27,7 @@ import { import {assembleAxes} from './axis/assemble'; import {AxisComponentIndex} from './axis/component'; import {ConcatModel} from './concat'; -import {DataComponent} from './data/index'; +import {DataComponent} from './data'; import {FacetModel} from './facet'; import {LayerModel} from './layer'; import {getHeaderGroups, getTitleGroup, HEADER_CHANNELS, LayoutHeaderComponent} from './layout/header'; @@ -186,7 +186,9 @@ export abstract class Model { sources: parent ? parent.component.data.sources : {}, outputNodes: parent ? parent.component.data.outputNodes : {}, outputNodeRefCounts: parent ? parent.component.data.outputNodeRefCounts : {}, - ancestorParse: parent ? {...parent.component.data.ancestorParse} : {} + ancestorParse: parent ? {...parent.component.data.ancestorParse} : {}, + // data is faceted if the spec is a facet spec or the parent has faceted data and no data is defined + isFaceted: isFacetSpec(spec) || (parent && parent.component.data.isFaceted && !spec.data) }, layoutSize: new Split(), layoutHeaders:{row: {}, column: {}}, diff --git a/src/compile/scale/domain.ts b/src/compile/scale/domain.ts index 504206a47e..baaf90570c 100644 --- a/src/compile/scale/domain.ts +++ b/src/compile/scale/domain.ts @@ -5,9 +5,11 @@ import {MAIN, RAW} from '../../data'; import {DateTime, dateTimeExpr, isDateTime} from '../../datetime'; import {FieldDef} from '../../fielddef'; import * as log from '../../log'; +import {ResolveMode} from '../../resolve'; import {Domain, hasDiscreteDomain, isBinScale, isSelectionDomain, ScaleConfig, ScaleType} from '../../scale'; import {isSortField, SortField} from '../../sort'; import * as util from '../../util'; +import {isDataRefUnionedDomain, isFieldRefUnionDomain} from '../../vega.schema'; import { isDataRefDomain, VgDataRef, @@ -15,9 +17,8 @@ import { VgFieldRefUnionDomain, VgNonUnionDomain, VgSortField, - VgUnionSortField + VgUnionSortField, } from '../../vega.schema'; -import {isDataRefUnionedDomain, isFieldRefUnionDomain} from '../../vega.schema'; import {binRequiresRange} from '../common'; import {FACET_SCALE_PREFIX} from '../data/optimize'; import {isFacetModel, isUnitModel, Model} from '../model'; @@ -57,6 +58,26 @@ function parseUnitScaleDomain(model: UnitModel) { signal: SELECTION_DOMAIN + JSON.stringify(specifiedDomain) }, true); } + + if (model.component.data.isFaceted) { + // get resolve from closest facet parent as this decides whether we need to refer to cloned subtree or not + let facetParent: Model = model; + while (!isFacetModel(facetParent) && facetParent.parent) { + facetParent = facetParent.parent; + } + + const resolve = facetParent.component.resolve.scale[channel]; + + if (resolve === 'shared') { + for (const domain of domains) { + // Replace the scale domain with data output from a cloned subtree after the facet. + if (isDataRefDomain(domain)) { + // use data from cloned subtree (which is the same as data but with a prefix added once) + domain.data = FACET_SCALE_PREFIX + domain.data.replace(FACET_SCALE_PREFIX, ''); + } + } + } + } }); } @@ -83,16 +104,6 @@ function parseNonUnitScaleDomain(model: Model) { } } - if (isFacetModel(model)) { - domains.forEach((domain) => { - // Replace the scale domain with data output from a cloned subtree after the facet. - if (isDataRefDomain(domain)) { - // use data from cloned subtree (which is the same as data but with a prefix added once) - domain.data = FACET_SCALE_PREFIX + domain.data.replace(FACET_SCALE_PREFIX, ''); - } - }); - } - localScaleComponents[channel].domains = domains; }); } diff --git a/src/compile/split.ts b/src/compile/split.ts index abd11e5575..69be98ee67 100644 --- a/src/compile/split.ts +++ b/src/compile/split.ts @@ -1,6 +1,6 @@ import * as log from '../log'; -import {duplicate} from '../util'; +import {duplicate, hash} from '../util'; /** * Generic class for storing properties that are explicitly specified @@ -101,7 +101,7 @@ export function tieBreakByComparing(compare: (v1: T, v2: T) => number) { export function defaultTieBreaker(v1: Explicit, v2: Explicit, property: keyof S, propertyOf: string) { if (v1.explicit && v2.explicit) { - log.warn(log.message.mergeConflictingProperty(property, propertyOf, v1.value, v2.value)); + log.warn(log.message.mergeConflictingProperty(property, propertyOf, hash(v1.value), hash(v2.value))); } // If equal score, prefer v1. return v1; diff --git a/src/spec.ts b/src/spec.ts index ab0964f070..f6aa29b8da 100644 --- a/src/spec.ts +++ b/src/spec.ts @@ -208,31 +208,31 @@ export type TopLevelExtendedSpec = TopLevel | TopLevel /* Custom type guards */ -export function isFacetSpec(spec: GenericSpec>): spec is GenericFacetSpec> { +export function isFacetSpec(spec: BaseSpec): spec is GenericFacetSpec> { return spec['facet'] !== undefined; } -export function isUnitSpec(spec: GenericSpec>): spec is FacetedCompositeUnitSpec | UnitSpec { +export function isUnitSpec(spec: BaseSpec): spec is FacetedCompositeUnitSpec | UnitSpec { return !!spec['mark']; } -export function isLayerSpec(spec: GenericSpec>): spec is GenericLayerSpec> { +export function isLayerSpec(spec: BaseSpec): spec is GenericLayerSpec> { return spec['layer'] !== undefined; } -export function isRepeatSpec(spec: GenericSpec>): spec is GenericRepeatSpec> { +export function isRepeatSpec(spec: BaseSpec): spec is GenericRepeatSpec> { return spec['repeat'] !== undefined; } -export function isConcatSpec(spec: GenericSpec>): spec is GenericVConcatSpec> | GenericHConcatSpec> { +export function isConcatSpec(spec: BaseSpec): spec is GenericVConcatSpec> | GenericHConcatSpec> { return isVConcatSpec(spec) || isHConcatSpec(spec); } -export function isVConcatSpec(spec: GenericSpec>): spec is GenericVConcatSpec> { +export function isVConcatSpec(spec: BaseSpec): spec is GenericVConcatSpec> { return spec['vconcat'] !== undefined; } -export function isHConcatSpec(spec: GenericSpec>): spec is GenericHConcatSpec> { +export function isHConcatSpec(spec: BaseSpec): spec is GenericHConcatSpec> { return spec['hconcat'] !== undefined; } diff --git a/test/compile/data/assemble.test.ts b/test/compile/data/assemble.test.ts index 001dc57c24..04fb2065c6 100644 --- a/test/compile/data/assemble.test.ts +++ b/test/compile/data/assemble.test.ts @@ -21,7 +21,8 @@ describe('compile/data/assemble', () => { sources: {named: src}, outputNodes: {out: main}, outputNodeRefCounts, - ancestorParse: {} + ancestorParse: {}, + isFaceted: false }); assert.equal(data.length, 1); @@ -45,7 +46,8 @@ describe('compile/data/assemble', () => { sources: {named: src}, outputNodes: {out: main}, outputNodeRefCounts, - ancestorParse: {} + ancestorParse: {}, + isFaceted: false }); assert.deepEqual(data, [{ diff --git a/test/compile/scale/parse.test.ts b/test/compile/scale/parse.test.ts index dd5aba3e89..3caf5d31b1 100644 --- a/test/compile/scale/parse.test.ts +++ b/test/compile/scale/parse.test.ts @@ -1,12 +1,13 @@ /* tslint:disable:quotemark */ import {assert} from 'chai'; + import {parseScaleCore} from '../../../src/compile/scale/parse'; import {SELECTION_DOMAIN} from '../../../src/compile/selection/selection'; import * as log from '../../../src/log'; import {NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTIES, SCALE_PROPERTIES} from '../../../src/scale'; import {toSet, without} from '../../../src/util'; -import {parseModel, parseUnitModelWithScale} from '../../util'; +import {parseModel, parseModelWithScale, parseUnitModelWithScale} from '../../util'; describe('src/compile', function() { it('NON_TYPE_RANGE_SCALE_PROPERTIES should be SCALE_PROPERTIES wihtout type, domain, and range properties', () => { @@ -352,6 +353,7 @@ describe('src/compile', function() { const xScale = model.getScaleComponent('x'); const yscale = model.getScaleComponent('y'); + it('should add a raw selection domain', function() { assert.property(xScale.explicit, 'domainRaw'); assert.propertyVal(xScale.explicit.domainRaw, 'signal', @@ -363,4 +365,71 @@ describe('src/compile', function() { }); }); }); + + describe('parseScaleDomain', function() { + describe('faceted domains', function() { + it('should use cloned subtree', function() { + const model = parseModelWithScale({ + facet: { + row: {field: "symbol", type: "nominal"} + }, + spec: { + mark: 'point', + encoding: { + x: {field: 'a', type: 'quantitative'}, + } + } + }); + + assert.deepEqual(model.component.scales.x.domains, [{ + data: 'scale_child_main', + field: 'a' + }]); + }); + + it('should not use cloned subtree if the data is not faceted', function() { + const model = parseModelWithScale({ + facet: { + row: {field: "symbol", type: "nominal"} + }, + spec: { + data: {url: 'foo'}, + mark: 'point', + encoding: { + x: {field: 'a', type: 'quantitative'}, + } + } + }); + + assert.deepEqual(model.component.scales.x.domains, [{ + data: 'child_main', + field: 'a' + }]); + }); + + it('should not use cloned subtree if the scale is independent', function() { + const model = parseModelWithScale({ + facet: { + row: {field: "symbol", type: "nominal"} + }, + spec: { + mark: 'point', + encoding: { + x: {field: 'a', type: 'quantitative'}, + } + }, + resolve: { + scale: { + x: 'independent' + } + } + }); + + assert.deepEqual(model.children[0].component.scales.x.domains, [{ + data: 'child_main', + field: 'a' + }]); + }); + }); + }); });