import { toMap } from 'datalib/src/util' ; import { ExtendedChannel } from 'vega-lite/build/src/channel' ; import { Config } from 'vega-lite/build/src/config' ; import { Data } from 'vega-lite/build/src/data' ; import { Mark } from 'vega-lite/build/src/mark' ; import { FacetedUnitSpec , TopLevel } from 'vega-lite/build/src/spec' ; import { Step } from 'vega-lite/build/src/spec/base' ; import { stack , StackOffset , StackProperties } from 'vega-lite/build/src/stack' ; import { TitleParams } from 'vega-lite/build/src/title' ; import { ALL_ENCODING_PROPS , getEncodingNestedProp , isEncodingTopLevelProperty , Property , toKey } from '../property' ; import { contains , extend , isObject , keys , some , without } from '../util' ; import { isWildcard , WildcardProperty , Wildcard } from '../wildcard' ; import { EncodingQuery , isDisabledAutoCountQuery , isEnabledAutoCountQuery , isFieldQuery , toEncoding } from './encoding' ; import { TransformQuery } from './transform' ; /**
 * A "query" version of a [Vega-Lite](https://github.com/vega/vega-lite)'s `UnitSpec` (single view specification).
 * This interface and most of  its children have `Query` suffixes to hint that their instanced are queries that
 * can contain wildcards to describe a collection of specifications.
 */ export interface SpecQuery { data ? : Data ; mark : WildcardProperty < Mark > ; transform ? : TransformQuery [ ] ; /**
     * Array of encoding query mappings.
     * Note: Vega-Lite's `encoding` is an object whose keys are unique encoding channels.
     * However, for CompassQL, the `channel` property of encoding query mappings can be wildcards.
     * Thus the `encoding` object in Vega-Lite is flatten as the `encodings` array in CompassQL.
     */ encodings : EncodingQuery [ ] ; /**
     * The width of the resulting encodings.
     * __NOTE:__ Does not support wildcards.
     */ width ? : number | 'container' | Step | Wildcard < Step > ; /**
     * The height of the resulting encodings.
     * __NOTE:__ Does not support wildcards.
     */ height ? : number | 'container' | Step | Wildcard < Step > ; /**
     * CSS color property to use as the background of visualization.
     * __NOTE:__ Does not support wildcards.
     */ background ? : string ; /**
     * The default visualization padding, in pixels, from the edge of the
     * visualization canvas to the data rectangle. If a number, specifies
     * padding for all sides. If an object, the value should have the
     * format {"left": 5, "top": 5, "right": 5, "bottom": 5}
     * to specify padding for each side of the visualization.
     *
     * __NOTE:__ Does not support wildcards.
     */ padding ? : number | Object ; /**
     * Title for the plot.
     * __NOTE:__ Does not support wildcards.
     */ title ? : string | TitleParams < any > ; /**
     * Vega-Lite Configuration
     */ config ? : Config ; } param { ExtendedUnitSpec } spec returns export function fromSpec ( spec ) { return extend ( spec . data ? { data : spec . data } : { } , spec . transform ? { transform : spec . transform } : { } , spec . width ? { width : spec . width } : { } , spec . height ? { height : spec . height } : { } , spec . background ? { background : spec . background } : { } , spec . padding ? { padding : spec . padding } : { } , spec . title ? { title : spec . title } : { } , { mark : spec . mark , encodings : keys ( spec . encoding ) . map ( ( channel ) => { const encQ = { channel : channel } ; const channelDef = spec . encoding [ channel ] ; for ( const prop in channelDef ) { if ( isEncodingTopLevelProperty ( prop as Property ) && channelDef [ prop ] !== undefined ) { if ( contains ( [ 'bin' , 'scale' , 'axis' , 'legend' ] , prop ) && channelDef [ prop ] === null ) { encQ [ prop ] = false ; } else { encQ [ prop ] = channelDef [ prop ] ; } } } if ( isFieldQuery ( encQ ) && encQ . aggregate === 'count' && ! encQ . field ) { encQ . field = '*' ; } return encQ ; } ) , } , spec . config ? { config : spec . config } : { } ) ; } export function isAggregate ( specQ ) { return some ( specQ . encodings , ( encQ ) => { return ( isFieldQuery ( encQ ) && ! isWildcard ( encQ . aggregate ) && ! ! encQ . aggregate ) || isEnabledAutoCountQuery ( encQ ) ; } ) ; } return export function getVlStack ( specQ ) { if ( ! hasRequiredStackProperties ( specQ ) ) { return null ; } const encoding = toEncoding ( specQ . encodings , { schema : null , wildcardMode : 'null' } ) ; const mark = specQ . mark as Mark ; return stack ( mark , encoding , { disallowNonLinearStack : true } ) ; } return export function getStackOffset ( specQ ) { for ( const encQ of specQ . encodings ) { if ( encQ [ Property . STACK ] !== undefined && ! isWildcard ( encQ [ Property . STACK ] ) ) { return encQ [ Property . STACK ] ; } } return undefined ; } return export function getStackChannel ( specQ ) { for ( const encQ of specQ . encodings ) { if ( encQ [ Property . STACK ] !== undefined && ! isWildcard ( encQ . channel ) ) { return encQ . channel ; } } return null ; } param specQ export function hasRequiredStackProperties ( specQ ) { if ( isWildcard ( specQ . mark ) ) { return false ; } const requiredEncodingProps = [ Property . STACK , Property . CHANNEL , Property . MARK , Property . FIELD , Property . AGGREGATE , Property . AUTOCOUNT , Property . SCALE , getEncodingNestedProp ( 'scale' , 'type' ) , Property . TYPE , ] ; const exclude = toMap ( without ( ALL_ENCODING_PROPS , requiredEncodingProps ) ) ; const encodings = specQ . encodings . filter ( ( encQ ) => ! isDisabledAutoCountQuery ( encQ ) ) ; for ( const encQ of encodings ) { if ( objectContainsWildcard ( encQ , { exclude : exclude } ) ) { return false ; } } return true ; } param obj param opt function objectContainsWildcard ( obj , opt = { } ) { if ( ! isObject ( obj ) ) { return false ; } for ( const childProp in obj ) { if ( obj . hasOwnProperty ( childProp ) ) { const wildcard = isWildcard ( obj [ childProp ] ) ; if ( ( wildcard && ( ! opt . exclude || ! opt . exclude [ childProp ] ) ) || objectContainsWildcard ( obj [ childProp ] , opt ) ) { return true ; } } } return false ; } param specQ param opt export function hasWildcard ( specQ , opt = { } ) { const exclude = opt . exclude ? toMap ( opt . exclude . map ( toKey ) ) : { } ; if ( isWildcard ( specQ . mark ) && ! exclude [ 'mark' ] ) { return true ; } for ( const encQ of specQ . encodings ) { if ( objectContainsWildcard ( encQ , exclude ) ) { return true ; } } return false ; }