From c7cc436b0ba07beffbb1961eab7683afd6e44786 Mon Sep 17 00:00:00 2001 From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com> Date: Fri, 14 Feb 2025 09:04:23 -0300 Subject: [PATCH] fix: Decimal values for Histogram bins (#32253) (cherry picked from commit ffe9244458d03995b760638e9bb5b347c0cc68d6) --- .../src/sections/chartTitle.tsx | 2 +- .../src/Histogram/controlPanel.tsx | 57 ++++++++++++++++++- .../src/Histogram/transformProps.ts | 37 ++++++++---- .../src/Histogram/types.ts | 2 + .../utils/pandas_postprocessing/histogram.py | 3 +- .../pandas_postprocessing/test_histogram.py | 48 +++++++++------- 6 files changed, 112 insertions(+), 37 deletions(-) diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx index e8cda4991fd17..6c8d08364cc28 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx @@ -54,7 +54,7 @@ export const titleControls: ControlPanelSectionConfig = { type: 'SelectControl', freeForm: true, clearable: true, - label: t('X AXIS TITLE BOTTOM MARGIN'), + label: t('X Axis Title Margin'), renderTrigger: true, default: TITLE_MARGIN_OPTIONS[0], choices: formatSelectOptions(TITLE_MARGIN_OPTIONS), diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx index a347694f1bfb6..d4e8d4b86dbce 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx @@ -27,7 +27,9 @@ import { formatSelectOptionsForRange, dndGroupByControl, columnsByType, - sections, + D3_FORMAT_OPTIONS, + D3_FORMAT_DOCS, + D3_NUMBER_FORMAT_DESCRIPTION_VALUES_TEXT, } from '@superset-ui/chart-controls'; import { showLegendControl, showValueControl } from '../controls'; @@ -105,7 +107,6 @@ const config: ControlPanelConfig = { ], ], }, - sections.titleControls, { label: t('Chart Options'), expanded: true, @@ -113,6 +114,58 @@ const config: ControlPanelConfig = { ['color_scheme'], [showValueControl], [showLegendControl], + [ + { + name: 'x_axis_title', + config: { + type: 'TextControl', + label: t('X Axis Title'), + renderTrigger: true, + default: '', + description: t('Changing this control takes effect instantly'), + }, + }, + ], + [ + { + name: 'x_axis_format', + config: { + type: 'SelectControl', + freeForm: true, + label: t('X Axis Format'), + renderTrigger: true, + default: 'SMART_NUMBER', + choices: D3_FORMAT_OPTIONS, + description: `${D3_FORMAT_DOCS} ${D3_NUMBER_FORMAT_DESCRIPTION_VALUES_TEXT}`, + }, + }, + ], + [ + { + name: 'y_axis_title', + config: { + type: 'TextControl', + label: t('Y Axis Title'), + renderTrigger: true, + default: '', + description: t('Changing this control takes effect instantly'), + }, + }, + ], + [ + { + name: 'y_axis_format', + config: { + type: 'SelectControl', + freeForm: true, + label: t('Y Axis Format'), + renderTrigger: true, + default: 'SMART_NUMBER', + choices: D3_FORMAT_OPTIONS, + description: `${D3_FORMAT_DOCS} ${D3_NUMBER_FORMAT_DESCRIPTION_VALUES_TEXT}`, + }, + }, + ], ], }, ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/transformProps.ts index df8fe3b1569f7..388e79ae94eef 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/transformProps.ts @@ -25,7 +25,7 @@ import { CategoricalColorNamespace, NumberFormats, getColumnLabel, - getNumberFormatter, + getValueFormatter, tooltipHtml, } from '@superset-ui/core'; import { HistogramChartProps, HistogramTransformedProps } from './types'; @@ -41,6 +41,7 @@ export default function transformProps( const refs: Refs = {}; let focusedSeries: number | undefined; const { + datasource: { currencyFormats = {}, columnFormats = {} }, formData, height, hooks, @@ -58,19 +59,33 @@ export default function transformProps( showLegend, showValue, sliceId, + xAxisFormat, xAxisTitle, yAxisTitle, + yAxisFormat, } = formData; const { data } = queriesData[0]; const colorFn = CategoricalColorNamespace.getScale(colorScheme); - const formatter = getNumberFormatter( - normalize ? NumberFormats.FLOAT_2_POINT : NumberFormats.INTEGER, - ); + + const formatter = (format: string) => + getValueFormatter( + column, + currencyFormats, + columnFormats, + format, + undefined, + ); + const xAxisFormatter = formatter(xAxisFormat); + const yAxisFormatter = formatter(yAxisFormat); + const percentFormatter = getPercentFormatter(NumberFormats.PERCENT_2_POINT); const groupbySet = new Set(groupby); - const xAxisData: string[] = Object.keys(data[0]).filter( - key => !groupbySet.has(key), - ); + const xAxisData: string[] = Object.keys(data[0]) + .filter(key => !groupbySet.has(key)) + .map(key => { + const array = key.split(' - ').map(value => parseFloat(value)); + return `${xAxisFormatter(array[0])} '-' ${xAxisFormatter(array[1])}`; + }); const barSeries: BarSeriesOption[] = data.map(datum => { const seriesName = groupby.length > 0 @@ -91,7 +106,7 @@ export default function transformProps( position: 'top', formatter: params => { const { value } = params; - return formatter.format(value as number); + return yAxisFormatter.format(value as number); }, }, }; @@ -108,7 +123,7 @@ export default function transformProps( const title = params[0].name; const rows = params.map(param => { const { marker, seriesName, value } = param; - return [`${marker}${seriesName}`, formatter.format(value as number)]; + return [`${marker}${seriesName}`, yAxisFormatter.format(value as number)]; }); if (groupby.length > 0) { const total = params.reduce( @@ -122,7 +137,7 @@ export default function transformProps( ), ); } - const totalRow = ['Total', formatter.format(total)]; + const totalRow = ['Total', yAxisFormatter.format(total)]; if (!normalize) { totalRow.push(percentFormatter.format(1)); } @@ -159,7 +174,7 @@ export default function transformProps( type: 'value', nameLocation: 'middle', axisLabel: { - formatter: (value: number) => formatter.format(value), + formatter: (value: number) => yAxisFormatter.format(value), }, }, series: barSeries, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/types.ts index ca6c16d79ee7d..ab3d4b75c2eb3 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/types.ts @@ -28,7 +28,9 @@ export type HistogramFormData = QueryFormData & { sliceId: number; showLegend: boolean; showValue: boolean; + xAxisFormat: string; xAxisTitle: string; + yAxisFormat: string; yAxisTitle: string; }; diff --git a/superset/utils/pandas_postprocessing/histogram.py b/superset/utils/pandas_postprocessing/histogram.py index f55f0d0762673..8ef9abadc7206 100644 --- a/superset/utils/pandas_postprocessing/histogram.py +++ b/superset/utils/pandas_postprocessing/histogram.py @@ -60,8 +60,7 @@ def histogram( # convert the bin edges to strings bin_edges_str = [ - f"{int(bin_edges[i])} - {int(bin_edges[i+1])}" - for i in range(len(bin_edges) - 1) + f"{bin_edges[i]} - {bin_edges[i+1]}" for i in range(len(bin_edges) - 1) ] def hist_values(series: Series) -> np.ndarray: diff --git a/tests/unit_tests/pandas_postprocessing/test_histogram.py b/tests/unit_tests/pandas_postprocessing/test_histogram.py index ee0261a9fa300..9246a06df0828 100644 --- a/tests/unit_tests/pandas_postprocessing/test_histogram.py +++ b/tests/unit_tests/pandas_postprocessing/test_histogram.py @@ -35,7 +35,13 @@ def test_histogram_no_groupby(): ) result = histogram(data_with_no_groupings, "a", [], bins) assert result.shape == (1, bins) - assert result.columns.tolist() == ["1 - 2", "2 - 4", "4 - 6", "6 - 8", "8 - 10"] + assert result.columns.tolist() == [ + "1.0 - 2.8", + "2.8 - 4.6", + "4.6 - 6.4", + "6.4 - 8.2", + "8.2 - 10.0", + ] assert result.values.tolist() == [[2, 2, 2, 2, 2]] @@ -44,11 +50,11 @@ def test_histogram_with_groupby(): assert result.shape == (2, bins + 1) assert result.columns.tolist() == [ "group", - "1 - 2", - "2 - 4", - "4 - 6", - "6 - 8", - "8 - 10", + "1.0 - 2.8", + "2.8 - 4.6", + "4.6 - 6.4", + "6.4 - 8.2", + "8.2 - 10.0", ] assert result.values.tolist() == [["A", 2, 0, 2, 0, 2], ["B", 0, 2, 0, 2, 0]] @@ -58,11 +64,11 @@ def test_histogram_with_groupby_and_normalize(): assert result.shape == (2, bins + 1) assert result.columns.tolist() == [ "group", - "1 - 2", - "2 - 4", - "4 - 6", - "6 - 8", - "8 - 10", + "1.0 - 2.8", + "2.8 - 4.6", + "4.6 - 6.4", + "6.4 - 8.2", + "8.2 - 10.0", ] assert result.values.tolist() == [ ["A", 0.2, 0.0, 0.2, 0.0, 0.2], @@ -75,11 +81,11 @@ def test_histogram_with_groupby_and_cumulative(): assert result.shape == (2, bins + 1) assert result.columns.tolist() == [ "group", - "1 - 2", - "2 - 4", - "4 - 6", - "6 - 8", - "8 - 10", + "1.0 - 2.8", + "2.8 - 4.6", + "4.6 - 6.4", + "6.4 - 8.2", + "8.2 - 10.0", ] assert result.values.tolist() == [["A", 2, 2, 4, 4, 6], ["B", 0, 2, 2, 4, 4]] @@ -89,11 +95,11 @@ def test_histogram_with_groupby_and_cumulative_and_normalize(): assert result.shape == (2, bins + 1) assert result.columns.tolist() == [ "group", - "1 - 2", - "2 - 4", - "4 - 6", - "6 - 8", - "8 - 10", + "1.0 - 2.8", + "2.8 - 4.6", + "4.6 - 6.4", + "6.4 - 8.2", + "8.2 - 10.0", ] assert result.values.tolist() == [ [