Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add timeFormatType field #8320

Merged
merged 16 commits into from Aug 4, 2022
4 changes: 4 additions & 0 deletions build/vega-lite-schema.json
Expand Up @@ -7725,6 +7725,10 @@
"description": "Default time format for raw time values (without time units) in text marks, legend labels and header labels.\n\n__Default value:__ `\"%b %d, %Y\"` __Note:__ Axes automatically determine the format for each label automatically so this config does not affect axes.",
"type": "string"
},
"timeFormatType": {
"description": "[Custom format type](https://vega.github.io/vega-lite/docs/config.html#custom-format-type) for `config.numberFormat`.\n\n__Default value:__ `undefined` -- This is equilvalent to call D3-format, which is exposed as [`format` in Vega-Expression](https://vega.github.io/vega/docs/expressions/#format). __Note:__ You must also set `customFormatTypes` to `true` to use this feature.",
"type": "string"
},
"title": {
"$ref": "#/definitions/TitleConfig",
"description": "Title configuration, which determines default properties for all [titles](https://vega.github.io/vega-lite/docs/title.html). For a full list of title configuration options, please see the [corresponding section of the title documentation](https://vega.github.io/vega-lite/docs/title.html#config)."
Expand Down
Binary file modified examples/compiled/config_numberFormatType_test.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion examples/compiled/config_numberFormatType_test.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions examples/compiled/config_numberFormatType_test.vg.json
Expand Up @@ -135,6 +135,13 @@
"labelFlush": true,
"labelOverlap": true,
"tickCount": {"signal": "ceil(child_width/40)"},
"encode": {
"labels": {
"update": {
"text": {"signal": "undefined(datum.value, \"%b %d, %Y\")"}
}
}
},
"zindex": 0
}
]
Expand Down
57 changes: 33 additions & 24 deletions src/compile/axis/encode.ts
Expand Up @@ -22,34 +22,43 @@ export function labels(model: UnitModel, channel: PositionScaleChannel, specifie
}),
...specifiedLabelsSpec
};
} else if (
format === undefined &&
formatType === undefined &&
channelDefType(fieldOrDatumDef) === 'quantitative' &&
config.customFormatTypes
) {
if (
isPositionFieldOrDatumDef(fieldOrDatumDef) &&
fieldOrDatumDef.stack === 'normalize' &&
config.normalizedNumberFormatType
) {
return {
text: formatCustomType({
fieldOrDatumDef,
field: 'datum.value',
format: config.normalizedNumberFormat,
formatType: config.normalizedNumberFormatType,
config
}),
...specifiedLabelsSpec
};
} else if (config.numberFormatType) {
} else if (format === undefined && formatType === undefined && config.customFormatTypes) {
if (channelDefType(fieldOrDatumDef) === 'quantitative') {
if (
isPositionFieldOrDatumDef(fieldOrDatumDef) &&
fieldOrDatumDef.stack === 'normalize' &&
config.normalizedNumberFormatType
) {
return {
text: formatCustomType({
fieldOrDatumDef,
field: 'datum.value',
format: config.normalizedNumberFormat,
formatType: config.normalizedNumberFormatType,
config
}),
...specifiedLabelsSpec
};
} else if (config.numberFormatType) {
return {
text: formatCustomType({
fieldOrDatumDef,
field: 'datum.value',
format: config.numberFormat,
formatType: config.numberFormatType,
config
}),
...specifiedLabelsSpec
};
}
}
if (channelDefType(fieldOrDatumDef) === 'temporal') {
return {
text: formatCustomType({
fieldOrDatumDef,
field: 'datum.value',
format: config.numberFormat,
formatType: config.numberFormatType,
format: config.timeFormat,
formatType: config.timeFormatType,
config
}),
...specifiedLabelsSpec
Expand Down
119 changes: 64 additions & 55 deletions src/compile/format.ts
Expand Up @@ -59,47 +59,48 @@ export function formatSignalRef({
const field = fieldToFormat(fieldOrDatumDef, expr, normalizeStack);
const type = channelDefType(fieldOrDatumDef);

if (
normalizeStack &&
type === 'quantitative' &&
format === undefined &&
formatType === undefined &&
config.customFormatTypes &&
config.normalizedNumberFormatType
) {
return formatCustomType({
fieldOrDatumDef,
format: config.normalizedNumberFormat,
formatType: config.normalizedNumberFormatType,
expr,
config
});
}

if (
type === 'quantitative' &&
format === undefined &&
formatType === undefined &&
config.customFormatTypes &&
config.numberFormatType
) {
return formatCustomType({
fieldOrDatumDef,
format: config.numberFormat,
formatType: config.numberFormatType,
expr,
config
});
if (format === undefined && formatType === undefined && config.customFormatTypes) {
if (type === 'quantitative') {
if (normalizeStack && config.normalizedNumberFormatType)
return formatCustomType({
fieldOrDatumDef,
format: config.normalizedNumberFormat,
formatType: config.normalizedNumberFormatType,
expr,
config
});
if (config.numberFormatType) {
return formatCustomType({
fieldOrDatumDef,
format: config.numberFormat,
formatType: config.numberFormatType,
expr,
config
});
}
}
if (type === 'temporal') {
if (config.timeFormatType) {
return formatCustomType({
fieldOrDatumDef,
format: config.timeFormat,
formatType: config.timeFormatType,
expr,
config
});
}
}
}

if (isFieldOrDatumDefForTimeFormat(fieldOrDatumDef)) {
const signal = timeFormatExpression(
const signal = timeFormatExpression({
field,
isFieldDef(fieldOrDatumDef) ? normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit : undefined,
timeUnit: isFieldDef(fieldOrDatumDef) ? normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit : undefined,
format,
config.timeFormat,
isScaleFieldDef(fieldOrDatumDef) && fieldOrDatumDef.scale?.type === ScaleType.UTC
);
formatType: config.timeFormatType,
rawTimeFormat: config.timeFormat,
isUTCScale: isScaleFieldDef(fieldOrDatumDef) && fieldOrDatumDef.scale?.type === ScaleType.UTC
});
return signal ? {signal} : undefined;
}

Expand Down Expand Up @@ -179,22 +180,16 @@ export function guideFormat(
) {
if (isCustomFormatType(formatType)) {
return undefined; // handled in encode block
} else if (
format === undefined &&
formatType === undefined &&
config.customFormatTypes &&
channelDefType(fieldOrDatumDef) === 'quantitative'
) {
} else if (format === undefined && formatType === undefined && config.customFormatTypes) {
if (
config.normalizedNumberFormatType &&
isPositionFieldOrDatumDef(fieldOrDatumDef) &&
fieldOrDatumDef.stack === 'normalize'
channelDefType(fieldOrDatumDef) === 'quantitative' &&
((config.normalizedNumberFormatType &&
isPositionFieldOrDatumDef(fieldOrDatumDef) &&
fieldOrDatumDef.stack === 'normalize') || // case: normalized number format
config.numberFormatType) // case: regular number format
) {
return undefined; // handled in encode block
}
if (config.numberFormatType) {
lsh marked this conversation as resolved.
Show resolved Hide resolved
return undefined; // handled in encode block
}
}

if (
Expand All @@ -211,6 +206,9 @@ export function guideFormat(

if (isFieldOrDatumDefForTimeFormat(fieldOrDatumDef)) {
const timeUnit = isFieldDef(fieldOrDatumDef) ? normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit : undefined;
if (timeUnit === undefined && config.customFormatTypes && config.timeFormatType) {
lsh marked this conversation as resolved.
Show resolved Hide resolved
return undefined; // hanlded in encode block
}

return timeFormat(format as string, timeUnit, config, omitTimeFormatConfig);
}
Expand Down Expand Up @@ -305,15 +303,26 @@ export function binFormatExpression(
/**
* Returns the time expression used for axis/legend labels or text mark for a temporal field
*/
export function timeFormatExpression(
field: string,
timeUnit: TimeUnit,
format: string | Dict<unknown>,
rawTimeFormat: string, // should be provided only for actual text and headers, not axis/legend labels
isUTCScale: boolean
): string {
export function timeFormatExpression({
field,
timeUnit,
format,
formatType,
rawTimeFormat,
isUTCScale
}: {
field: string;
timeUnit: TimeUnit;
format: string | Dict<unknown>;
formatType?: string;
rawTimeFormat: string; // should be provided only for actual text and headers, not axis/legend labels
isUTCScale: boolean;
}): string {
if (!timeUnit || format) {
// If there is no time unit, or if user explicitly specifies format for axis/legend/text.
if (formatType) {
return `${formatType}(${field}, '${format}')`;
}
format = isString(format) ? format : rawTimeFormat; // only use provided timeFormat if there is no timeUnit.
return `${isUTCScale ? 'utc' : 'time'}Format(${field}, '${format}')`;
} else {
Expand Down
32 changes: 18 additions & 14 deletions src/compile/legend/encode.ts
Expand Up @@ -160,20 +160,24 @@ export function labels(specifiedlabelsSpec: any, {fieldOrDatumDef, model, channe
formatType,
config
});
} else if (
fieldOrDatumDef.type === 'quantitative' &&
format === undefined &&
formatType === undefined &&
config.customFormatTypes &&
config.numberFormatType
) {
text = formatCustomType({
fieldOrDatumDef,
field: 'datum.value',
format: config.numberFormat,
formatType: config.numberFormatType,
config
});
} else if (format === undefined && formatType === undefined && config.customFormatTypes) {
if (fieldOrDatumDef.type === 'quantitative' && config.numberFormatType) {
text = formatCustomType({
fieldOrDatumDef,
field: 'datum.value',
format: config.numberFormat,
formatType: config.numberFormatType,
config
});
} else if (fieldOrDatumDef.type === 'temporal' && config.timeFormatType) {
text = formatCustomType({
fieldOrDatumDef,
field: 'datum.value',
format: config.timeFormat,
formatType: config.timeFormatType,
config
});
}
}

const labelsSpec = {
Expand Down
9 changes: 9 additions & 0 deletions src/config.ts
Expand Up @@ -189,6 +189,15 @@ export interface VLOnlyConfig<ES extends ExprRef | SignalRef> {
*/
timeFormat?: string;

/**
* [Custom format type](https://vega.github.io/vega-lite/docs/config.html#custom-format-type)
* for `config.numberFormat`.
*
* __Default value:__ `undefined` -- This is equilvalent to call D3-format, which is exposed as [`format` in Vega-Expression](https://vega.github.io/vega/docs/expressions/#format).
lsh marked this conversation as resolved.
Show resolved Hide resolved
* __Note:__ You must also set `customFormatTypes` to `true` to use this feature.
*/
timeFormatType?: string;

/**
* Allow the `formatType` property for text marks and guides to accept a custom formatter function [registered as a Vega expression](https://vega.github.io/vega-lite/usage/compile.html#format-type).
*/
Expand Down