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 | FacetMapping, repeater: RepeaterValue, ): FacetFieldDef | FacetMapping { if (!repeater) { return facet as FacetFieldDef; } if (isFacetMapping(facet)) { return replaceRepeaterInMapping(facet, repeater) as FacetMapping; } return replaceRepeaterInFieldDef(facet, repeater) as FacetFieldDef; } export function replaceRepeaterInEncoding>( encoding: E, repeater: RepeaterValue, ): Encoding { if (!repeater) { return encoding as Encoding; } return replaceRepeaterInMapping(encoding, repeater) as Encoding; } /** * Replaces repeated value and returns if the repeated value is valid. */ function replaceRepeatInProp(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, 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; } function replaceRepeaterInFieldOrDatumDef(def: FieldDef | DatumDef, 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, repeater: RepeaterValue) { if (isFieldOrDatumDef(channelDef)) { const fd = replaceRepeaterInFieldOrDatumDef(channelDef, repeater); if (fd) { return fd; } else if (isConditionalDef>(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 = Encoding | FacetMapping; function replaceRepeaterInMapping( mapping: EncodingOrFacet, repeater: RepeaterValue, ): EncodingOrFacet { const out: EncodingOrFacet = {}; for (const channel in mapping) { if (hasProperty(mapping, channel)) { const channelDef: ChannelDef | ChannelDef[] = mapping[channel]; if (isArray(channelDef)) { // array cannot have condition (out as any)[channel] = (channelDef as ChannelDef[]) // 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; }