compassql
Version:
CompassQL visualization query language
233 lines (189 loc) • 6.76 kB
text/typescript
import {Axis, AXIS_PROPERTIES} from 'vega-lite/build/src/axis';
import {BinParams} from 'vega-lite/build/src/bin';
import {Legend, LEGEND_PROPERTIES} from 'vega-lite/build/src/legend';
import {Scale, SCALE_PROPERTIES} from 'vega-lite/build/src/scale';
import {EncodingSortField} from 'vega-lite/build/src/sort';
import {Flag} from 'vega-lite/build/src/util';
import {AutoCountQuery, FieldQuery, ValueQuery} from './query/encoding';
import {TransformQuery} from './query/transform';
import {Diff, flagKeys} from './util';
/**
* There are two types of `Property`'s.
* One is just flat property names.
* (Try to hover `FlatProp` to see all of them.)
* Another is an object that describes a parent property (e.g., `scale`) and the child property (e.g., `type`)
*/
export type Property = FlatProp | EncodingNestedProp;
export type FlatProp = MarkProp | TransformProp | ViewProp | EncodingTopLevelProp;
export type MarkProp = 'mark' | 'stack'; // FIXME: determine how 'stack' works;
export type TransformProp = keyof TransformQuery;
export type ViewProp = 'width' | 'height' | 'background' | 'padding' | 'title';
export type EncodingTopLevelProp = Diff<keyof (FieldQuery & ValueQuery & AutoCountQuery), 'description'>; // Do not include description since description is simply a metadata
export type EncodingNestedProp = BinProp | SortProp | ScaleProp | AxisProp | LegendProp;
export type EncodingNestedChildProp =
| keyof BinParams
| keyof EncodingSortField<string>
| keyof Scale
| keyof Axis
| keyof Legend;
/**
* An object that describes a parent property (e.g., `scale`) and the child property (e.g., `type`)
*/
export type BaseEncodingNestedProp<P, T> = {
parent: P;
child: keyof T;
};
export type BinProp = BaseEncodingNestedProp<'bin', BinParams>;
export type SortProp = BaseEncodingNestedProp<'sort', EncodingSortField<string>>;
export type ScaleProp = BaseEncodingNestedProp<'scale', Scale>;
export type AxisProp = BaseEncodingNestedProp<'axis', Axis>;
export type LegendProp = BaseEncodingNestedProp<'legend', Legend>;
export function isEncodingNestedProp(p: Property): p is EncodingNestedProp {
return !!p['parent'];
}
const ENCODING_TOPLEVEL_PROP_INDEX: Flag<EncodingTopLevelProp> = {
channel: 1,
aggregate: 1,
autoCount: 1,
bin: 1,
timeUnit: 1,
hasFn: 1,
sort: 1,
stack: 1,
field: 1,
type: 1,
format: 1,
scale: 1,
axis: 1,
legend: 1,
value: 1
};
export const ENCODING_TOPLEVEL_PROPS = flagKeys(ENCODING_TOPLEVEL_PROP_INDEX);
export function isEncodingTopLevelProperty(p: Property): p is EncodingTopLevelProp {
return p in ENCODING_TOPLEVEL_PROP_INDEX;
}
export type EncodingNestedPropParent = 'bin' | 'scale' | 'sort' | 'axis' | 'legend';
const ENCODING_NESTED_PROP_PARENT_INDEX: Flag<EncodingNestedPropParent> = {
bin: 1,
scale: 1,
sort: 1,
axis: 1,
legend: 1
};
export function isEncodingNestedParent(prop: string): prop is EncodingNestedPropParent {
return ENCODING_NESTED_PROP_PARENT_INDEX[prop as string];
}
// FIXME -- we should not have to manually specify these
export const BIN_CHILD_PROPS: (keyof BinParams)[] = ['maxbins', 'divide', 'extent', 'base', 'step', 'steps', 'minstep'];
export const SORT_CHILD_PROPS: (keyof EncodingSortField<string>)[] = ['field', 'op', 'order'];
const BIN_PROPS = BIN_CHILD_PROPS.map(
(c): BinProp => {
return {parent: 'bin', child: c};
}
);
export const SORT_PROPS = SORT_CHILD_PROPS.map(
(c): SortProp => {
return {parent: 'sort', child: c};
}
);
export const SCALE_PROPS = SCALE_PROPERTIES.map(
(c): ScaleProp => {
return {parent: 'scale', child: c};
}
);
const AXIS_PROPS = AXIS_PROPERTIES.map(
(c): AxisProp => {
return {parent: 'axis', child: c};
}
);
const LEGEND_PROPS = LEGEND_PROPERTIES.map(
(c): LegendProp => {
return {parent: 'legend', child: c};
}
);
export const ENCODING_NESTED_PROPS = ([] as EncodingNestedProp[]).concat(
BIN_PROPS,
SORT_PROPS,
SCALE_PROPS,
AXIS_PROPS,
LEGEND_PROPS
);
export const VIEW_PROPS: Property[] = ['width', 'height', 'background', 'padding', 'title'] as Property[];
const PROP_KEY_DELIMITER = '.';
export function toKey(p: Property): string {
if (isEncodingNestedProp(p)) {
return p.parent + PROP_KEY_DELIMITER + p.child;
}
return p;
}
export function fromKey(k: string): Property {
const split = k.split(PROP_KEY_DELIMITER);
/* istanbul ignore else */
if (split.length === 1) {
return k as Property;
} else if (split.length === 2) {
return {
parent: split[0],
child: split[1]
} as EncodingNestedProp;
} else {
throw 'Invalid property key with ' + split.length + ' dots: ' + k;
}
}
const ENCODING_NESTED_PROP_INDEX = ENCODING_NESTED_PROPS.reduce((i, prop: EncodingNestedProp) => {
i[prop.parent] = i[prop.parent] || [];
i[prop.parent][prop.child] = prop;
return i;
}, {});
// FIXME consider using a more general method
export function getEncodingNestedProp(parent: EncodingTopLevelProp, child: EncodingNestedChildProp) {
return (ENCODING_NESTED_PROP_INDEX[parent] || {})[child];
}
export function isEncodingProperty(p: Property): p is EncodingTopLevelProp | EncodingNestedProp {
return isEncodingTopLevelProperty(p) || isEncodingNestedProp(p);
}
export const ALL_ENCODING_PROPS = ([] as Property[]).concat(ENCODING_TOPLEVEL_PROPS, ENCODING_NESTED_PROPS);
export const DEFAULT_PROP_PRECEDENCE: Property[] = ([
'type', // type is a constraint for field
'field',
// Field Transform
'bin',
'timeUnit',
'aggregate',
'autoCount',
// Encoding
'channel',
// Mark
'mark',
'stack',
'scale',
'sort',
'axis',
'legend'
] as Property[]).concat(BIN_PROPS, SCALE_PROPS, AXIS_PROPS, LEGEND_PROPS, SORT_PROPS);
export namespace Property {
export const MARK: 'mark' = 'mark';
export const TRANSFORM: 'transform' = 'transform';
// Layout
export const STACK: 'stack' = 'stack';
export const FORMAT: 'format' = 'format';
// TODO: sub parts of stack
// Encoding Properties
export const CHANNEL: 'channel' = 'channel';
export const AGGREGATE: 'aggregate' = 'aggregate';
export const AUTOCOUNT: 'autoCount' = 'autoCount';
export const BIN: 'bin' = 'bin';
export const HAS_FN: 'hasFn' = 'hasFn';
export const TIMEUNIT: 'timeUnit' = 'timeUnit';
export const FIELD: 'field' = 'field';
export const TYPE: 'type' = 'type';
export const SORT: 'sort' = 'sort';
export const SCALE: 'scale' = 'scale';
export const AXIS: 'axis' = 'axis';
export const LEGEND: 'legend' = 'legend';
export const WIDTH: 'width' = 'width';
export const HEIGHT: 'height' = 'height';
export const BACKGROUND: 'background' = 'background';
export const PADDING: 'padding' = 'padding';
export const TITLE: 'title' = 'title';
}