vega-lite
Version:
Vega-Lite is a concise high-level language for interactive visualization.
162 lines (143 loc) • 4.52 kB
text/typescript
import {isArray} from 'vega-util';
import {
ChannelDef,
DatumDef,
Field,
FieldDef,
FieldName,
hasConditionalFieldOrDatumDef,
isConditionalDef,
isFieldDef,
isFieldOrDatumDef,
isRepeatRef,
isSortableFieldDef,
ScaleFieldDef,
ValueDef,
} from '../channeldef.js';
import {Encoding} from '../encoding.js';
import * as log from '../log/index.js';
import {isSortField} from '../sort.js';
import {FacetFieldDef, FacetMapping, isFacetMapping} from '../spec/facet.js';
import {hasProperty} from '../util.js';
export interface RepeaterValue {
row?: string;
column?: string;
repeat?: string;
layer?: string;
}
export function replaceRepeaterInFacet(
facet: FacetFieldDef<Field> | FacetMapping<Field>,
repeater: RepeaterValue,
): FacetFieldDef<FieldName> | FacetMapping<FieldName> {
if (!repeater) {
return facet as FacetFieldDef<FieldName>;
}
if (isFacetMapping(facet)) {
return replaceRepeaterInMapping(facet, repeater) as FacetMapping<FieldName>;
}
return replaceRepeaterInFieldDef(facet, repeater) as FacetFieldDef<FieldName>;
}
export function replaceRepeaterInEncoding<E extends Encoding<Field>>(
encoding: E,
repeater: RepeaterValue,
): Encoding<FieldName> {
if (!repeater) {
return encoding as Encoding<FieldName>;
}
return replaceRepeaterInMapping(encoding, repeater) as Encoding<FieldName>;
}
/**
* Replaces repeated value and returns if the repeated value is valid.
*/
function replaceRepeatInProp<T>(prop: keyof T, o: T, repeater: RepeaterValue): T {
const val = o[prop];
if (isRepeatRef(val)) {
if (val.repeat in repeater) {
return {...o, [prop]: repeater[val.repeat]};
} else {
log.warn(log.message.noSuchRepeatedValue(val.repeat));
return undefined;
}
}
return o;
}
/**
* Replace repeater values in a field def with the concrete field name.
*/
function replaceRepeaterInFieldDef(fieldDef: FieldDef<Field>, repeater: RepeaterValue) {
fieldDef = replaceRepeatInProp('field', fieldDef, repeater);
if (fieldDef === undefined) {
// the field def should be ignored
return undefined;
} else if (fieldDef === null) {
return null;
}
if (isSortableFieldDef(fieldDef) && isSortField(fieldDef.sort)) {
const sort = replaceRepeatInProp('field', fieldDef.sort, repeater);
fieldDef = {
...fieldDef,
...(sort ? {sort} : {}),
};
}
return fieldDef as ScaleFieldDef<FieldName>;
}
function replaceRepeaterInFieldOrDatumDef(def: FieldDef<Field> | DatumDef<Field>, repeater: RepeaterValue) {
if (isFieldDef(def)) {
return replaceRepeaterInFieldDef(def, repeater);
} else {
const datumDef = replaceRepeatInProp('datum', def, repeater);
if (datumDef !== def && !datumDef.type) {
datumDef.type = 'nominal';
}
return datumDef;
}
}
function replaceRepeaterInChannelDef(channelDef: ChannelDef<Field>, repeater: RepeaterValue) {
if (isFieldOrDatumDef(channelDef)) {
const fd = replaceRepeaterInFieldOrDatumDef(channelDef, repeater);
if (fd) {
return fd;
} else if (isConditionalDef<ChannelDef<Field>>(channelDef)) {
return {condition: channelDef.condition};
}
} else {
if (hasConditionalFieldOrDatumDef(channelDef)) {
const fd = replaceRepeaterInFieldOrDatumDef(channelDef.condition, repeater);
if (fd) {
return {
...channelDef,
condition: fd,
} as ChannelDef;
} else {
const {condition, ...channelDefWithoutCondition} = channelDef;
return channelDefWithoutCondition as ChannelDef;
}
}
return channelDef as ValueDef;
}
return undefined;
}
type EncodingOrFacet<F extends Field> = Encoding<F> | FacetMapping<F>;
function replaceRepeaterInMapping(
mapping: EncodingOrFacet<Field>,
repeater: RepeaterValue,
): EncodingOrFacet<FieldName> {
const out: EncodingOrFacet<FieldName> = {};
for (const channel in mapping) {
if (hasProperty(mapping, channel)) {
const channelDef: ChannelDef<Field> | ChannelDef<Field>[] = mapping[channel];
if (isArray(channelDef)) {
// array cannot have condition
(out as any)[channel] = (channelDef as ChannelDef<Field>[]) // somehow we need to cast it here
.map((cd) => replaceRepeaterInChannelDef(cd, repeater))
.filter((cd) => cd);
} else {
const cd = replaceRepeaterInChannelDef(channelDef, repeater);
if (cd !== undefined) {
(out as any)[channel] = cd;
}
}
}
}
return out;
}