UNPKG

vega-lite

Version:

Vega-Lite is a concise high-level language for interactive visualization.

159 lines (141 loc) 4.21 kB
import * as log from '../log/index.js'; import {deepEqual, duplicate, getFirstDefined, keys} from '../util.js'; /** * Generic class for storing properties that are explicitly specified * and implicitly determined by the compiler. * This is important for scale/axis/legend merging as * we want to prioritize properties that users explicitly specified. */ export class Split<T extends object> { constructor( public readonly explicit: Partial<T> = {}, public readonly implicit: Partial<T> = {}, ) {} public clone() { return new Split(duplicate(this.explicit), duplicate(this.implicit)); } public combine(): Partial<T> { return { ...this.explicit, // Explicit properties comes first ...this.implicit, }; } public get<K extends keyof T>(key: K): T[K] { // Explicit has higher precedence return getFirstDefined(this.explicit[key], this.implicit[key]); } public getWithExplicit<K extends keyof T>(key: K): Explicit<T[K]> { // Explicit has higher precedence if (this.explicit[key] !== undefined) { return {explicit: true, value: this.explicit[key]}; } else if (this.implicit[key] !== undefined) { return {explicit: false, value: this.implicit[key]}; } return {explicit: false, value: undefined}; } public setWithExplicit<K extends keyof T>(key: K, {value, explicit}: Explicit<T[K]>) { if (value !== undefined) { this.set(key, value, explicit); } } public set<K extends keyof T>(key: K, value: T[K], explicit: boolean) { delete this[explicit ? 'implicit' : 'explicit'][key]; this[explicit ? 'explicit' : 'implicit'][key] = value; return this; } public copyKeyFromSplit<S extends T>(key: keyof T, {explicit, implicit}: Split<S>) { // Explicit has higher precedence if (explicit[key] !== undefined) { this.set(key, explicit[key], true); } else if (implicit[key] !== undefined) { this.set(key, implicit[key], false); } } public copyKeyFromObject<S extends T>(key: keyof T, s: Partial<S>) { // Explicit has higher precedence if (s[key] !== undefined) { this.set(key, s[key], true); } } /** * Merge split object into this split object. Properties from the other split * overwrite properties from this split. */ public copyAll(other: Split<T>) { for (const key of keys(other.combine())) { const val = other.getWithExplicit(key); this.setWithExplicit(key, val); } } } export interface Explicit<T> { explicit: boolean; value: T; } export function makeExplicit<T>(value: T): Explicit<T> { return { explicit: true, value, }; } export function makeImplicit<T>(value: T): Explicit<T> { return { explicit: false, value, }; } export type SplitParentProperty = 'scale' | 'axis' | 'legend' | ''; export function tieBreakByComparing<S, T>(compare: (v1: T, v2: T) => number) { return ( v1: Explicit<T>, v2: Explicit<T>, property: keyof S | never, propertyOf: SplitParentProperty, ): Explicit<T> => { const diff = compare(v1.value, v2.value); if (diff > 0) { return v1; } else if (diff < 0) { return v2; } return defaultTieBreaker<S, T>(v1, v2, property, propertyOf); }; } export function defaultTieBreaker<S, T>( v1: Explicit<T>, v2: Explicit<T>, property: keyof S, propertyOf: SplitParentProperty, ) { if (v1.explicit && v2.explicit) { log.warn(log.message.mergeConflictingProperty(property, propertyOf, v1.value, v2.value)); } // If equal score, prefer v1. return v1; } export function mergeValuesWithExplicit<S, T>( v1: Explicit<T>, v2: Explicit<T>, property: keyof S, propertyOf: SplitParentProperty, tieBreaker: ( v1: Explicit<T>, v2: Explicit<T>, property: keyof S, propertyOf: string, ) => Explicit<T> = defaultTieBreaker, ) { if (v1 === undefined || v1.value === undefined) { // For first run return v2; } if (v1.explicit && !v2.explicit) { return v1; } else if (v2.explicit && !v1.explicit) { return v2; } else if (deepEqual(v1.value, v2.value)) { return v1; } else { return tieBreaker(v1, v2, property, propertyOf); } }