-
Notifications
You must be signed in to change notification settings - Fork 592
/
filterinvalid.ts
109 lines (93 loc) · 3.43 KB
/
filterinvalid.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import {FilterTransform as VgFilterTransform} from 'vega';
import {isScaleChannel} from '../../channel';
import {TypedFieldDef, vgField as fieldRef} from '../../channeldef';
import {Dict, hash, keys} from '../../util';
import {getScaleInvalidDataMode, isScaleInvalidDataInclude} from '../invalid/ScaleInvalidDataMode';
import {DataSourcesForHandlingInvalidValues} from '../invalid/datasources';
import {UnitModel} from '../unit';
import {DataFlowNode} from './dataflow';
import {isCountingAggregateOp} from '../../aggregate';
export class FilterInvalidNode extends DataFlowNode {
public clone() {
return new FilterInvalidNode(null, {...this.filter});
}
constructor(
parent: DataFlowNode,
public readonly filter: Dict<TypedFieldDef<string>>
) {
super(parent);
}
public static make(
parent: DataFlowNode,
model: UnitModel,
dataSourcesForHandlingInvalidValues: DataSourcesForHandlingInvalidValues
): FilterInvalidNode {
const {config, markDef} = model;
const {marks, scales} = dataSourcesForHandlingInvalidValues;
if (marks === 'pre-filter' && scales === 'pre-filter') {
// If neither marks nor scale domains need data source to filter null values, then don't add the filter.
return null;
}
const filter = model.reduceFieldDef(
(aggregator: Dict<TypedFieldDef<string>>, fieldDef, channel) => {
const scaleComponent = isScaleChannel(channel) && model.getScaleComponent(channel);
if (scaleComponent) {
const scaleType = scaleComponent.get('type');
const {aggregate} = fieldDef;
const invalidDataMode = getScaleInvalidDataMode({
scaleChannel: channel,
markDef,
config,
scaleType,
isCountAggregate: isCountingAggregateOp(aggregate)
});
// If the invalid data mode is include or always-valid, we don't need to filter invalid values as the scale can handle invalid values.
if (!isScaleInvalidDataInclude(invalidDataMode) && invalidDataMode !== 'always-valid') {
aggregator[fieldDef.field] = fieldDef as any; // we know that the fieldDef is a typed field def
}
}
return aggregator;
},
{} as Dict<TypedFieldDef<string>>
);
if (!keys(filter).length) {
return null;
}
return new FilterInvalidNode(parent, filter);
}
public dependentFields() {
return new Set(keys(this.filter));
}
public producedFields() {
return new Set<string>(); // filter does not produce any new fields
}
public hash() {
return `FilterInvalid ${hash(this.filter)}`;
}
/**
* Create the VgTransforms for each of the filtered fields.
*/
public assemble(): VgFilterTransform {
const filters = keys(this.filter).reduce((vegaFilters, field) => {
const fieldDef = this.filter[field];
const ref = fieldRef(fieldDef, {expr: 'datum'});
if (fieldDef !== null) {
if (fieldDef.type === 'temporal') {
vegaFilters.push(`(isDate(${ref}) || (isValid(${ref}) && isFinite(+${ref})))`);
} else if (fieldDef.type === 'quantitative') {
vegaFilters.push(`isValid(${ref})`);
vegaFilters.push(`isFinite(+${ref})`);
} else {
// should never get here
}
}
return vegaFilters;
}, [] as string[]);
return filters.length > 0
? {
type: 'filter',
expr: filters.join(' && ')
}
: null;
}
}