vega-lite
Version:
Vega-Lite is a concise high-level language for interactive visualization.
120 lines (105 loc) • 4.12 kB
text/typescript
import {ScaleType, SignalRef} from 'vega';
import {array} from 'vega-util';
import {AxisConfig} from '../../axis.js';
import {PositionScaleChannel} from '../../channel.js';
import {Config, StyleConfigIndex} from '../../config.js';
import {isQuantitative} from '../../scale.js';
import {keys, titleCase} from '../../util.js';
import {isSignalRef} from '../../vega.schema.js';
import {getStyleConfig, signalOrStringValue} from '../common.js';
function getAxisConfigFromConfigTypes(
configTypes: string[],
config: Config,
channel: 'x' | 'y',
orient: string | SignalRef,
) {
// TODO: add special casing to add conditional value based on orient signal
return Object.assign.apply(null, [
{},
...configTypes.map((configType) => {
if (configType === 'axisOrient') {
const orient1 = channel === 'x' ? 'bottom' : 'left';
const orientConfig1 = config[channel === 'x' ? 'axisBottom' : 'axisLeft'] || {};
const orientConfig2 = config[channel === 'x' ? 'axisTop' : 'axisRight'] || {};
const props = new Set([...keys(orientConfig1), ...keys(orientConfig2)]);
const conditionalOrientAxisConfig = {};
for (const prop of props.values()) {
(conditionalOrientAxisConfig as any)[prop] = {
// orient is surely signal in this case
signal: `${(orient as any)['signal']} === "${orient1}" ? ${signalOrStringValue(
orientConfig1[prop],
)} : ${signalOrStringValue(orientConfig2[prop])}`,
};
}
return conditionalOrientAxisConfig;
}
return (config as any)[configType];
}),
]);
}
export type AxisConfigs = ReturnType<typeof getAxisConfigs>;
export function getAxisConfigs(
channel: PositionScaleChannel,
scaleType: ScaleType,
orient: string | SignalRef,
config: Config,
) {
const typeBasedConfigTypes =
scaleType === 'band'
? ['axisDiscrete', 'axisBand']
: scaleType === 'point'
? ['axisDiscrete', 'axisPoint']
: isQuantitative(scaleType)
? ['axisQuantitative']
: scaleType === 'time' || scaleType === 'utc'
? ['axisTemporal']
: [];
const axisChannel = channel === 'x' ? 'axisX' : 'axisY';
const axisOrient = isSignalRef(orient) ? 'axisOrient' : `axis${titleCase(orient)}`; // axisTop, axisBottom, ...
const vlOnlyConfigTypes = [
// technically Vega does have axisBand, but if we make another separation here,
// it will further introduce complexity in the code
...typeBasedConfigTypes,
...typeBasedConfigTypes.map((c) => axisChannel + c.substr(4)),
];
const vgConfigTypes = ['axis', axisOrient, axisChannel];
return {
vlOnlyAxisConfig: getAxisConfigFromConfigTypes(vlOnlyConfigTypes, config, channel, orient),
vgAxisConfig: getAxisConfigFromConfigTypes(vgConfigTypes, config, channel, orient),
axisConfigStyle: getAxisConfigStyle([...vgConfigTypes, ...vlOnlyConfigTypes], config),
};
}
export function getAxisConfigStyle(axisConfigTypes: string[], config: Config) {
const toMerge = [{}];
for (const configType of axisConfigTypes) {
// TODO: add special casing to add conditional value based on orient signal
let style = (config as any)[configType]?.style;
if (style) {
style = array(style);
for (const s of style) {
toMerge.push(config.style[s]);
}
}
}
return Object.assign.apply(null, toMerge);
}
export function getAxisConfig(
property: keyof AxisConfig<SignalRef>,
styleConfigIndex: StyleConfigIndex<SignalRef>,
style: string | string[],
axisConfigs: Partial<AxisConfigs> = {},
): {configFrom?: string; configValue?: any} {
const styleConfig = getStyleConfig(property, style, styleConfigIndex);
if (styleConfig !== undefined) {
return {
configFrom: 'style',
configValue: styleConfig,
};
}
for (const configFrom of ['vlOnlyAxisConfig', 'vgAxisConfig', 'axisConfigStyle'] as const) {
if (axisConfigs[configFrom]?.[property] !== undefined) {
return {configFrom, configValue: axisConfigs[configFrom][property]};
}
}
return {};
}