diff --git a/frontend/src/metabase/static-viz/components/XYChart/Values/Values.tsx b/frontend/src/metabase/static-viz/components/XYChart/Values/Values.tsx index 0aaf2abfbe321f4950441a7d5cafd8c0ba2c914d..86ff531ce434e90fa4600f25ed0a73c9ded64a3d 100644 --- a/frontend/src/metabase/static-viz/components/XYChart/Values/Values.tsx +++ b/frontend/src/metabase/static-viz/components/XYChart/Values/Values.tsx @@ -68,8 +68,8 @@ export default function Values({ if (containBars && xScale.bandwidth) { const innerBarScaleDomain = multipleSeries .filter(series => series.type === "bar") - .map((barSerie, index) => { - barSeriesIndexMap.set(barSerie, index); + .map((barSeries, index) => { + barSeriesIndexMap.set(barSeries, index); return index; }); innerBarScale = scaleBand({ @@ -79,8 +79,15 @@ export default function Values({ } const multiSeriesValues: MultiSeriesValue[][] = multipleSeries.map(series => { - const singleSerieValues = getValues(series, areStacked); - return singleSerieValues.map(value => { + const singleSeriesValues = + series.type === "bar" + ? fixSmallBarChartValues( + getValues(series, areStacked), + innerBarScale?.domain().length ?? 0, + ) + : getValues(series, areStacked); + + return singleSeriesValues.map(value => { return { ...value, series, @@ -92,6 +99,21 @@ export default function Values({ }); }); + function fixSmallBarChartValues( + singleSeriesValues: Value[], + numberOfBarSeries: number, + ) { + const barWidth = innerBarScale?.bandwidth() ?? Infinity; + const MIN_BAR_WIDTH = 20; + // Use the same logic as in https://github.com/metabase/metabase/blob/cb51e574de31c7d4485a9dfbef3261f67c0b7495/frontend/src/metabase/visualizations/lib/chart_values.js#L138 + if (numberOfBarSeries > 1 && barWidth < MIN_BAR_WIDTH) { + singleSeriesValues.forEach(value => { + value.hidden = true; + }); + } + return singleSeriesValues; + } + function getBarXOffset(series: HydratedSeries) { if (containBars && innerBarScale) { // Use the same logic when rendering <BarSeries />, as bar charts can display values in groups. @@ -112,13 +134,15 @@ export default function Values({ return ( <> - {verticalOverlappingFreeValues.map((singleSerieValues, seriesIndex) => { - const compact = getCompact(singleSerieValues.map(value => value.datum)); + {verticalOverlappingFreeValues.map((singleSeriesValues, seriesIndex) => { + const compact = getCompact( + singleSeriesValues.map(value => value.datum), + ); return fixHorizontalOverlappingValues( seriesIndex, compact, - singleSerieValues, + singleSeriesValues, ).map((value, index) => { if (value.hidden) { return null; @@ -180,7 +204,7 @@ export default function Values({ function fixHorizontalOverlappingValues( seriesIndex: number, compact: boolean, - singleSerieValues: MultiSeriesValue[], + singleSeriesValues: MultiSeriesValue[], ) { const valueStep = getValueStep( multipleSeries, @@ -190,7 +214,7 @@ export default function Values({ innerWidth, ); - return singleSerieValues.filter((_, index) => index % valueStep === 0); + return singleSeriesValues.filter((_, index) => index % valueStep === 0); } } diff --git a/frontend/src/metabase/static-viz/lib/numbers.ts b/frontend/src/metabase/static-viz/lib/numbers.ts index 9d8e864b855def46449c3279d76a4ed72d38ab7b..fad75199b4ca7d3bbdd24288137ba5ff6dccb1c5 100644 --- a/frontend/src/metabase/static-viz/lib/numbers.ts +++ b/frontend/src/metabase/static-viz/lib/numbers.ts @@ -1,3 +1,5 @@ +import { merge } from "icepick"; + export type NumberFormatOptions = { number_style?: "currency" | "decimal" | "scientific" | "percentage"; currency?: string; @@ -32,7 +34,7 @@ export const formatNumber = (number: number, options?: NumberFormatOptions) => { prefix, suffix, compact, - } = { ...DEFAULT_OPTIONS, ...options }; + } = handleSmallNumberFormat(number, { ...DEFAULT_OPTIONS, ...options }); function createFormat(compact?: boolean) { if (compact) { @@ -43,6 +45,7 @@ export const formatNumber = (number: number, options?: NumberFormatOptions) => { currency: currency, currencyDisplay: currency_style, useGrouping: true, + maximumFractionDigits: decimals != null ? decimals : 2, }); } @@ -67,5 +70,26 @@ export const formatNumber = (number: number, options?: NumberFormatOptions) => { return `${prefix}${formattedNumber}${suffix}`; }; +// Simple hack to handle small decimal numbers (0-1) +function handleSmallNumberFormat<T>(value: number, options: T): T { + const hasAtLeastThreeDecimalPoints = Math.abs(value) < 0.01; + if (hasAtLeastThreeDecimalPoints && Math.abs(value) > 0) { + options = maybeMerge(options, { + compact: true, + decimals: 4, + }); + } + + return options; +} + +const maybeMerge = <T, S1>(collection: T, object: S1) => { + if (collection == null) { + return collection; + } + + return merge(collection, object); +}; + export const formatPercent = (percent: number) => `${(100 * percent).toFixed(2)} %`; diff --git a/frontend/src/metabase/static-viz/lib/numbers.unit.spec.js b/frontend/src/metabase/static-viz/lib/numbers.unit.spec.js index 14723e0b845d0763edc311442c90b76008557282..1b23ab78a9fcadd3c931add54bcec848f15f98be 100644 --- a/frontend/src/metabase/static-viz/lib/numbers.unit.spec.js +++ b/frontend/src/metabase/static-viz/lib/numbers.unit.spec.js @@ -99,6 +99,13 @@ describe("formatNumber", () => { expect(text).toEqual("10000.11"); }); + + it("should format small number", () => { + expect(formatNumber(0.00196)).toEqual("0.002"); + expect(formatNumber(0.00201)).toEqual("0.002"); + expect(formatNumber(-0.00119)).toEqual("-0.0012"); + expect(formatNumber(-0.00191)).toEqual("-0.0019"); + }); }); describe("formatPercent", () => {