diff --git a/app/configurator/components/ui-helpers.spec.ts b/app/configurator/components/ui-helpers.spec.ts index 65998ba5ed..c683417f86 100644 --- a/app/configurator/components/ui-helpers.spec.ts +++ b/app/configurator/components/ui-helpers.spec.ts @@ -64,6 +64,15 @@ describe("useDimensionFormatters", () => { currencyExponent: 1, __typename: "Measure", } as DimensionMetadataFragment, + { + iri: "iri-currency-int", + isNumerical: true, + isKeyDimension: false, + isCurrency: true, + currencyExponent: 1, + resolution: 0, + __typename: "Measure", + } as DimensionMetadataFragment, ]) ); return { formatters }; @@ -86,7 +95,19 @@ describe("useDimensionFormatters", () => { it("should work with currencies", () => { const { formatters } = setup(); - expect(formatters["iri-currency"]("20002.3333")).toEqual("20'002,3"); + + // Keeps precision if it is over the currencyExponent + expect(formatters["iri-currency"]("20002.3333")).toEqual("20'002,3333"); + + // Pads with 0 otherwise + expect(formatters["iri-currency"]("20002")).toEqual("20'002,0"); + }); + + it("should work with decimal currencies", () => { + const { formatters } = setup(); + + // If we have a resolution on the dimension + expect(formatters["iri-currency-int"]("20002")).toEqual("20'002"); }); }); diff --git a/app/configurator/components/ui-helpers.ts b/app/configurator/components/ui-helpers.ts index d50dbbf4ef..45b10ab5bb 100644 --- a/app/configurator/components/ui-helpers.ts +++ b/app/configurator/components/ui-helpers.ts @@ -152,8 +152,29 @@ const namedNodeFormatter = (d: DimensionMetadataFragment) => { const currencyFormatter = (d: Measure, locale: string) => { const formatLocale = getD3FormatLocale(locale); - // Use the currency exponent from the dimension, with default 2 - return formatLocale.format(`,.${d.currencyExponent || 2}f`); + const minDecimals = d.resolution ?? d.currencyExponent ?? 2; + const maxDecimals = 5; + const baseFormatter = formatLocale.format(`,.${maxDecimals}f`); + return (v: number) => { + const formatted = baseFormatter(v); + const l = formatted.length; + + // TODO Should be dependent on the locale + const dot = formatted.indexOf("."); + + let lastSignificantIndex = formatted.length - maxDecimals + minDecimals - 1; + for (let i = l - maxDecimals + minDecimals; i < l; i++) { + if (formatted[i] !== "0") { + lastSignificantIndex = i; + } + } + + return formatted.substring( + 0, + lastSignificantIndex + + (minDecimals === 0 || dot === lastSignificantIndex ? 0 : 1) + ); + }; }; export const useDimensionFormatters = ( @@ -193,7 +214,7 @@ export const useDimensionFormatters = ( return [d.iri, formatter]; }) ); - }, [dimensions, formatNumber, dateFormatters, formatDateAuto]); + }, [locale, dimensions, formatNumber, dateFormatters, formatDateAuto]); }; export type LocalDateFormatters = ReturnType; diff --git a/app/graphql/queries/data-cubes.graphql b/app/graphql/queries/data-cubes.graphql index 4adad9c84f..3c7213c8ef 100644 --- a/app/graphql/queries/data-cubes.graphql +++ b/app/graphql/queries/data-cubes.graphql @@ -56,6 +56,7 @@ fragment dimensionMetadata on Dimension { ... on Measure { isCurrency currencyExponent + resolution } } diff --git a/app/graphql/query-hooks.ts b/app/graphql/query-hooks.ts index 1504b6f2d1..ee41b9391f 100644 --- a/app/graphql/query-hooks.ts +++ b/app/graphql/query-hooks.ts @@ -228,6 +228,7 @@ export type Measure = Dimension & { isKeyDimension: Scalars['Boolean']; isCurrency?: Maybe; currencyExponent?: Maybe; + resolution?: Maybe; values: Array; related?: Maybe>; hierarchy?: Maybe>; @@ -455,7 +456,7 @@ type DimensionMetadata_GeoCoordinatesDimension_Fragment = { __typename: 'GeoCoor type DimensionMetadata_GeoShapesDimension_Fragment = { __typename: 'GeoShapesDimension', iri: string, label: string, isNumerical: boolean, isKeyDimension: boolean, order?: Maybe, values: Array, unit?: Maybe, related?: Maybe> }; -type DimensionMetadata_Measure_Fragment = { __typename: 'Measure', isCurrency?: Maybe, currencyExponent?: Maybe, iri: string, label: string, isNumerical: boolean, isKeyDimension: boolean, order?: Maybe, values: Array, unit?: Maybe, related?: Maybe> }; +type DimensionMetadata_Measure_Fragment = { __typename: 'Measure', isCurrency?: Maybe, currencyExponent?: Maybe, resolution?: Maybe, iri: string, label: string, isNumerical: boolean, isKeyDimension: boolean, order?: Maybe, values: Array, unit?: Maybe, related?: Maybe> }; type DimensionMetadata_NominalDimension_Fragment = { __typename: 'NominalDimension', iri: string, label: string, isNumerical: boolean, isKeyDimension: boolean, order?: Maybe, values: Array, unit?: Maybe, related?: Maybe> }; @@ -854,6 +855,7 @@ export const DimensionMetadataFragmentDoc = gql` ... on Measure { isCurrency currencyExponent + resolution } } `; diff --git a/app/graphql/resolver-types.ts b/app/graphql/resolver-types.ts index ccf5ca06b3..e5448a65e0 100644 --- a/app/graphql/resolver-types.ts +++ b/app/graphql/resolver-types.ts @@ -234,6 +234,7 @@ export type Measure = Dimension & { isKeyDimension: Scalars['Boolean']; isCurrency?: Maybe; currencyExponent?: Maybe; + resolution?: Maybe; values: Array; related?: Maybe>; hierarchy?: Maybe>; @@ -714,6 +715,7 @@ export type MeasureResolvers; isCurrency?: Resolver, ParentType, ContextType>; currencyExponent?: Resolver, ParentType, ContextType>; + resolution?: Resolver, ParentType, ContextType>; values?: Resolver, ParentType, ContextType, RequireFields>; related?: Resolver>, ParentType, ContextType>; hierarchy?: Resolver>, ParentType, ContextType, RequireFields>; diff --git a/app/graphql/resolvers/index.ts b/app/graphql/resolvers/index.ts index 4d208fd34b..e918ff6ec1 100644 --- a/app/graphql/resolvers/index.ts +++ b/app/graphql/resolvers/index.ts @@ -233,11 +233,8 @@ export const resolvers: Resolvers = { }, Measure: { ...mkDimensionResolvers("Measure"), - isCurrency: ({ data: { isCurrency } }) => { - return isCurrency; - }, - currencyExponent: ({ data: { currencyExponent } }) => { - return currencyExponent || 0; - }, + isCurrency: ({ data: { isCurrency } }) => isCurrency, + currencyExponent: ({ data: { currencyExponent } }) => currencyExponent || 0, + resolution: ({ data: { resolution } }) => resolution ?? null, }, }; diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index c77c19f243..6677d2368b 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -206,6 +206,7 @@ type Measure implements Dimension { isKeyDimension: Boolean! isCurrency: Boolean currencyExponent: Int + resolution: Int values( sourceType: String! sourceUrl: String! diff --git a/app/graphql/shared-types.ts b/app/graphql/shared-types.ts index a6c671e3b8..5f3b1032b4 100644 --- a/app/graphql/shared-types.ts +++ b/app/graphql/shared-types.ts @@ -53,6 +53,7 @@ export type ResolvedDimension = { currencyExponent?: number; hasUndefinedValues: boolean; unit?: string; + resolution?: number; dataType?: string; order?: number; name: string; diff --git a/app/rdf/parse.ts b/app/rdf/parse.ts index a67b8b81a0..30a82cb274 100644 --- a/app/rdf/parse.ts +++ b/app/rdf/parse.ts @@ -251,6 +251,10 @@ export const parseCubeDimension = ({ hasUndefinedValues, unit: dimensionUnitLabel, dataType: dataType?.value, + resolution: + dataType?.value === ns.xsd.int || dataType?.value === ns.xsd.integer + ? 0 + : undefined, isCurrency: !!dimensionUnit?.isCurrency?.value, currencyExponent: dimensionUnit?.currencyExponent?.value ? parseInt(dimensionUnit?.currencyExponent?.value)