diff --git a/_data/link.yml b/_data/link.yml index 3bde3fc5cd..5bc40b991e 100644 --- a/_data/link.yml +++ b/_data/link.yml @@ -42,9 +42,8 @@ Bin: link: bin.html BinParams: link: "bin.html#bin-parameters" -FilterOperand: - name: Filter - link: "transform.html#filter" +"LogicalOperand": + link: "transform.html#predicate" AggregateOpDef: link: "transform.html#aggregate-op-def" TimeUnit: diff --git a/build/vega-lite-schema.json b/build/vega-lite-schema.json index b28ff8edc9..ed44bc6d7e 100644 --- a/build/vega-lite-schema.json +++ b/build/vega-lite-schema.json @@ -1641,41 +1641,6 @@ }, "type": "object" }, - "EqualFilter": { - "additionalProperties": false, - "properties": { - "equal": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "$ref": "#/definitions/DateTime" - } - ], - "description": "The value that the field should be equal to." - }, - "field": { - "description": "Field to be filtered.", - "type": "string" - }, - "timeUnit": { - "$ref": "#/definitions/TimeUnit", - "description": "Time unit for the field to be filtered." - } - }, - "required": [ - "field", - "equal" - ], - "type": "object" - }, "FacetFieldDef": { "additionalProperties": false, "properties": { @@ -1992,31 +1957,128 @@ ], "type": "object" }, - "Filter": { - "anyOf": [ - { - "$ref": "#/definitions/EqualFilter" + "FieldEqualPredicate": { + "additionalProperties": false, + "properties": { + "equal": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "$ref": "#/definitions/DateTime" + } + ], + "description": "The value that the field should be equal to." }, - { - "$ref": "#/definitions/RangeFilter" + "field": { + "description": "Field to be filtered.", + "type": "string" }, - { - "$ref": "#/definitions/OneOfFilter" + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit for the field to be filtered." + } + }, + "required": [ + "field", + "equal" + ], + "type": "object" + }, + "FieldOneOfPredicate": { + "additionalProperties": false, + "properties": { + "field": { + "description": "Field to be filtered", + "type": "string" }, - { - "$ref": "#/definitions/SelectionFilter" + "oneOf": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "items": { + "type": "boolean" + }, + "type": "array" + }, + { + "items": { + "$ref": "#/definitions/DateTime" + }, + "type": "array" + } + ], + "description": "A set of values that the `field`'s value should be a member of,\nfor a data item included in the filtered data." }, - { + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "time unit for the field to be filtered." + } + }, + "required": [ + "field", + "oneOf" + ], + "type": "object" + }, + "FieldRangePredicate": { + "additionalProperties": false, + "properties": { + "field": { + "description": "Field to be filtered", "type": "string" + }, + "range": { + "description": "An array of inclusive minimum and maximum values\nfor a field value of a data item to be included in the filtered data.", + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/DateTime" + } + ] + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "time unit for the field to be filtered." } - ] + }, + "required": [ + "field", + "range" + ], + "type": "object" }, "FilterTransform": { "additionalProperties": false, "properties": { "filter": { - "$ref": "#/definitions/FilterOperand", - "description": "The `filter` property must be either (1) a filter object for [equal-filters](filter.html#equalfilter),\n[range-filters](filter.html#rangefilter), [one-of filters](filter.html#oneoffilter), or [selection filters](filter.html#selectionfilter);\n(2) a [Vega Expression](filter.html#expression) string,\nwhere `datum` can be used to refer to the current data object; or (3) an array of filters (either objects or expression strings) that must all be true for a datum to pass the filter and be included." + "$ref": "#/definitions/LogicalOperand", + "description": "The `filter` property must be one of the predicate definitions:\n(1) a [Vega Expression](filter.html#expression) string,\nwhere `datum` can be used to refer to the current data object;\n(2) one of the field predicates: [equal predicate](filter.html#equal-predicate);\n[range precidate](filter.html#range-predicate), [one-of predicate](filter.html#one-of-predicate);\n(3) a [selection predicate](filter.html#selection-predicate);\nor (4) a logical operand that combines (1), (2), or (3)." } }, "required": [ @@ -3045,12 +3107,12 @@ ], "type": "string" }, - "AndFilter": { + "LogicalAnd": { "additionalProperties": false, "properties": { "and": { "items": { - "$ref": "#/definitions/FilterOperand" + "$ref": "#/definitions/LogicalOperand" }, "type": "array" } @@ -3075,11 +3137,11 @@ ], "type": "object" }, - "NotFilter": { + "LogicalNot": { "additionalProperties": false, "properties": { "not": { - "$ref": "#/definitions/FilterOperand" + "$ref": "#/definitions/LogicalOperand" } }, "required": [ @@ -3099,19 +3161,19 @@ ], "type": "object" }, - "FilterOperand": { + "LogicalOperand": { "anyOf": [ { - "$ref": "#/definitions/NotFilter" + "$ref": "#/definitions/LogicalNot" }, { - "$ref": "#/definitions/AndFilter" + "$ref": "#/definitions/LogicalAnd" }, { - "$ref": "#/definitions/OrFilter" + "$ref": "#/definitions/LogicalOr" }, { - "$ref": "#/definitions/Filter" + "$ref": "#/definitions/Predicate" } ] }, @@ -3131,12 +3193,12 @@ } ] }, - "OrFilter": { + "LogicalOr": { "additionalProperties": false, "properties": { "or": { "items": { - "$ref": "#/definitions/FilterOperand" + "$ref": "#/definitions/LogicalOperand" }, "type": "array" } @@ -3788,53 +3850,6 @@ ], "type": "string" }, - "OneOfFilter": { - "additionalProperties": false, - "properties": { - "field": { - "description": "Field to be filtered", - "type": "string" - }, - "oneOf": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "items": { - "type": "boolean" - }, - "type": "array" - }, - { - "items": { - "$ref": "#/definitions/DateTime" - }, - "type": "array" - } - ], - "description": "A set of values that the `field`'s value should be a member of,\nfor a data item included in the filtered data." - }, - "timeUnit": { - "$ref": "#/definitions/TimeUnit", - "description": "time unit for the field to be filtered." - } - }, - "required": [ - "field", - "oneOf" - ], - "type": "object" - }, "OrderFieldDef": { "additionalProperties": false, "properties": { @@ -3998,6 +4013,25 @@ ], "type": "object" }, + "Predicate": { + "anyOf": [ + { + "$ref": "#/definitions/FieldEqualPredicate" + }, + { + "$ref": "#/definitions/FieldRangePredicate" + }, + { + "$ref": "#/definitions/FieldOneOfPredicate" + }, + { + "$ref": "#/definitions/SelectionPredicate" + }, + { + "type": "string" + } + ] + }, "RangeConfig": { "additionalProperties": { "$ref": "#/definitions/RangeConfigValue" @@ -4111,40 +4145,6 @@ } ] }, - "RangeFilter": { - "additionalProperties": false, - "properties": { - "field": { - "description": "Field to be filtered", - "type": "string" - }, - "range": { - "description": "An array of inclusive minimum and maximum values\nfor a field value of a data item to be included in the filtered data.", - "items": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/definitions/DateTime" - } - ] - }, - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "timeUnit": { - "$ref": "#/definitions/TimeUnit", - "description": "time unit for the field to be filtered." - } - }, - "required": [ - "field", - "range" - ], - "type": "object" - }, "Repeat": { "additionalProperties": false, "properties": { @@ -4606,7 +4606,7 @@ } ] }, - "SelectionFilter": { + "SelectionPredicate": { "additionalProperties": false, "properties": { "selection": { diff --git a/scripts/rename-schema.sh b/scripts/rename-schema.sh index 472daed2b6..be34c0de69 100755 --- a/scripts/rename-schema.sh +++ b/scripts/rename-schema.sh @@ -25,7 +25,3 @@ perl -pi -e s,'LogicalOperand','SelectionOperand',g build/vega-lite-sche perl -pi -e s,'LogicalAnd','SelectionAnd',g build/vega-lite-schema.json perl -pi -e s,'LogicalOr','SelectionOr',g build/vega-lite-schema.json perl -pi -e s,'LogicalNot','SelectionNot',g build/vega-lite-schema.json -perl -pi -e s,'LogicalOperand','FilterOperand',g build/vega-lite-schema.json -perl -pi -e s,'LogicalAnd','AndFilter',g build/vega-lite-schema.json -perl -pi -e s,'LogicalOr','OrFilter',g build/vega-lite-schema.json -perl -pi -e s,'LogicalNot','NotFilter',g build/vega-lite-schema.json diff --git a/site/docs/transform/filter.md b/site/docs/transform/filter.md index e3740ef7ad..b1d517190c 100644 --- a/site/docs/transform/filter.md +++ b/site/docs/transform/filter.md @@ -19,7 +19,7 @@ The filter transform removes objects from a data stream based on a provided filt } ``` -Vega-Lite filter transforms must have the `filter` property. +Vega-Lite filter transforms must have the `filter` property describing the predicate for the filtering condition. {% include table.html props="filter" source="FilterTransform" %} @@ -30,15 +30,14 @@ Vega-Lite filter transforms must have the `filter` property. For a [Vega Expression](https://vega.github.io/vega/docs/expressions/) string, each datum object can be referred using bound variable `datum`. For example, setting `filter` to `"datum.b2 > 60"` would make the output data includes only items that have values in the field `b2` over 60. -## Filter Object +## Field Predicate +For a filter predicate, a `field` must be provided along with one of the predicate properties: ([`equal`](#equal-predicate), [`range`](#range-predicate), or [`oneOf`](#oneofilter)). Values of these operators can be primitive types (string, number, boolean) or a [DateTime definition object](types.html#datetime) to describe time. In addition, `timeUnit` can be provided to further transform a temporal `field`. -For a filter object, either a `field` or [`selection` name](#selectionfilter) must be provided. The former takes one of the filter operators ([`equal`](#equalfilter), [`range`](#rangefilter), or [`oneOf`](#oneofilter)). Values of these operators can be primitive types (string, number, boolean) or a [DateTime definition object](types.html#datetime) to describe time. In addition, `timeUnit` can be provided to further transform a temporal `field`. +{:#equal-predicate} +### Field Equal Predicate -{:#equalfilter} -### Equal Filter - -{% include table.html props="field,equal,timeUnit" source="EqualFilter" %} +{% include table.html props="field,equal,timeUnit" source="FieldEqualPredicate" %} For example, to check if the `car_color` field's value is equal to `"red"`, we can use the following filter: @@ -47,10 +46,10 @@ For example, to check if the `car_color` field's value is equal to `"red"`, we c {"filter": {"field": "car_color", "equal": "red"}} ``` -{:#rangefilter} -### Range Filter +{:#range-predicate} +### Field Range Predicate -{% include table.html props="field,range,timeUnit" source="RangeFilter" %} +{% include table.html props="field,range,timeUnit" source="FieldRangePredicate" %} **Examples** @@ -60,17 +59,19 @@ For example, to check if the `car_color` field's value is equal to `"red"`, we c - `{"filter": {"field": "date", "range": [{"year": 2006, "month": "jan", "date": 1}, {"year": 2008, "month": "feb", "date": 20}] }}` checks if the `date`'s value is between Jan 1, 2006 and Feb 20, 2008. -{:#oneoffilter} -### One-Of Filter +{:#one-of-predicate} +### Field One-Of Predicate -{% include table.html props="field,oneOf,timeUnit" source="OneOfFilter" %} +{% include table.html props="field,oneOf,timeUnit" source="FieldOneOfPredicate" %} For example, `{"filter": {"field": "car_color", "oneOf":["red", "yellow"]}}` checks if the `car_color` field's value is `"red"` or `"yellow"`. -{:#selectionfilter} -### Selection Filter +{:#selection-predicate} +## Selection Predicate + +For a selection predicate, a `selection` name must be provided. -{% include table.html props="selection" source="SelectionFilter" %} +{% include table.html props="selection" source="SelectionPredicate" %} For example, with `{"filter": {"selection": "brush"}}`, only data values that fall within the selection named `brush` will remain in the dataset as shown below. diff --git a/src/compile/data/filter.ts b/src/compile/data/filter.ts index fdff4cc508..e78ab42f49 100644 --- a/src/compile/data/filter.ts +++ b/src/compile/data/filter.ts @@ -1,5 +1,5 @@ -import {expression, Filter} from '../../filter'; import {LogicalOperand} from '../../logical'; +import {expression, Predicate} from '../../predicate'; import {duplicate} from '../../util'; import {VgFilterTransform} from '../../vega.schema'; import {Model} from '../model'; @@ -11,7 +11,7 @@ export class FilterNode extends DataFlowNode { return new FilterNode(this.model, duplicate(this.filter)); } - constructor(private readonly model: Model, private filter: LogicalOperand) { + constructor(private readonly model: Model, private filter: LogicalOperand) { super(); this.expr = expression(this.model, this.filter, this); } diff --git a/src/compile/data/formatparse.ts b/src/compile/data/formatparse.ts index 89863993c6..9ce9e1f64f 100644 --- a/src/compile/data/formatparse.ts +++ b/src/compile/data/formatparse.ts @@ -1,8 +1,8 @@ import {isCountingAggregateOp} from '../../aggregate'; import {isNumberFieldDef, isTimeFieldDef} from '../../fielddef'; -import {isEqualFilter, isOneOfFilter, isRangeFilter} from '../../filter'; import * as log from '../../log'; import {forEachLeave} from '../../logical'; +import {isFieldPredicate} from '../../predicate'; import {isCalculate, isFilter, Transform} from '../../transform'; import {accessPath, Dict, duplicate, keys, toSet} from '../../util'; import {VgFormulaTransform} from '../../vega.schema'; @@ -54,7 +54,7 @@ export class ParseNode extends DataFlowNode { calcFieldMap[transform.as] = true; } else if (isFilter(transform)) { forEachLeave(transform.filter, (filter) => { - if (isEqualFilter(filter) || isRangeFilter(filter) || isOneOfFilter(filter)) { + if (isFieldPredicate(filter)) { if (filter.timeUnit) { parse[filter.field] = 'date'; } diff --git a/src/compile/data/parse.ts b/src/compile/data/parse.ts index 7d06801f48..268295f164 100644 --- a/src/compile/data/parse.ts +++ b/src/compile/data/parse.ts @@ -1,8 +1,8 @@ import {isNumber, isString} from 'vega-util'; import {MAIN, RAW} from '../../data'; import {DateTime, isDateTime} from '../../datetime'; -import {isEqualFilter, isOneOfFilter, isRangeFilter} from '../../filter'; import * as log from '../../log'; +import {isFieldEqualPredicate, isFieldOneOfPredicate, isFieldRangePredicate} from '../../predicate'; import {isAggregate, isBin, isCalculate, isFilter, isLookup, isTimeUnit} from '../../transform'; import {Dict, keys} from '../../util'; import {isFacetModel, isLayerModel, isUnitModel, Model} from '../model'; @@ -76,11 +76,11 @@ export function parseTransformArray(model: Model) { // For EqualFilter, just use the equal property. // For RangeFilter and OneOfFilter, all array members should have // the same type, so we only use the first one. - if (isEqualFilter(filter)) { + if (isFieldEqualPredicate(filter)) { val = filter.equal; - } else if (isRangeFilter(filter)) { + } else if (isFieldRangePredicate(filter)) { val = filter.range[0]; - } else if (isOneOfFilter(filter)) { + } else if (isFieldOneOfPredicate(filter)) { val = (filter.oneOf || filter['in'])[0]; } // else -- for filter expression, we can't infer anything diff --git a/src/compile/mark/mixins.ts b/src/compile/mark/mixins.ts index 678b77adc6..cc4b6b28ba 100644 --- a/src/compile/mark/mixins.ts +++ b/src/compile/mark/mixins.ts @@ -6,7 +6,7 @@ import {MarkDef} from '../../mark'; import * as util from '../../util'; import {VG_MARK_CONFIGS, VgEncodeEntry, VgValueRef} from '../../vega.schema'; import {getMarkConfig} from '../common'; -import {predicate} from '../selection/selection'; +import {selectionPredicate} from '../selection/selection'; import {UnitModel} from '../unit'; import * as ref from './valueref'; @@ -95,7 +95,7 @@ function wrapCondition( const vgConditions = conditions.map((c) => { const conditionValueRef = refFn(c); return { - test: predicate(model, c.selection), + test: selectionPredicate(model, c.selection), ...conditionValueRef }; }); diff --git a/src/compile/selection/selection.ts b/src/compile/selection/selection.ts index aca6065f78..cb986eb852 100644 --- a/src/compile/selection/selection.ts +++ b/src/compile/selection/selection.ts @@ -212,7 +212,7 @@ export function assembleLayerSelectionMarks(model: LayerModel, marks: any[]): an return marks; } -export function predicate(model: Model, selections: LogicalOperand, dfnode?: DataFlowNode): string { +export function selectionPredicate(model: Model, selections: LogicalOperand, dfnode?: DataFlowNode): string { const stores: string[] = []; function expr(name: string): string { const vname = varName(name); diff --git a/src/filter.ts b/src/predicate.ts similarity index 51% rename from src/filter.ts rename to src/predicate.ts index c7e9091152..68d271cc86 100644 --- a/src/filter.ts +++ b/src/predicate.ts @@ -1,6 +1,6 @@ import {DataFlowNode} from './compile/data/dataflow'; import {Model} from './compile/model'; -import {predicate} from './compile/selection/selection'; +import {selectionPredicate} from './compile/selection/selection'; import {DateTime, dateTimeExpr, isDateTime} from './datetime'; import {vgField} from './fielddef'; import {LogicalOperand} from './logical'; @@ -8,26 +8,31 @@ import {fieldExpr as timeUnitFieldExpr, getLocalTimeUnit, isLocalSingleTimeUnit, import {isArray, isString, logicalExpr} from './util'; -export type Filter = - // FieldFilter (but we don't type FieldFilter here so the schema has no nesting +export type Predicate = + // a) FieldPrecidate (but we don't type FieldFilter here so the schema has no nesting // and thus the documentation shows all of the types clearly) - EqualFilter | RangeFilter | OneOfFilter | - SelectionFilter | string; + FieldEqualPredicate | FieldRangePredicate | FieldOneOfPredicate | + // b) Selection Predicate + SelectionPredicate | + // c) Vega Expression string + string; -export type FieldFilter = EqualFilter | RangeFilter | OneOfFilter; -export interface SelectionFilter { + +export type FieldPredicate = FieldEqualPredicate | FieldRangePredicate | FieldOneOfPredicate; + +export interface SelectionPredicate { /** * Filter using a selection name. */ selection: LogicalOperand; } -export function isSelectionFilter(filter: LogicalOperand): filter is SelectionFilter { - return filter && filter['selection']; +export function isSelectionPredicate(predicate: LogicalOperand): predicate is SelectionPredicate { + return predicate && predicate['selection']; } -export interface EqualFilter { +export interface FieldEqualPredicate { // TODO: support aggregate /** @@ -47,11 +52,11 @@ export interface EqualFilter { } -export function isEqualFilter(filter: any): filter is EqualFilter { - return filter && !!filter.field && filter.equal!==undefined; +export function isFieldEqualPredicate(predicate: any): predicate is FieldEqualPredicate { + return predicate && !!predicate.field && predicate.equal !== undefined; } -export interface RangeFilter { +export interface FieldRangePredicate { // TODO: support aggregate /** @@ -74,16 +79,16 @@ export interface RangeFilter { } -export function isRangeFilter(filter: any): filter is RangeFilter { - if (filter && filter.field) { - if (isArray(filter.range) && filter.range.length === 2) { +export function isFieldRangePredicate(predicate: any): predicate is FieldRangePredicate { + if (predicate && predicate.field) { + if (isArray(predicate.range) && predicate.range.length === 2) { return true; } } return false; } -export interface OneOfFilter { +export interface FieldOneOfPredicate { // TODO: support aggregate /** @@ -104,73 +109,73 @@ export interface OneOfFilter { } -export function isOneOfFilter(filter: any): filter is OneOfFilter { - return filter && !!filter.field && ( - isArray(filter.oneOf) || - isArray(filter.in) // backward compatibility +export function isFieldOneOfPredicate(predicate: any): predicate is FieldOneOfPredicate { + return predicate && !!predicate.field && ( + isArray(predicate.oneOf) || + isArray(predicate.in) // backward compatibility ); } -export function isFieldFilter(filter: Filter): filter is OneOfFilter | EqualFilter | RangeFilter { - return isOneOfFilter(filter) || isEqualFilter(filter) || isRangeFilter(filter); +export function isFieldPredicate(predicate: Predicate): predicate is FieldOneOfPredicate | FieldEqualPredicate | FieldRangePredicate { + return isFieldOneOfPredicate(predicate) || isFieldEqualPredicate(predicate) || isFieldRangePredicate(predicate); } /** - * Converts a filter into an expression. + * Converts a predicate into an expression. */ // model is only used for selection filters. -export function expression(model: Model, filterOp: LogicalOperand, node?: DataFlowNode): string { - return logicalExpr(filterOp, (filter: Filter) => { - if (isString(filter)) { - return filter; - } else if (isSelectionFilter(filter)) { - return predicate(model, filter.selection, node); +export function expression(model: Model, filterOp: LogicalOperand, node?: DataFlowNode): string { + return logicalExpr(filterOp, (predicate: Predicate) => { + if (isString(predicate)) { + return predicate; + } else if (isSelectionPredicate(predicate)) { + return selectionPredicate(model, predicate.selection, node); } else { // Filter Object - return fieldFilterExpression(filter); + return fieldFilterExpression(predicate); } }); } // This method is used by Voyager. Do not change its behavior without changing Voyager. -export function fieldFilterExpression(filter: FieldFilter, useInRange=true) { - const fieldExpr = filter.timeUnit ? +export function fieldFilterExpression(predicate: FieldPredicate, useInRange=true) { + const fieldExpr = predicate.timeUnit ? // For timeUnit, cast into integer with time() so we can use ===, inrange, indexOf to compare values directly. // TODO: We calculate timeUnit on the fly here. Consider if we would like to consolidate this with timeUnit pipeline // TODO: support utc - ('time(' + timeUnitFieldExpr(filter.timeUnit, filter.field) + ')') : - vgField(filter, {expr: 'datum'}); + ('time(' + timeUnitFieldExpr(predicate.timeUnit, predicate.field) + ')') : + vgField(predicate, {expr: 'datum'}); - if (isEqualFilter(filter)) { - return fieldExpr + '===' + valueExpr(filter.equal, filter.timeUnit); - } else if (isOneOfFilter(filter)) { + if (isFieldEqualPredicate(predicate)) { + return fieldExpr + '===' + valueExpr(predicate.equal, predicate.timeUnit); + } else if (isFieldOneOfPredicate(predicate)) { // "oneOf" was formerly "in" -- so we need to add backward compatibility - const oneOf: OneOfFilter[] = filter.oneOf || filter['in']; + const oneOf: FieldOneOfPredicate[] = predicate.oneOf || predicate['in']; return 'indexof([' + - oneOf.map((v) => valueExpr(v, filter.timeUnit)).join(',') + + oneOf.map((v) => valueExpr(v, predicate.timeUnit)).join(',') + '], ' + fieldExpr + ') !== -1'; - } else if (isRangeFilter(filter)) { - const lower = filter.range[0]; - const upper = filter.range[1]; + } else if (isFieldRangePredicate(predicate)) { + const lower = predicate.range[0]; + const upper = predicate.range[1]; if (lower !== null && upper !== null && useInRange) { return 'inrange(' + fieldExpr + ', [' + - valueExpr(lower, filter.timeUnit) + ', ' + - valueExpr(upper, filter.timeUnit) + '])'; + valueExpr(lower, predicate.timeUnit) + ', ' + + valueExpr(upper, predicate.timeUnit) + '])'; } const exprs = []; if (lower !== null) { - exprs.push(`${fieldExpr} >= ${valueExpr(lower, filter.timeUnit)}`); + exprs.push(`${fieldExpr} >= ${valueExpr(lower, predicate.timeUnit)}`); } if (upper !== null) { - exprs.push(`${fieldExpr} <= ${valueExpr(upper, filter.timeUnit)}`); + exprs.push(`${fieldExpr} <= ${valueExpr(upper, predicate.timeUnit)}`); } return exprs.length > 0 ? exprs.join(' && ') : 'true'; } /* istanbul ignore next: it should never reach here */ - throw new Error(`Invalid field filter: ${JSON.stringify(filter)}`); + throw new Error(`Invalid field predicate: ${JSON.stringify(predicate)}`); } function valueExpr(v: any, timeUnit: TimeUnit): string { @@ -189,8 +194,8 @@ function valueExpr(v: any, timeUnit: TimeUnit): string { return JSON.stringify(v); } -export function normalizeFilter(f: Filter): Filter { - if (isFieldFilter(f) && f.timeUnit) { +export function normalizePredicate(f: Predicate): Predicate { + if (isFieldPredicate(f) && f.timeUnit) { return { ...f, timeUnit: normalizeTimeUnit(f.timeUnit) diff --git a/src/transform.ts b/src/transform.ts index aa1e09f691..bf3ec5af44 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -1,19 +1,23 @@ import {AggregateOp} from './aggregate'; import {BinParams} from './bin'; import {Data} from './data'; -import {Filter, normalizeFilter} from './filter'; import {LogicalOperand, normalizeLogicalOperand} from './logical'; +import {normalizePredicate, Predicate} from './predicate'; import {TimeUnit} from './timeunit'; export interface FilterTransform { /** - * The `filter` property must be either (1) a filter object for [equal-filters](filter.html#equalfilter), - * [range-filters](filter.html#rangefilter), [one-of filters](filter.html#oneoffilter), or [selection filters](filter.html#selectionfilter); - * (2) a [Vega Expression](filter.html#expression) string, - * where `datum` can be used to refer to the current data object; or (3) an array of filters (either objects or expression strings) that must all be true for a datum to pass the filter and be included. + * The `filter` property must be one of the predicate definitions: + * (1) a [Vega Expression](filter.html#expression) string, + * where `datum` can be used to refer to the current data object; + * (2) one of the field predicates: [equal predicate](filter.html#equal-predicate); + * [range precidate](filter.html#range-predicate), [one-of predicate](filter.html#one-of-predicate); + * (3) a [selection predicate](filter.html#selection-predicate); + * or (4) a logical operand that combines (1), (2), or (3). */ - filter: LogicalOperand; + // TODO: https://github.com/vega/vega-lite/issues/2901 + filter: LogicalOperand; } export function isFilter(t: Transform): t is FilterTransform { @@ -165,7 +169,7 @@ export function normalizeTransform(transform: Transform[]) { return transform.map(t => { if (isFilter(t)) { return { - filter: normalizeLogicalOperand(t.filter, normalizeFilter) + filter: normalizeLogicalOperand(t.filter, normalizePredicate) }; } return t; diff --git a/test/compile/selection/predicate.test.ts b/test/compile/selection/predicate.test.ts index 93271e8401..db0c9db9ab 100644 --- a/test/compile/selection/predicate.test.ts +++ b/test/compile/selection/predicate.test.ts @@ -3,11 +3,11 @@ import {assert} from 'chai'; import {nonPosition} from '../../../src/compile/mark/mixins'; import * as selection from '../../../src/compile/selection/selection'; -import {expression} from '../../../src/filter'; +import {expression} from '../../../src/predicate'; import {VgEncodeEntry} from '../../../src/vega.schema'; import {parseUnitModel} from '../../util'; -const predicate = selection.predicate; +const predicate = selection.selectionPredicate; describe('Selection Predicate', function() { const model = parseUnitModel({ diff --git a/test/filter.test.ts b/test/predicate.test.ts similarity index 90% rename from test/filter.test.ts rename to test/predicate.test.ts index 394ed44187..1ce1088e2e 100644 --- a/test/filter.test.ts +++ b/test/predicate.test.ts @@ -1,6 +1,6 @@ import {assert} from 'chai'; -import {expression, fieldFilterExpression, isEqualFilter, isOneOfFilter, isRangeFilter} from '../src/filter'; +import {expression, fieldFilterExpression, isFieldEqualPredicate, isFieldOneOfPredicate, isFieldRangePredicate} from '../src/predicate'; import {TimeUnit} from '../src/timeunit'; describe('filter', () => { @@ -11,36 +11,36 @@ describe('filter', () => { describe('isEqualFilter', () => { it('should return true for an equal filter', () => { - assert.isTrue(isEqualFilter(equalFilter)); + assert.isTrue(isFieldEqualPredicate(equalFilter)); }); it('should return false for other filters', () => { [oneOfFilter, rangeFilter, exprFilter].forEach((filter) => { - assert.isFalse(isEqualFilter(filter)); + assert.isFalse(isFieldEqualPredicate(filter)); }); }); }); describe('isOneOfFilter', () => { it('should return true for an in filter', () => { - assert.isTrue(isOneOfFilter(oneOfFilter)); + assert.isTrue(isFieldOneOfPredicate(oneOfFilter)); }); it('should return false for other filters', () => { [equalFilter, rangeFilter, exprFilter].forEach((filter) => { - assert.isFalse(isOneOfFilter(filter)); + assert.isFalse(isFieldOneOfPredicate(filter)); }); }); }); describe('isRangeFilter', () => { it('should return true for a range filter', () => { - assert.isTrue(isRangeFilter(rangeFilter)); + assert.isTrue(isFieldRangePredicate(rangeFilter)); }); it('should return false for other filters', () => { [oneOfFilter, equalFilter, exprFilter].forEach((filter) => { - assert.isFalse(isRangeFilter(filter)); + assert.isFalse(isFieldRangePredicate(filter)); }); }); }); diff --git a/test/transform.test.ts b/test/transform.test.ts index f4a9d3b491..cb2867bdd7 100644 --- a/test/transform.test.ts +++ b/test/transform.test.ts @@ -1,13 +1,13 @@ import {assert} from 'chai'; -import {Filter} from '../src/filter'; import * as log from '../src/log'; import {LogicalOperand} from '../src/logical'; +import {Predicate} from '../src/predicate'; import {TimeUnit} from '../src/timeunit'; import {normalizeTransform, Transform} from '../src/transform'; describe('normalizeTransform()', () => { it('replaces filter with timeUnit=yearmonthday with yearmonthdate and throws the right warning', log.wrap((localLogger) => { - const filter: LogicalOperand = { + const filter: LogicalOperand = { and: [ {not: {timeUnit: 'yearmonthday' as TimeUnit, field: 'd', equal: {year: 2008}}}, {or: [{field: 'a', equal: 5}]}