From 8837838a76c2aa09728fea9ec4b6cd4eaf36139d Mon Sep 17 00:00:00 2001 From: Arvind Satyanarayan Date: Wed, 28 Aug 2019 19:54:52 -0400 Subject: [PATCH] Correct references to flattened fields in selection signals. Fields are only flattened if they participate in encoding rules. Thus, we continue to use bracket notation to access datum values in signal expressions as we cannot be guaranteed that the flattened field will be present. However, the field name stored as part of a SelectionProjection will *always* use dot notation as these names are either returned to us flattened by model.vgField or users explicitly specify them in the input spec (in which case, dot notation is their only recourse). Thus, when referencing the field via a selection's top-level signal, we just directly access the flattened field name, rather than unpacking it via bracket notation. --- src/compile/selection/assemble.ts | 4 +- src/compile/selection/transforms/scales.ts | 6 +- test/compile/selection/scales.test.ts | 93 ++++++++++++++++++++++ 3 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/compile/selection/assemble.ts b/src/compile/selection/assemble.ts index 912162ac985..6be16bcc664 100644 --- a/src/compile/selection/assemble.ts +++ b/src/compile/selection/assemble.ts @@ -5,7 +5,7 @@ import {forEachSelection, MODIFY, SELECTION_DOMAIN, STORE, unitName, VL_SELECTIO import {dateTimeExpr, isDateTime} from '../../datetime'; import {warn} from '../../log'; import {SelectionInit, SelectionInitInterval} from '../../selection'; -import {accessPathWithDatum, keys, varName} from '../../util'; +import {keys, varName} from '../../util'; import {VgData} from '../../vega.schema'; import {FacetModel} from '../facet'; import {LayerModel} from '../layer'; @@ -197,7 +197,7 @@ export function assembleSelectionScaleDomain(model: Model, domainRaw: SignalRef) } } - return {signal: accessPathWithDatum(field, name)}; + return {signal: `${name}[${stringValue(field)}]`}; } return {signal: 'null'}; diff --git a/src/compile/selection/transforms/scales.ts b/src/compile/selection/transforms/scales.ts index e2b5f7dab3c..9e74f0b56ef 100644 --- a/src/compile/selection/transforms/scales.ts +++ b/src/compile/selection/transforms/scales.ts @@ -3,7 +3,7 @@ import {VL_SELECTION_RESOLVE} from '..'; import {Channel, isScaleChannel, X, Y} from '../../../channel'; import * as log from '../../../log'; import {hasContinuousDomain} from '../../../scale'; -import {accessPathWithDatum, varName} from '../../../util'; +import {varName} from '../../../util'; import {UnitModel} from '../../unit'; import {SelectionProjection} from './project'; import {TransformCompiler} from './transforms'; @@ -33,13 +33,13 @@ const scaleBindings: TransformCompiler = { continue; } - scale.set('domainRaw', {signal: accessPathWithDatum(proj.field, name)}, true); + scale.set('domainRaw', {signal: `${name}[${stringValue(proj.field)}]`}, true); bound.push(proj); // Bind both x/y for diag plot of repeated views. if (model.repeater && model.repeater.row === model.repeater.column) { const scale2 = model.getScaleComponent(channel === X ? Y : X); - scale2.set('domainRaw', {signal: accessPathWithDatum(proj.field, name)}, true); + scale2.set('domainRaw', {signal: `${name}[${stringValue(proj.field)}]`}, true); } } }, diff --git a/test/compile/selection/scales.test.ts b/test/compile/selection/scales.test.ts index dca23cea428..7234a38a08d 100644 --- a/test/compile/selection/scales.test.ts +++ b/test/compile/selection/scales.test.ts @@ -6,6 +6,7 @@ import {UnitModel} from '../../../src/compile/unit'; import * as log from '../../../src/log'; import {Domain} from '../../../src/scale'; import {parseConcatModel, parseRepeatModel, parseUnitModelWithScale} from '../../util'; +import {Model} from '../../../src/compile/model'; describe('Selection + Scales', () => { describe('domainRaw', () => { @@ -159,6 +160,98 @@ describe('Selection + Scales', () => { expect('domainRaw' in scales[0]).toBeTruthy(); expect(scales[0].domainRaw.signal).toBe('brush["date"]'); }); + + it('should handle nested field references', () => { + let model: Model = parseUnitModelWithScale({ + selection: { + grid: { + type: 'interval', + bind: 'scales' + } + }, + data: { + values: [{nested: {a: '1', b: 28}}, {nested: {a: '2', b: 55}}, {nested: {a: '3', b: 43}}] + }, + mark: 'point', + encoding: { + y: { + field: 'nested.a', + type: 'quantitative' + }, + x: { + field: 'nested.b', + type: 'quantitative' + } + } + }); + model.parseSelections(); + + let scales = assembleScalesForModel(model); + expect('domainRaw' in scales[0]).toBeTruthy(); + expect(scales[0].domainRaw.signal).toBe('grid["nested.b"]'); + expect('domainRaw' in scales[1]).toBeTruthy(); + expect(scales[1].domainRaw.signal).toBe('grid["nested.a"]'); + + model = parseConcatModel({ + vconcat: [ + { + mark: 'area', + selection: { + brush: {type: 'interval', encodings: ['x']} + }, + encoding: { + x: {field: 'nested.a', type: 'temporal'}, + y: {field: 'price', type: 'quantitative'} + } + }, + { + mark: 'area', + encoding: { + x: { + field: 'date', + type: 'temporal', + scale: {domain: {selection: 'brush', encoding: 'x'}} + }, + y: { + field: 'price', + type: 'quantitative' + } + } + }, + { + mark: 'area', + encoding: { + x: { + field: 'date', + type: 'temporal', + scale: {domain: {selection: 'brush', field: 'nested.a'}} + }, + y: { + field: 'price', + type: 'quantitative' + } + } + } + ], + resolve: { + scale: { + color: 'independent', + opacity: 'independent' + } + } + }); + + model.parseScale(); + model.parseSelections(); + + scales = assembleScalesForModel(model.children[1]); + expect('domainRaw' in scales[0]).toBeTruthy(); + expect(scales[0].domainRaw.signal).toBe('brush["nested.a"]'); + + scales = assembleScalesForModel(model.children[2]); + expect('domainRaw' in scales[0]).toBeTruthy(); + expect(scales[0].domainRaw.signal).toBe('brush["nested.a"]'); + }); }); describe('signals', () => {