import {Gradient, SignalRef, Text} from 'vega';import {isArray, isBoolean, isNumber, isString} from 'vega-util';import {Aggregate, isAggregateOp, isArgmaxDef, isArgminDef, isCountingAggregateOp} from './aggregate';import {Axis} from './axis';import {autoMaxBins, Bin, BinParams, binToString, isBinned, isBinning} from './bin';import { ANGLE, Channel, COLOR, COLUMN, DESCRIPTION, DETAIL, ExtendedChannel, FACET, FILL, FILLOPACITY, HREF, isScaleChannel, isSecondaryRangeChannel, isXorY, KEY, LATITUDE, LATITUDE2, LONGITUDE, LONGITUDE2, OPACITY, ORDER, RADIUS, RADIUS2, ROW, SHAPE, SIZE, STROKE, STROKEDASH, STROKEOPACITY, STROKEWIDTH, TEXT, THETA, THETA2, TOOLTIP, URL, X, X2, Y, Y2} from './channel';import {getMarkConfig} from './compile/common';import {isCustomFormatType} from './compile/format';import {CompositeAggregate} from './compositemark';import {Config} from './config';import {DateTime, dateTimeToExpr, isDateTime} from './datetime';import {Encoding} from './encoding';import {ExprRef, isExprRef} from './expr';import {FormatMixins, Guide, GuideEncodingConditionalValueDef, TitleMixins} from './guide';import {ImputeParams} from './impute';import {Legend} from './legend';import * as log from './log';import {LogicalComposition} from './logical';import {isRectBasedMark, Mark, MarkDef} from './mark';import {Predicate} from './predicate';import {Scale, SCALE_CATEGORY_INDEX} from './scale';import {isSortByChannel, Sort, SortOrder} from './sort';import {isFacetFieldDef} from './spec/facet';import {StackOffset, StackProperties} from './stack';import { getTimeUnitParts, isLocalSingleTimeUnit, normalizeTimeUnit, TimeUnit, TimeUnitParams, timeUnitToString} from './timeunit';import {AggregatedFieldDef, WindowFieldDef} from './transform';import {getFullName, QUANTITATIVE, StandardType, Type} from './type';import { contains, flatAccessWithDatum, getFirstDefined, internalField, omit, removePathFromField, replacePathInField, titleCase} from './util';import {isSignalRef} from './vega.schema';
export type PrimitiveValue = number | string | boolean | null;
export type Value<ES extends ExprRef | SignalRef = ExprRef | SignalRef> = | PrimitiveValue | number[] | Gradient | Text | ES;
export interface ValueDef<V extends Value = Value> { value: V;}
export type PositionValueDef = ValueDef<number | 'width' | 'height' | ExprRef | SignalRef>;export type NumericValueDef = ValueDef<number | ExprRef | SignalRef>;
export type ValueDefWithCondition<F extends FieldDef<any> | DatumDef<any>, V extends Value = Value> = Partial< ValueDef<V | ExprRef | SignalRef>> & { condition?: | Conditional<F> | Conditional<ValueDef<V | ExprRef | SignalRef>> | Conditional<ValueDef<V | ExprRef | SignalRef>>[];};
export type StringValueDefWithCondition<F extends Field, T extends Type = StandardType> = ValueDefWithCondition< MarkPropFieldOrDatumDef<F, T>, string | null>;export type TypeForShape = 'nominal' | 'ordinal' | 'geojson';
export type Conditional<CD extends FieldDef<any> | DatumDef | ValueDef<any> | ExprRef | SignalRef> = | ConditionalPredicate<CD> | ConditionalSelection<CD>;
export type ConditionalPredicate<CD extends FieldDef<any> | DatumDef | ValueDef<any> | ExprRef | SignalRef> = { test: LogicalComposition<Predicate>;} & CD;
export type ConditionalSelection<CD extends FieldDef<any> | DatumDef | ValueDef<any> | ExprRef | SignalRef> = { selection: LogicalComposition<string>;} & CD;
export function isConditionalSelection<T>(c: Conditional<T>): c is ConditionalSelection<T> { return c['selection'];}
export interface ConditionValueDefMixins<V extends Value = Value> { condition?: Conditional<ValueDef<V>> | Conditional<ValueDef<V>>[];}
export type FieldOrDatumDefWithCondition<F extends FieldDef<any, any> | DatumDef<any>, V extends Value = Value> = F & ConditionValueDefMixins<V | ExprRef | SignalRef>;
export type MarkPropDef<F extends Field, V extends Value, T extends Type = StandardType> = | FieldOrDatumDefWithCondition<MarkPropFieldDef<F, T>, V> | FieldOrDatumDefWithCondition<DatumDef<F>, V> | ValueDefWithCondition<MarkPropFieldOrDatumDef<F, T>, V>;
export type ColorDef<F extends Field> = MarkPropDef<F, Gradient | string | null>;export type NumericMarkPropDef<F extends Field> = MarkPropDef<F, number>;
export type NumericArrayMarkPropDef<F extends Field> = MarkPropDef<F, number[]>;
export type ShapeDef<F extends Field> = MarkPropDef<F, string | null, TypeForShape>;
export type StringFieldDefWithCondition<F extends Field> = FieldOrDatumDefWithCondition<StringFieldDef<F>, string>;export type TextDef<F extends Field> = | FieldOrDatumDefWithCondition<StringFieldDef<F>, Text> | FieldOrDatumDefWithCondition<StringDatumDef<F>, Text> | ValueDefWithCondition<StringFieldDef<F>, Text>;
export interface RepeatRef { repeat: 'row' | 'column' | 'repeat' | 'layer';}
export type FieldName = string;export type Field = FieldName | RepeatRef;
export function isRepeatRef(field: Field | any): field is RepeatRef { return field && !isString(field) && 'repeat' in field;}
export type HiddenCompositeAggregate = CompositeAggregate;
export interface FieldDefBase<F, B extends Bin = Bin> extends BandMixins { field?: F;
timeUnit?: TimeUnit | TimeUnitParams;
aggregate?: Aggregate | HiddenCompositeAggregate;
bin?: B;}
export function toFieldDefBase(fieldDef: FieldDef<string>): FieldDefBase<string> { const {field, timeUnit, bin, aggregate} = fieldDef; return { ...(timeUnit ? {timeUnit} : {}), ...(bin ? {bin} : {}), ...(aggregate ? {aggregate} : {}), field };}
export interface TypeMixins<T extends Type> { type?: T;}
export type TypedFieldDef< F extends Field, T extends Type = any, B extends Bin = boolean | BinParams | 'binned' | null > = FieldDefBase<F, B> & TitleMixins & TypeMixins<T>;
export interface SortableFieldDef< F extends Field, T extends Type = StandardType, B extends Bin = boolean | BinParams | null> extends TypedFieldDef<F, T, B> { sort?: Sort<F>;}
export function isSortableFieldDef<F extends Field>(fieldDef: FieldDef<F>): fieldDef is SortableFieldDef<F> { return 'sort' in fieldDef;}
export type ScaleFieldDef< F extends Field, T extends Type = StandardType, B extends Bin = boolean | BinParams | null> = SortableFieldDef<F, T, B> & ScaleMixins;
export interface ScaleMixins { scale?: Scale | null;}
export interface DatumDef< F extends Field = string, V extends PrimitiveValue | DateTime | ExprRef | SignalRef = PrimitiveValue | DateTime | ExprRef | SignalRef> extends Partial<TypeMixins<Type>>, BandMixins { datum?: F extends RepeatRef ? V | RepeatRef : V; }
export type StringDatumDef<F extends Field = string> = DatumDef<F> & FormatMixins;
export type ScaleDatumDef<F extends Field = string> = ScaleMixins & DatumDef<F>;
export type SecondaryFieldDef<F extends Field> = FieldDefBase<F, null> & TitleMixins;
export type Position2Def<F extends Field> = SecondaryFieldDef<F> | DatumDef<F> | PositionValueDef;
export type SecondaryChannelDef<F extends Field> = Encoding<F>['x2' | 'y2'];
export type FieldDefWithoutScale<F extends Field, T extends Type = StandardType> = TypedFieldDef<F, T>;
export type LatLongFieldDef<F extends Field> = FieldDefBase<F, null> & TitleMixins & Partial<TypeMixins<'quantitative'>>;
export type LatLongDef<F extends Field> = LatLongFieldDef<F> | DatumDef<F> | NumericValueDef;
export type PositionFieldDefBase<F extends Field> = ScaleFieldDef< F, StandardType, boolean | BinParams | 'binned' | null > & PositionBaseMixins;
export type PositionDatumDefBase<F extends Field> = ScaleDatumDef<F> & PositionBaseMixins;
export interface PositionBaseMixins { stack?: StackOffset | null | boolean;}
export interface BandMixins { band?: number;}
export type PositionFieldDef<F extends Field> = PositionFieldDefBase<F> & PositionMixins;
export type PositionDatumDef<F extends Field> = PositionDatumDefBase<F> & PositionMixins;
export type PositionDef<F extends Field> = PositionFieldDef<F> | PositionDatumDef<F> | PositionValueDef;
export interface PositionMixins { axis?: Axis<ExprRef | SignalRef> | null;
impute?: ImputeParams | null;}
export type PolarDef<F extends Field> = PositionFieldDefBase<F> | PositionDatumDefBase<F> | PositionValueDef;
export function getBand({ channel, fieldDef, fieldDef2, markDef: mark, stack, config, isMidPoint}: { isMidPoint?: boolean; channel: Channel; fieldDef: FieldDef<string> | DatumDef; fieldDef2?: SecondaryChannelDef<string>; stack: StackProperties; markDef: MarkDef<Mark, SignalRef>; config: Config<SignalRef>;}): number { if (isFieldOrDatumDef(fieldDef) && fieldDef.band !== undefined) { return fieldDef.band; } if (isFieldDef(fieldDef)) { const {timeUnit, bin} = fieldDef;
if (timeUnit && !fieldDef2) { if (isMidPoint) { return getMarkConfig('timeUnitBandPosition', mark, config); } else { return isRectBasedMark(mark.type) ? getMarkConfig('timeUnitBand', mark, config) : 0; } } else if (isBinning(bin)) { return isRectBasedMark(mark.type) && !isMidPoint ? 1 : 0.5; } } if (stack?.fieldChannel === channel && isMidPoint) { return 0.5; } return undefined;}
export function hasBand( channel: Channel, fieldDef: FieldDef<string>, fieldDef2: SecondaryChannelDef<string>, stack: StackProperties, markDef: MarkDef<Mark, SignalRef>, config: Config<SignalRef>): boolean { if (isBinning(fieldDef.bin) || (fieldDef.timeUnit && isTypedFieldDef(fieldDef) && fieldDef.type === 'temporal')) { return !!getBand({channel, fieldDef, fieldDef2, stack, markDef, config}); } return false;}
export type MarkPropFieldDef<F extends Field, T extends Type = Type> = ScaleFieldDef<F, T, boolean | BinParams | null> & LegendMixins;
export type MarkPropDatumDef<F extends Field> = LegendMixins & ScaleDatumDef<F>;
export type MarkPropFieldOrDatumDef<F extends Field, T extends Type = Type> = | MarkPropFieldDef<F, T> | MarkPropDatumDef<F>;
export interface LegendMixins { legend?: Legend<ExprRef | SignalRef> | null;}
export interface OrderFieldDef<F extends Field> extends FieldDefWithoutScale<F> { sort?: SortOrder;}
export type OrderValueDef = ConditionValueDefMixins<number> & NumericValueDef;
export interface StringFieldDef<F extends Field> extends FieldDefWithoutScale<F, StandardType>, FormatMixins {}
export type FieldDef<F extends Field, T extends Type = any> = SecondaryFieldDef<F> | TypedFieldDef<F, T>;export type ChannelDef<F extends Field = string> = Encoding<F>[keyof Encoding<F>];
export function isConditionalDef<CD extends ChannelDef<any> | GuideEncodingConditionalValueDef | ExprRef | SignalRef>( channelDef: CD): channelDef is CD & {condition: Conditional<any>} { return !!channelDef && 'condition' in channelDef;}
export function hasConditionalFieldDef<F extends Field>( channelDef: Partial<ChannelDef<F>>): channelDef is {condition: Conditional<TypedFieldDef<F>>} { const condition = channelDef && channelDef['condition']; return !!condition && !isArray(condition) && isFieldDef(condition);}
export function hasConditionalFieldOrDatumDef<F extends Field>( channelDef: ChannelDef<F>): channelDef is {condition: Conditional<TypedFieldDef<F>>} { const condition = channelDef && channelDef['condition']; return !!condition && !isArray(condition) && isFieldOrDatumDef(condition);}
export function hasConditionalValueDef<F extends Field>( channelDef: ChannelDef<F>): channelDef is ValueDef<any> & {condition: Conditional<ValueDef<any>> | Conditional<ValueDef<any>>[]} { const condition = channelDef && channelDef['condition']; return !!condition && (isArray(condition) || isValueDef(condition));}
export function isFieldDef<F extends Field>( channelDef: Partial<ChannelDef<F>> | FieldDefBase<F> | DatumDef<F, any>): channelDef is FieldDefBase<F> | TypedFieldDef<F> | SecondaryFieldDef<F> { return !!channelDef && (!!channelDef['field'] || channelDef['aggregate'] === 'count');}
export function channelDefType<F extends Field>(channelDef: ChannelDef<F>): Type | undefined { return channelDef && channelDef['type'];}
export function isDatumDef<F extends Field>( channelDef: Partial<ChannelDef<F>> | FieldDefBase<F> | DatumDef<F, any>): channelDef is DatumDef<F, any> { return !!channelDef && 'datum' in channelDef;}
export function isContinuousFieldOrDatumDef<F extends Field>( cd: ChannelDef<F>): cd is TypedFieldDef<F> | DatumDef<F, number> { return (isTypedFieldDef(cd) && isContinuous(cd)) || isNumericDataDef(cd);}
export function isQuantitativeFieldOrDatumDef<F extends Field>(cd: ChannelDef<F>) { return channelDefType(cd) === 'quantitative' || isNumericDataDef(cd);}
export function isNumericDataDef<F extends Field>(cd: ChannelDef<F>): cd is DatumDef<F, number> { return isDatumDef(cd) && isNumber(cd.datum);}
export function isFieldOrDatumDef<F extends Field>( channelDef: Partial<ChannelDef<F>>): channelDef is FieldDef<F, any> | DatumDef<F> { return isFieldDef(channelDef) || isDatumDef(channelDef);}
export function isTypedFieldDef<F extends Field>(channelDef: ChannelDef<F>): channelDef is TypedFieldDef<F> { return !!channelDef && ('field' in channelDef || channelDef['aggregate'] === 'count') && 'type' in channelDef;}
export function isValueDef<F extends Field>(channelDef: Partial<ChannelDef<F>>): channelDef is ValueDef<any> { return channelDef && 'value' in channelDef && 'value' in channelDef;}
export function isScaleFieldDef<F extends Field>(channelDef: ChannelDef<F>): channelDef is ScaleFieldDef<F> { return !!channelDef && ('scale' in channelDef || 'sort' in channelDef);}
export function isPositionFieldOrDatumDef<F extends Field>( channelDef: ChannelDef<F>): channelDef is PositionFieldDef<F> | PositionDatumDef<F> { return channelDef && ('axis' in channelDef || 'stack' in channelDef || 'impute' in channelDef);}
export function isMarkPropFieldOrDatumDef<F extends Field>( channelDef: ChannelDef<F>): channelDef is MarkPropFieldDef<F, any> | MarkPropDatumDef<F> { return !!channelDef && 'legend' in channelDef;}
export function isStringFieldOrDatumDef<F extends Field>( channelDef: ChannelDef<F>): channelDef is StringFieldDef<F> | StringDatumDef<F> { return !!channelDef && ('format' in channelDef || 'formatType' in channelDef);}
export function toStringFieldDef<F extends Field>(fieldDef: FieldDef<F>): StringFieldDef<F> { return omit(fieldDef, ['legend', 'axis', 'header', 'scale'] as any[]);}
export interface FieldRefOption { nofn?: boolean; expr?: 'datum' | 'parent' | 'datum.datum'; prefix?: string; binSuffix?: 'end' | 'range' | 'mid'; suffix?: string; forAs?: boolean;}
function isOpFieldDef( fieldDef: FieldDefBase<string> | WindowFieldDef | AggregatedFieldDef): fieldDef is WindowFieldDef | AggregatedFieldDef { return 'op' in fieldDef;}
export function vgField( fieldDef: FieldDefBase<string> | WindowFieldDef | AggregatedFieldDef, opt: FieldRefOption = {}): string { let field = fieldDef.field; const prefix = opt.prefix; let suffix = opt.suffix;
let argAccessor = '';
if (isCount(fieldDef)) { field = internalField('count'); } else { let fn: string;
if (!opt.nofn) { if (isOpFieldDef(fieldDef)) { fn = fieldDef.op; } else { const {bin, aggregate, timeUnit} = fieldDef; if (isBinning(bin)) { fn = binToString(bin); suffix = (opt.binSuffix ?? '') + (opt.suffix ?? ''); } else if (aggregate) { if (isArgmaxDef(aggregate)) { argAccessor = `["${field}"]`; field = `argmax_${aggregate.argmax}`; } else if (isArgminDef(aggregate)) { argAccessor = `["${field}"]`; field = `argmin_${aggregate.argmin}`; } else { fn = String(aggregate); } } else if (timeUnit) { fn = timeUnitToString(timeUnit); suffix = ((!contains(['range', 'mid'], opt.binSuffix) && opt.binSuffix) || '') + (opt.suffix ?? ''); } } }
if (fn) { field = field ? `${fn}_${field}` : fn; } }
if (suffix) { field = `${field}_${suffix}`; }
if (prefix) { field = `${prefix}_${field}`; }
if (opt.forAs) { return removePathFromField(field); } else if (opt.expr) { return flatAccessWithDatum(field, opt.expr) + argAccessor; } else { return replacePathInField(field) + argAccessor; }}
export function isDiscrete(def: TypedFieldDef<Field> | DatumDef<any, any>) { switch (def.type) { case 'nominal': case 'ordinal': case 'geojson': return true; case 'quantitative': return isFieldDef(def) && !!def.bin; case 'temporal': return false; } throw new Error(log.message.invalidFieldType(def.type));}
export function isContinuous(fieldDef: TypedFieldDef<Field>) { return !isDiscrete(fieldDef);}
export function isCount(fieldDef: FieldDefBase<Field>) { return fieldDef.aggregate === 'count';}
export type FieldTitleFormatter = (fieldDef: FieldDefBase<string>, config: Config) => string;
export function verbalTitleFormatter(fieldDef: FieldDefBase<string>, config: Config) { const {field, bin, timeUnit, aggregate} = fieldDef; if (aggregate === 'count') { return config.countTitle; } else if (isBinning(bin)) { return `${field} (binned)`; } else if (timeUnit) { const unit = normalizeTimeUnit(timeUnit)?.unit; if (unit) { return `${field} (${getTimeUnitParts(unit).join('-')})`; } } else if (aggregate) { if (isArgmaxDef(aggregate)) { return `${field} for max ${aggregate.argmax}`; } else if (isArgminDef(aggregate)) { return `${field} for min ${aggregate.argmin}`; } else { return `${titleCase(aggregate)} of ${field}`; } } return field;}
export function functionalTitleFormatter(fieldDef: FieldDefBase<string>) { const {aggregate, bin, timeUnit, field} = fieldDef; if (isArgmaxDef(aggregate)) { return `${field} for argmax(${aggregate.argmax})`; } else if (isArgminDef(aggregate)) { return `${field} for argmin(${aggregate.argmin})`; }
const timeUnitParams = normalizeTimeUnit(timeUnit);
const fn = aggregate || timeUnitParams?.unit || (timeUnitParams?.maxbins && 'timeunit') || (isBinning(bin) && 'bin'); if (fn) { return fn.toUpperCase() + '(' + field + ')'; } else { return field; }}
export const defaultTitleFormatter: FieldTitleFormatter = (fieldDef: FieldDefBase<string>, config: Config) => { switch (config.fieldTitle) { case 'plain': return fieldDef.field; case 'functional': return functionalTitleFormatter(fieldDef); default: return verbalTitleFormatter(fieldDef, config); }};
let titleFormatter = defaultTitleFormatter;
export function setTitleFormatter(formatter: FieldTitleFormatter) { titleFormatter = formatter;}
export function resetTitleFormatter() { setTitleFormatter(defaultTitleFormatter);}
export function title( fieldOrDatumDef: TypedFieldDef<string> | SecondaryFieldDef<string> | DatumDef, config: Config, {allowDisabling, includeDefault = true}: {allowDisabling: boolean; includeDefault?: boolean}) { const guideTitle = getGuide(fieldOrDatumDef)?.title;
if (!isFieldDef(fieldOrDatumDef)) { return guideTitle; } const fieldDef = fieldOrDatumDef;
const def = includeDefault ? defaultTitle(fieldDef, config) : undefined;
if (allowDisabling) { return getFirstDefined(guideTitle, fieldDef.title, def); } else { return guideTitle ?? fieldDef.title ?? def; }}
export function getGuide(fieldDef: TypedFieldDef<string> | SecondaryFieldDef<string> | DatumDef): Guide { if (isPositionFieldOrDatumDef(fieldDef) && fieldDef.axis) { return fieldDef.axis; } else if (isMarkPropFieldOrDatumDef(fieldDef) && fieldDef.legend) { return fieldDef.legend; } else if (isFacetFieldDef(fieldDef) && fieldDef.header) { return fieldDef.header; } return undefined;}
export function defaultTitle(fieldDef: FieldDefBase<string>, config: Config) { return titleFormatter(fieldDef, config);}
export function getFormatMixins(fieldDef: TypedFieldDef<string> | DatumDef) { if (isStringFieldOrDatumDef(fieldDef)) { const {format, formatType} = fieldDef; return {format, formatType}; } else { const guide = getGuide(fieldDef) ?? {}; const {format, formatType} = guide; return {format, formatType}; }}
export function defaultType<T extends TypedFieldDef<Field>>(fieldDef: T, channel: ExtendedChannel): Type { switch (channel) { case 'latitude': case 'longitude': return 'quantitative';
case 'row': case 'column': case 'facet': case 'shape': case 'strokeDash': return 'nominal';
case 'order': return 'ordinal'; }
if (isSortableFieldDef(fieldDef) && isArray(fieldDef.sort)) { return 'ordinal'; }
const {aggregate, bin, timeUnit} = fieldDef; if (timeUnit) { return 'temporal'; }
if (bin || (aggregate && !isArgmaxDef(aggregate) && !isArgminDef(aggregate))) { return 'quantitative'; }
if (isScaleFieldDef(fieldDef) && fieldDef.scale?.type) { switch (SCALE_CATEGORY_INDEX[fieldDef.scale.type]) { case 'numeric': case 'discretizing': return 'quantitative'; case 'time': return 'temporal'; } }
return 'nominal';}
export function getFieldDef<F extends Field>(channelDef: ChannelDef<F>): FieldDef<F> { if (isFieldDef(channelDef)) { return channelDef; } else if (hasConditionalFieldDef(channelDef)) { return channelDef.condition; } return undefined;}
export function getFieldOrDatumDef<F extends Field = string, CD extends ChannelDef<F> = ChannelDef<F>>( channelDef: CD): FieldDef<F> | DatumDef<F> { if (isFieldOrDatumDef<F>(channelDef)) { return channelDef; } else if (hasConditionalFieldOrDatumDef(channelDef)) { return channelDef.condition; } return undefined;}
export function initChannelDef( channelDef: ChannelDef<string>, channel: ExtendedChannel, config: Config, opt: {compositeMark?: boolean} = {}): ChannelDef<string> { if (isString(channelDef) || isNumber(channelDef) || isBoolean(channelDef)) { const primitiveType = isString(channelDef) ? 'string' : isNumber(channelDef) ? 'number' : 'boolean'; log.warn(log.message.primitiveChannelDef(channel, primitiveType, channelDef)); return {value: channelDef} as ValueDef<any>; }
if (isFieldOrDatumDef(channelDef)) { return initFieldOrDatumDef(channelDef, channel, config, opt); } else if (hasConditionalFieldOrDatumDef(channelDef)) { return { ...channelDef, condition: initFieldOrDatumDef(channelDef.condition, channel, config, opt) as Conditional<TypedFieldDef<string>> }; } return channelDef;}
export function initFieldOrDatumDef( fd: FieldDef<string, any> | DatumDef, channel: ExtendedChannel, config: Config, opt: {compositeMark?: boolean}): FieldDef<string, any> | DatumDef { if (isStringFieldOrDatumDef(fd)) { const {format, formatType, ...rest} = fd; if (isCustomFormatType(formatType) && !config.customFormatTypes) { log.warn(log.message.customFormatTypeNotAllowed(channel)); return initFieldOrDatumDef(rest, channel, config, opt); } } else { const guideType = isPositionFieldOrDatumDef(fd) ? 'axis' : isMarkPropFieldOrDatumDef(fd) ? 'legend' : isFacetFieldDef(fd) ? 'header' : null; if (guideType && fd[guideType]) { const {format, formatType, ...newGuide} = fd[guideType]; if (isCustomFormatType(formatType) && !config.customFormatTypes) { log.warn(log.message.customFormatTypeNotAllowed(channel)); return initFieldOrDatumDef({...fd, [guideType]: newGuide}, channel, config, opt); } } }
if (isFieldDef(fd)) { return initFieldDef(fd, channel, opt); } return initDatumDef(fd);}
function initDatumDef(datumDef: DatumDef): DatumDef { let type = datumDef['type']; if (type) { return datumDef; } const {datum} = datumDef; type = isNumber(datum) ? 'quantitative' : isString(datum) ? 'nominal' : isDateTime(datum) ? 'temporal' : undefined;
return {...datumDef, type};}
export function initFieldDef( fd: FieldDef<string, any>, channel: ExtendedChannel, {compositeMark = false}: {compositeMark?: boolean} = {}) { const {aggregate, timeUnit, bin, field} = fd; const fieldDef = {...fd};
if (!compositeMark && aggregate && !isAggregateOp(aggregate) && !isArgmaxDef(aggregate) && !isArgminDef(aggregate)) { log.warn(log.message.invalidAggregate(aggregate)); delete fieldDef.aggregate; }
if (timeUnit) { fieldDef.timeUnit = normalizeTimeUnit(timeUnit); }
if (field) { fieldDef.field = `${field}`; }
if (isBinning(bin)) { fieldDef.bin = normalizeBin(bin, channel); }
if (isBinned(bin) && !isXorY(channel)) { log.warn(log.message.channelShouldNotBeUsedForBinned(channel)); }
if (isTypedFieldDef(fieldDef)) { const {type} = fieldDef; const fullType = getFullName(type); if (type !== fullType) { fieldDef.type = fullType; } if (type !== 'quantitative') { if (isCountingAggregateOp(aggregate)) { log.warn(log.message.invalidFieldTypeForCountAggregate(type, aggregate)); fieldDef.type = 'quantitative'; } } } else if (!isSecondaryRangeChannel(channel)) { const newType = defaultType(fieldDef as TypedFieldDef<any>, channel); fieldDef['type'] = newType; }
if (isTypedFieldDef(fieldDef)) { const {compatible, warning} = channelCompatibility(fieldDef, channel) || {}; if (compatible === false) { log.warn(warning); } }
if (isSortableFieldDef(fieldDef) && isString(fieldDef.sort)) { const {sort} = fieldDef; if (isSortByChannel(sort)) { return { ...fieldDef, sort: {encoding: sort} }; } const sub = sort.substr(1); if (sort.charAt(0) === '-' && isSortByChannel(sub)) { return { ...fieldDef, sort: {encoding: sub, order: 'descending'} }; } }
if (isFacetFieldDef(fieldDef)) { const {header} = fieldDef; const {orient, ...rest} = header; if (orient) { return { ...fieldDef, header: { ...rest, labelOrient: header.labelOrient || orient, titleOrient: header.titleOrient || orient } }; } }
return fieldDef;}
export function normalizeBin(bin: BinParams | boolean | 'binned', channel?: ExtendedChannel) { if (isBoolean(bin)) { return {maxbins: autoMaxBins(channel)}; } else if (bin === 'binned') { return { binned: true }; } else if (!bin.maxbins && !bin.step) { return {...bin, maxbins: autoMaxBins(channel)}; } else { return bin; }}
const COMPATIBLE = {compatible: true};export function channelCompatibility( fieldDef: TypedFieldDef<Field>, channel: ExtendedChannel): {compatible: boolean; warning?: string} { const type = fieldDef.type;
if (type === 'geojson' && channel !== 'shape') { return { compatible: false, warning: `Channel ${channel} should not be used with a geojson data.` }; }
switch (channel) { case ROW: case COLUMN: case FACET: if (isContinuous(fieldDef)) { return { compatible: false, warning: log.message.facetChannelShouldBeDiscrete(channel) }; } return COMPATIBLE;
case X: case Y: case COLOR: case FILL: case STROKE: case TEXT: case DETAIL: case KEY: case TOOLTIP: case HREF: case URL: case ANGLE: case THETA: case RADIUS: case DESCRIPTION: return COMPATIBLE;
case LONGITUDE: case LONGITUDE2: case LATITUDE: case LATITUDE2: if (type !== QUANTITATIVE) { return { compatible: false, warning: `Channel ${channel} should be used with a quantitative field only, not ${fieldDef.type} field.` }; } return COMPATIBLE;
case OPACITY: case FILLOPACITY: case STROKEOPACITY: case STROKEWIDTH: case SIZE: case THETA2: case RADIUS2: case X2: case Y2: if (type === 'nominal' && !fieldDef['sort']) { return { compatible: false, warning: `Channel ${channel} should not be used with an unsorted discrete field.` }; } return COMPATIBLE;
case STROKEDASH: if (!contains(['ordinal', 'nominal'], fieldDef.type)) { return { compatible: false, warning: 'StrokeDash channel should be used with only discrete data.' }; } return COMPATIBLE;
case SHAPE: if (!contains(['ordinal', 'nominal', 'geojson'], fieldDef.type)) { return { compatible: false, warning: 'Shape channel should be used with only either discrete or geojson data.' }; } return COMPATIBLE;
case ORDER: if (fieldDef.type === 'nominal' && !('sort' in fieldDef)) { return { compatible: false, warning: `Channel order is inappropriate for nominal field, which has no inherent order.` }; } return COMPATIBLE; }}
export function isFieldOrDatumDefForTimeFormat(fieldOrDatumDef: FieldDef<string> | DatumDef): boolean { const {formatType} = getFormatMixins(fieldOrDatumDef); return formatType === 'time' || (!formatType && isTimeFieldDef(fieldOrDatumDef));}
export function isTimeFieldDef(def: FieldDef<any> | DatumDef): boolean { return def && (def['type'] === 'temporal' || (isFieldDef(def) && !!def.timeUnit));}
export function valueExpr( v: number | string | boolean | DateTime | ExprRef | SignalRef | number[], { timeUnit, type, wrapTime, undefinedIfExprNotRequired }: { timeUnit: TimeUnit | TimeUnitParams; type?: Type; wrapTime?: boolean; undefinedIfExprNotRequired?: boolean; }): string { const unit = timeUnit && normalizeTimeUnit(timeUnit)?.unit; let isTime = unit || type === 'temporal';
let expr; if (isExprRef(v)) { expr = v.expr; } else if (isSignalRef(v)) { expr = v.signal; } else if (isDateTime(v)) { isTime = true; expr = dateTimeToExpr(v); } else if (isString(v) || isNumber(v)) { if (isTime) { expr = `datetime(${JSON.stringify(v)})`;
if (isLocalSingleTimeUnit(unit)) { if ((isNumber(v) && v < 10000) || (isString(v) && isNaN(Date.parse(v)))) { expr = dateTimeToExpr({[unit]: v}); } } } } if (expr) { return wrapTime && isTime ? `time(${expr})` : expr; } return undefinedIfExprNotRequired ? undefined : JSON.stringify(v);}
export function valueArray( fieldOrDatumDef: TypedFieldDef<string> | DatumDef, values: (number | string | boolean | DateTime)[]) { const {type} = fieldOrDatumDef; return values.map(v => { const expr = valueExpr(v, { timeUnit: isFieldDef(fieldOrDatumDef) ? fieldOrDatumDef.timeUnit : undefined, type, undefinedIfExprNotRequired: true }); if (expr !== undefined) { return {signal: expr}; } return v; });}
export function binRequiresRange(fieldDef: FieldDef<string>, channel: Channel): boolean { if (!isBinning(fieldDef.bin)) { console.warn('Only call this method for binned field defs.'); return false; }
return isScaleChannel(channel) && contains(['ordinal', 'nominal'], (fieldDef as ScaleFieldDef<string>).type);}