Skip to content
Snippets Groups Projects
Commit 497d5beb authored by Tom Robinson's avatar Tom Robinson
Browse files

Various column formatting enhancements

parent 05d7277a
Branches
Tags
No related merge requests found
......@@ -8,7 +8,13 @@ const SETTING_TYPES = [
{
name: "Dates and Times",
type: TYPE.DateTime,
settings: ["date_style", "date_abbreviate", "time_enabled", "time_style"],
settings: [
"date_style",
"date_separator",
"date_abbreviate",
"time_enabled",
"time_style",
],
column: {
special_type: TYPE.DateTime,
unit: "second",
......@@ -17,7 +23,7 @@ const SETTING_TYPES = [
{
name: "Numbers",
type: TYPE.Number,
settings: ["locale"],
settings: ["number_separators"],
column: {
base_type: TYPE.Number,
special_type: TYPE.Number,
......
......@@ -44,6 +44,12 @@ import type {
TimeEnabled,
} from "metabase/lib/formatting/date";
// a one or two character string specifying the decimal and grouping separator characters
type NumberSeparators = ".," | ", " | ",." | ".";
// single character string specifying date separators
type DateSeparator = "/" | "-" | ".";
export type FormattingOptions = {
// GENERIC
column?: Column | Field,
......@@ -63,11 +69,9 @@ export type FormattingOptions = {
scale?: number,
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
scale?: number,
locale?: string,
number_separators?: NumberSeparators,
minimumFractionDigits?: number,
maximumFractionDigits?: number,
// use thousand separators, defualt to false if locale === null
useGrouping?: boolean,
// decimals sets both minimumFractionDigits and maximumFractionDigits
decimals?: number,
// STRING
......@@ -76,6 +80,7 @@ export type FormattingOptions = {
// DATE/TIME
// date/timeout style string that is used to derive a date_format or time_format for different units, see metabase/lib/formatting/date
date_style?: DateStyle,
date_separator?: DateSeparator,
date_abbreviate?: boolean,
date_format?: string,
time_style?: TimeStyle,
......@@ -88,7 +93,6 @@ type FormattedString = string | React$Element<any>;
const DEFAULT_NUMBER_OPTIONS: FormattingOptions = {
compact: false,
maximumFractionDigits: 2,
useGrouping: true,
};
function getDefaultNumberOptions(options) {
......@@ -100,11 +104,6 @@ function getDefaultNumberOptions(options) {
defaults.maximumFractionDigits = options.decimals;
}
// previously we used locale === null to signify that we should turn off thousand separators
if (options.locale === null) {
defaults.useGrouping = false;
}
return defaults;
}
......@@ -127,15 +126,19 @@ const RANGE_SEPARATOR = ` – `;
// for extracting number portion from a formatted currency string
const NUMBER_REGEX = /[\+\-]?[0-9\., ]+/;
const DEFAULT_NUMBER_SEPARATORS = ".,";
export function numberFormatterForOptions(options: FormattingOptions) {
options = { ...getDefaultNumberOptions(options), ...options };
// if we don't provide a locale much of the formatting doens't work
// always use "en" locale so we have known number separators we can replace depending on number_separators option
// TODO: if we do that how can we get localized currency names?
// $FlowFixMe: doesn't know about Intl.NumberFormat
return new Intl.NumberFormat(options.locale || "en", {
return new Intl.NumberFormat("en", {
style: options.number_style,
currency: options.currency,
currencyDisplay: options.currency_style,
useGrouping: options.useGrouping,
// always use grouping separators, but we may replace/remove them depending on number_separators option
useGrouping: true,
minimumIntegerDigits: options.minimumIntegerDigits,
minimumFractionDigits: options.minimumFractionDigits,
maximumFractionDigits: options.maximumFractionDigits,
......@@ -173,7 +176,8 @@ export function formatNumber(number: number, options: FormattingOptions = {}) {
} else {
nf = numberFormatterForOptions(options);
}
const formatted = nf.format(number);
let formatted = nf.format(number);
// extract number portion of currency if we're formatting a cell
if (
......@@ -183,10 +187,16 @@ export function formatNumber(number: number, options: FormattingOptions = {}) {
) {
const match = formatted.match(NUMBER_REGEX);
if (match) {
return match[0].trim();
formatted = match[0].trim();
}
}
// replace the separators if not default
const separators = options["number_separators"];
if (separators && separators !== DEFAULT_NUMBER_SEPARATORS) {
formatted = replaceNumberSeparators(formatted, separators);
}
return formatted;
} catch (e) {
console.warn("Error formatting number", e);
......@@ -199,6 +209,21 @@ export function formatNumber(number: number, options: FormattingOptions = {}) {
}
}
// replaces the decimale and grouping separators with those specified by a NumberSeparators option
function replaceNumberSeparators(
formatted: string,
separators: NumberSeparators,
) {
const [decimalSeparator, groupingSeparator] = (separators || ".,").split("");
const separatorMap = {
",": groupingSeparator || "",
".": decimalSeparator,
};
return formatted.replace(/,|\./g, separator => separatorMap[separator]);
}
function formatNumberScientific(
value: number,
options: FormattingOptions,
......@@ -431,7 +456,11 @@ export function formatDateTimeWithUnit(
if (!dateFormat) {
// $FlowFixMe: date_style default set above
dateFormat = getDateFormatFromStyle(options.date_style, unit);
dateFormat = getDateFormatFromStyle(
options["date_style"],
unit,
options["date_separator"],
);
}
if (!timeFormat) {
......
......@@ -63,26 +63,38 @@ export const DEFAULT_DATE_STYLE: DateStyle = "MMMM D, YYYY";
export function getDateFormatFromStyle(
style: DateStyle,
unit: ?DatetimeUnit,
separator?: DateSeparator,
): DateFormat {
const replaceSeparators = format =>
separator && format ? format.replace(/\//g, separator) : format;
if (!unit) {
unit = "default";
}
if (DATE_STYLE_TO_FORMAT[style]) {
if (DATE_STYLE_TO_FORMAT[style][unit]) {
return DATE_STYLE_TO_FORMAT[style][unit];
return replaceSeparators(DATE_STYLE_TO_FORMAT[style][unit]);
}
} else {
console.warn("Unknown date style", style);
}
if (DEFAULT_DATE_FORMATS[unit]) {
return DEFAULT_DATE_FORMATS[unit];
return replaceSeparators(DEFAULT_DATE_FORMATS[unit]);
}
return style;
return replaceSeparators(style);
}
const UNITS_WITH_HOUR: DatetimeUnit[] = ["default", "second", "minute", "hour"];
const UNITS_WITH_HOUR: DatetimeUnit[] = [
"default",
"millisecond",
"second",
"minute",
"hour",
];
const UNITS_WITH_DAY: DatetimeUnit[] = [
"default",
"millisecond",
"second",
"minute",
"hour",
"day",
......
......@@ -41,6 +41,11 @@ const ColumnSettings = ({
// fake series
const series = [{ card: {}, data: { rows: [], cols: [] } }];
// add a "unit" to make certain settings work
if (column.unit == null) {
column = { ...column, unit: "millisecond" };
}
const settingsDefs = getSettingDefintionsForColumn(series, column);
const computedSettings = getComputedSettings(
......
......@@ -2,6 +2,7 @@
import { t } from "c-3po";
import moment from "moment";
import _ from "underscore";
import { nestedSettings } from "./nested";
import ChartNestedSettingColumns from "metabase/visualizations/components/settings/ChartNestedSettingColumns";
......@@ -101,28 +102,32 @@ const EXAMPLE_DATE = moment("2018-01-07 17:24");
function getDateStyleOptionsForUnit(
unit: ?DatetimeUnit,
abbreviate?: boolean = false,
separator?: string = null,
) {
const options = [
dateStyleOption("MMMM D, YYYY", unit, null, abbreviate),
dateStyleOption("D MMMM, YYYY", unit, null, abbreviate),
dateStyleOption("dddd, MMMM D, YYYY", unit, null, abbreviate),
dateStyleOption("MMMM D, YYYY", unit, null, abbreviate, separator),
dateStyleOption("D MMMM, YYYY", unit, null, abbreviate, separator),
dateStyleOption("dddd, MMMM D, YYYY", unit, null, abbreviate, separator),
dateStyleOption(
"M/D/YYYY",
unit,
hasDay(unit) ? "month, day, year" : null,
abbreviate,
separator,
),
dateStyleOption(
"D/M/YYYY",
unit,
hasDay(unit) ? "day, month, year" : null,
abbreviate,
separator,
),
dateStyleOption(
"YYYY/M/D",
unit,
hasDay(unit) ? "year, month, day" : null,
abbreviate,
separator,
),
];
const seen = new Set();
......@@ -142,8 +147,9 @@ function dateStyleOption(
unit: ?DatetimeUnit,
description?: ?string,
abbreviate?: boolean = false,
separator?: string = null,
) {
let format = getDateFormatFromStyle(style, unit);
let format = getDateFormatFromStyle(style, unit, separator);
if (abbreviate) {
format = format.replace(/MMMM/, "MMM").replace(/dddd/, "ddd");
}
......@@ -168,12 +174,39 @@ export const DATE_COLUMN_SETTINGS = {
title: t`Date style`,
widget: "select",
default: DEFAULT_DATE_STYLE,
isValid: ({ unit }: Column, settings: ColumnSettings) => {
const options = getDateStyleOptionsForUnit(unit);
return !!_.findWhere(options, { value: settings["date_style"] });
},
getProps: ({ unit }: Column, settings: ColumnSettings) => ({
options: getDateStyleOptionsForUnit(unit, settings["date_abbreviate"]),
options: getDateStyleOptionsForUnit(
unit,
settings["date_abbreviate"],
settings["date_separator"],
),
}),
getHidden: ({ unit }: Column) =>
getDateStyleOptionsForUnit(unit).length < 2,
},
date_separator: {
title: t`Date separator`,
widget: "buttonGroup",
default: "/",
getProps: (column: Column, settings: ColumnSettings) => {
const style = /\//.test(settings["date_style"])
? settings["date_style"]
: "M/D/YYYY";
return {
options: [
{ name: style, value: "/" },
{ name: style.replace(/\//g, "-"), value: "-" },
{ name: style.replace(/\//g, "."), value: "." },
],
};
},
getHidden: ({ unit }: Column, settings: ColumnSettings) =>
!/\//.test(settings["date_style"] || ""),
},
date_abbreviate: {
title: t`Abbreviate names of days and months`,
widget: "toggle",
......@@ -287,18 +320,19 @@ export const NUMBER_COLUMN_SETTINGS = {
series[0].card.display !== "table",
readDependencies: ["number_style"],
},
locale: {
number_separators: {
// uses 1-2 character string to represent decimal and thousands separators
title: t`Separator style`,
widget: "select",
props: {
options: [
{ name: "100,000.00", value: "en" },
{ name: "100 000,00", value: "fr" },
{ name: "100.000,00", value: "de" },
{ name: "100000.00", value: null },
{ name: "100,000.00", value: ".," },
{ name: "100 000,00", value: ", " },
{ name: "100.000,00", value: ",." },
{ name: "100000.00", value: "." },
],
},
default: "en",
default: ".,",
},
decimals: {
title: t`Minimum number of decimal places`,
......@@ -328,7 +362,6 @@ export const NUMBER_COLUMN_SETTINGS = {
"number_style",
"currency_style",
"currency",
"locale",
"decimals",
],
},
......@@ -339,7 +372,7 @@ export const NUMBER_COLUMN_SETTINGS = {
settings["currency_in_header"]
) {
return (0)
.toLocaleString(settings["locale"] || "en", {
.toLocaleString("en", {
style: "currency",
currency: settings["currency"],
currencyDisplay: settings["currency_style"],
......@@ -354,7 +387,6 @@ export const NUMBER_COLUMN_SETTINGS = {
"currency",
"currency_style",
"currency_header_only",
"locale",
],
},
_column_title_full: {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment