compassql
Version:
CompassQL visualization query language
130 lines (110 loc) • 4.68 kB
text/typescript
import {Mark} from 'vega-lite/build/src/mark';
import {QueryConfig} from './config';
import {checkEncoding} from './constraint/encoding';
import {checkSpec} from './constraint/spec';
import {WildcardIndex} from './wildcardindex';
import {SpecQueryModel} from './model';
import {Property, ENCODING_TOPLEVEL_PROPS, ENCODING_NESTED_PROPS} from './property';
import {PropIndex} from './propindex';
import {Wildcard} from './wildcard';
import {Schema} from './schema';
import {isValueQuery, isDisabledAutoCountQuery} from './query/encoding';
const ENUMERATOR_INDEX = new PropIndex<EnumeratorFactory>();
export interface Enumerator {
(answerSets: SpecQueryModel[], specM: SpecQueryModel): SpecQueryModel[];
}
export function getEnumerator(prop: Property) {
return ENUMERATOR_INDEX.get(prop);
}
export interface EnumeratorFactory {
(wildcardIndex: WildcardIndex, schema: Schema, opt: QueryConfig): Enumerator;
}
ENUMERATOR_INDEX.set('mark', (wildcardIndex: WildcardIndex, schema: Schema, opt: QueryConfig): Enumerator => {
return (answerSet, specM: SpecQueryModel) => {
const markWildcard = specM.getMark() as Wildcard<Mark>;
// enumerate the value
markWildcard.enum.forEach((mark) => {
specM.setMark(mark);
// Check spec constraint
const violatedSpecConstraint = checkSpec('mark', wildcardIndex.mark, specM, schema, opt);
if (!violatedSpecConstraint) {
// emit
answerSet.push(specM.duplicate());
}
});
// Reset to avoid side effect
specM.resetMark();
return answerSet;
};
});
ENCODING_TOPLEVEL_PROPS.forEach((prop) => {
ENUMERATOR_INDEX.set(prop, EncodingPropertyGeneratorFactory(prop));
});
ENCODING_NESTED_PROPS.forEach((nestedProp) => {
ENUMERATOR_INDEX.set(nestedProp, EncodingPropertyGeneratorFactory(nestedProp));
});
/**
* @param prop property type.
* @return an answer set reducer factory for the given prop.
*/
export function EncodingPropertyGeneratorFactory(prop: Property): EnumeratorFactory {
/**
* @return as reducer that takes a specQueryModel as input and output an answer set array.
*/
return (wildcardIndex: WildcardIndex, schema: Schema, opt: QueryConfig): Enumerator => {
return (answerSet: SpecQueryModel[], specM: SpecQueryModel) => {
// index of encoding mappings that require enumeration
const indices = wildcardIndex.encodingIndicesByProperty.get(prop);
function enumerate(jobIndex: number) {
if (jobIndex === indices.length) {
// emit and terminate
answerSet.push(specM.duplicate());
return;
}
const index = indices[jobIndex];
const wildcard: Wildcard<any> = wildcardIndex.encodings[index].get(prop);
const encQ = specM.getEncodingQueryByIndex(index);
const propWildcard = specM.getEncodingProperty(index, prop);
if (isValueQuery(encQ) || (
// TODO: encQ.exclude
// If this encoding query is an excluded autoCount, there is no point enumerating other properties
// for this encoding query because they will be excluded anyway.
// Thus, we can just move on to the next encoding to enumerate.
(isDisabledAutoCountQuery(encQ)) ||
// nested encoding property might have its parent set to false
// therefore, we no longer have to enumerate them
!propWildcard
)
) { // TODO: encQ.excluded
enumerate(jobIndex + 1);
} else {
wildcard.enum.forEach((propVal) => {
if (propVal === null) {
// our duplicate() method use JSON.stringify, parse and thus can accidentally
// convert undefined in an array into null
propVal = undefined;
}
specM.setEncodingProperty(index, prop, propVal, wildcard);
// Check encoding constraint
const violatedEncodingConstraint = checkEncoding(prop, wildcard, index, specM, schema, opt);
if (violatedEncodingConstraint) {
return; // do not keep searching
}
// Check spec constraint
const violatedSpecConstraint = checkSpec(prop, wildcard, specM, schema, opt);
if (violatedSpecConstraint) {
return; // do not keep searching
}
// If qualify all of the constraints, keep enumerating
enumerate(jobIndex + 1);
});
// Reset to avoid side effect
specM.resetEncodingProperty(index, prop, wildcard);
}
}
// start enumerating from 0
enumerate(0);
return answerSet;
};
};
}