diff --git a/frontend/src/metabase/lib/formatting/nullable.ts b/frontend/src/metabase/lib/formatting/nullable.ts new file mode 100644 index 0000000000000000000000000000000000000000..efaf102541ff45588c6a0a981a443b93ebcf4023 --- /dev/null +++ b/frontend/src/metabase/lib/formatting/nullable.ts @@ -0,0 +1,5 @@ +import { NULL_DISPLAY_VALUE } from "../constants"; + +export function formatNullable<T>(value: T | null | undefined) { + return value ?? NULL_DISPLAY_VALUE; +} diff --git a/frontend/src/metabase/static-viz/lib/format.ts b/frontend/src/metabase/static-viz/lib/format.ts index 944292bb13467a82589e08c8da08a8e380b94831..66d4afeee09eeb064aaddce597c3f19d30dab51d 100644 --- a/frontend/src/metabase/static-viz/lib/format.ts +++ b/frontend/src/metabase/static-viz/lib/format.ts @@ -1,5 +1,6 @@ import moment from "moment"; import { Moment } from "moment-timezone"; +import { NumberLike, StringLike } from "@visx/scale"; import { DatasetColumn, RowValue, @@ -105,7 +106,7 @@ export const getStaticFormatters = ( chartColumns: ChartColumns, settings: VisualizationSettings, ): ChartTicksFormatters => { - const yTickFormatter = (value: RowValue) => { + const yTickFormatter = (value: StringLike) => { const column = chartColumns.dimension.column; const columnSettings = settings.column_settings?.[getColumnKey(column)]; @@ -120,7 +121,7 @@ export const getStaticFormatters = ( const metricColumn = getLabelsMetricColumn(chartColumns); - const percentXTicksFormatter = (percent: any) => { + const percentXTicksFormatter = (percent: NumberLike) => { const column = metricColumn.column; const number_separators = settings.column_settings?.[getColumnKey(column)]?.number_separators; @@ -136,7 +137,7 @@ export const getStaticFormatters = ( ); }; - const xTickFormatter = (value: any) => { + const xTickFormatter = (value: NumberLike) => { const column = metricColumn.column; const columnSettings = settings.column_settings?.[getColumnKey(column)]; @@ -180,6 +181,6 @@ export const getLabelsStaticFormatter = ( }; export const getColumnValueStaticFormatter = () => { - return (value: any, column: DatasetColumn) => + return (value: RowValue, column: DatasetColumn) => String(formatStaticValue(value, { column })); }; diff --git a/frontend/src/metabase/visualizations/lib/apply_tooltips.js b/frontend/src/metabase/visualizations/lib/apply_tooltips.js index 584391ccd9d4ba187fdb1e02440a5f114825b9b3..6956e98c86f4cdffc010b77970a5adba31ecb0f3 100644 --- a/frontend/src/metabase/visualizations/lib/apply_tooltips.js +++ b/frontend/src/metabase/visualizations/lib/apply_tooltips.js @@ -5,8 +5,9 @@ import moment from "moment-timezone"; import { getIn } from "icepick"; import { formatValue } from "metabase/lib/formatting"; +import { formatNullable } from "metabase/lib/formatting/nullable"; -import { isNormalized, isStacked, formatNull } from "./renderer_utils"; +import { isNormalized, isStacked } from "./renderer_utils"; import { determineSeriesIndexFromElement } from "./tooltip"; import { getFriendlyName } from "./utils"; @@ -176,7 +177,7 @@ export function getClickHoverObject( } return { key: getColumnDisplayName(col, colVizSettingsKeys[i]), - value: formatNull(aggregatedRow[i]), + value: formatNullable(aggregatedRow[i]), col: col, }; }); diff --git a/frontend/src/metabase/visualizations/lib/renderer_utils.js b/frontend/src/metabase/visualizations/lib/renderer_utils.js index e797d0f8baff166baeb59e7bdefda3a4fcd33ea3..a562c862d7458f43aeb9a36162cba9e59a90a0a2 100644 --- a/frontend/src/metabase/visualizations/lib/renderer_utils.js +++ b/frontend/src/metabase/visualizations/lib/renderer_utils.js @@ -5,10 +5,10 @@ import { getIn } from "icepick"; import { parseTimestamp } from "metabase/lib/time"; import { - NULL_DISPLAY_VALUE, NULL_NUMERIC_VALUE, TOTAL_ORDINAL_VALUE, } from "metabase/lib/constants"; +import { formatNullable } from "metabase/lib/formatting/nullable"; import { datasetContainsNoResults } from "metabase-lib/queries/utils/dataset"; import { @@ -109,7 +109,7 @@ const memoizedParseXValue = _.memoize( if (isTimeseries && !isQuantitative) { return parseTimestampAndWarn(xValue, unit); } - const parsedValue = isNumeric ? xValue : String(formatNull(xValue)); + const parsedValue = isNumeric ? xValue : String(formatNullable(xValue)); return { parsedValue }; }, // create cache key from args @@ -408,10 +408,6 @@ export const isMultiCardSeries = series => series.length > 1 && getIn(series, [0, "card", "id"]) !== getIn(series, [1, "card", "id"]); -export function formatNull(value) { - return value === null ? NULL_DISPLAY_VALUE : value; -} - // Hack: for numeric dimensions we have to replace null values // with anything else since crossfilter groups merge 0 and null export function replaceNullValuesForOrdinal(value) { diff --git a/frontend/src/metabase/visualizations/shared/components/RowChart/types.ts b/frontend/src/metabase/visualizations/shared/components/RowChart/types.ts index 6b74d2cf946995cf9418d151c5f554aac6c5ed4d..29ea0ce139ca0673be968ee7d36aec38dedd50fb 100644 --- a/frontend/src/metabase/visualizations/shared/components/RowChart/types.ts +++ b/frontend/src/metabase/visualizations/shared/components/RowChart/types.ts @@ -1,7 +1,8 @@ +import { StringLike } from "@visx/scale"; import { AxisStyle, ChartFont, GoalStyle } from "../../types/style"; export type XValue = number | null; -export type YValue = string | number | boolean; +export type YValue = string | number | boolean | null; export type Series<TDatum, TSeriesInfo = unknown> = { seriesKey: string; @@ -14,7 +15,7 @@ export type Series<TDatum, TSeriesInfo = unknown> = { export type BarData<TDatum, TSeriesInfo = unknown> = { xStartValue: number; xEndValue: number; - yValue: YValue; + yValue: StringLike; isNegative: boolean; isBorderValue?: boolean; datum: TDatum; diff --git a/frontend/src/metabase/visualizations/shared/components/RowChart/utils/data.ts b/frontend/src/metabase/visualizations/shared/components/RowChart/utils/data.ts index 3c929efd3952fdb3f6f6999a4c5e688eaaf20a9f..91bfd79053277039f748f7112449225239e0065a 100644 --- a/frontend/src/metabase/visualizations/shared/components/RowChart/utils/data.ts +++ b/frontend/src/metabase/visualizations/shared/components/RowChart/utils/data.ts @@ -4,6 +4,7 @@ import type { Series as D3Series } from "d3-shape"; import d3 from "d3"; import { ContinuousScaleType } from "metabase/visualizations/shared/types/scale"; import { isNotNull } from "metabase/core/utils/types"; +import { formatNullable } from "metabase/lib/formatting/nullable"; import { BarData, Series, SeriesData, StackOffset } from "../types"; export const StackOffsetFn = { @@ -21,7 +22,7 @@ export const calculateNonStackedBars = <TDatum>( return multipleSeries.map((series, seriesIndex) => { const bars: BarData<TDatum>[] = data .map((datum, datumIndex) => { - const yValue = series.yAccessor(datum); + const yValue = formatNullable(series.yAccessor(datum)); const xValue = series.xAccessor(datum); const isNegative = xValue != null && xValue < 0; @@ -100,7 +101,7 @@ export const calculateStackedBars = <TDatum>( const [xStartValue, xEndValue] = stackedDatum; - const yValue = series.yAccessor(stackedDatum.data); + const yValue = formatNullable(series.yAccessor(stackedDatum.data)); const isNegative = xStartValue < 0; const isBorderValue = (isNegative && xStartValue === datumMin) || diff --git a/frontend/src/metabase/visualizations/shared/components/RowChart/utils/scale.ts b/frontend/src/metabase/visualizations/shared/components/RowChart/utils/scale.ts index 5c0d3ef0267d62262451514c08b1fb307e25ba6c..3a221109ff9785fb6522454198bc3446c3f7e132 100644 --- a/frontend/src/metabase/visualizations/shared/components/RowChart/utils/scale.ts +++ b/frontend/src/metabase/visualizations/shared/components/RowChart/utils/scale.ts @@ -58,7 +58,7 @@ export const getChartScales = <TDatum>( rangeOverride?: Range, ) => { const yDomain = createYDomain(seriesData); - const yScale = scaleBand<YValue>({ + const yScale = scaleBand({ domain: yDomain, range: [0, innerHeight], padding: 0.2, diff --git a/frontend/src/metabase/visualizations/shared/components/RowChartView/RowChartView.tsx b/frontend/src/metabase/visualizations/shared/components/RowChartView/RowChartView.tsx index 474152237f364bf379107fc81082a819ba25581f..b99d0bbf4cc4649af0c6781fbaa260adc5e89179 100644 --- a/frontend/src/metabase/visualizations/shared/components/RowChartView/RowChartView.tsx +++ b/frontend/src/metabase/visualizations/shared/components/RowChartView/RowChartView.tsx @@ -5,7 +5,7 @@ import { Bar } from "@visx/shape"; import type { NumberValue, ScaleBand, ScaleContinuousNumeric } from "d3-scale"; import { Text } from "@visx/text"; import { GridColumns } from "@visx/grid"; -import { scaleBand } from "@visx/scale"; +import { scaleBand, StringLike, NumberLike } from "@visx/scale"; import { HoveredData } from "metabase/visualizations/shared/types/events"; import { Margin } from "metabase/visualizations/shared/types/layout"; import { VerticalGoalLine } from "../VerticalGoalLine/VerticalGoalLine"; @@ -16,12 +16,12 @@ import { getDataLabel } from "./utils/data-labels"; export interface RowChartViewProps<TDatum> { width: number; height: number; - yScale: ScaleBand<YValue>; + yScale: ScaleBand<StringLike>; xScale: ScaleContinuousNumeric<number, number, never>; seriesData: SeriesData<TDatum>[]; - labelsFormatter: (value: NumberValue) => string; - yTickFormatter: (value: YValue) => string; - xTickFormatter: (value: NumberValue) => string; + labelsFormatter: (value: NumberLike) => string; + yTickFormatter: (value: StringLike) => string; + xTickFormatter: (value: NumberLike) => string; xTicks: number[]; goal: { label: string; diff --git a/frontend/src/metabase/visualizations/shared/utils/data.ts b/frontend/src/metabase/visualizations/shared/utils/data.ts index 064df89b3a2f6b8ca029622a500d11edb015bcfd..c4c42b06759492182f4bb7374c57323331f17148 100644 --- a/frontend/src/metabase/visualizations/shared/utils/data.ts +++ b/frontend/src/metabase/visualizations/shared/utils/data.ts @@ -16,7 +16,7 @@ import { TwoDimensionalChartData, } from "metabase/visualizations/shared/types/data"; import { Series } from "metabase/visualizations/shared/components/RowChart/types"; -import { NULL_DISPLAY_VALUE } from "metabase/lib/constants"; +import { formatNullable } from "metabase/lib/formatting/nullable"; import { isMetric } from "metabase-lib/types/utils/isa"; const getMetricValue = (value: RowValue): MetricValue => { @@ -185,10 +185,7 @@ const getBreakoutSeries = ( return { seriesKey: breakoutName, seriesName: breakoutName, - yAccessor: (datum: GroupedDatum) => - datum.dimensionValue == null - ? NULL_DISPLAY_VALUE - : datum.dimensionValue, + yAccessor: (datum: GroupedDatum) => formatNullable(datum.dimensionValue), xAccessor: (datum: GroupedDatum) => datum.breakout?.[breakoutName]?.[metric.column.name] ?? null, seriesInfo: { @@ -208,8 +205,7 @@ const getMultipleMetricSeries = ( return { seriesKey: metric.column.name, seriesName: metric.column.display_name ?? metric.column.name, - yAccessor: (datum: GroupedDatum) => - datum.dimensionValue != null ? datum.dimensionValue : "null", + yAccessor: (datum: GroupedDatum) => datum.dimensionValue, xAccessor: (datum: GroupedDatum) => datum.metrics[metric.column.name], seriesInfo: { dimensionColumn: dimension.column, diff --git a/frontend/src/metabase/visualizations/visualizations/RowChart/utils/events.ts b/frontend/src/metabase/visualizations/visualizations/RowChart/utils/events.ts index 4f2a2fa160d26cf142288405cc6d1b67b18c29e3..b0eae2f1a778c8cc74d08e3b9a570640a6fee57d 100644 --- a/frontend/src/metabase/visualizations/visualizations/RowChart/utils/events.ts +++ b/frontend/src/metabase/visualizations/visualizations/RowChart/utils/events.ts @@ -3,7 +3,7 @@ import { RowValue, VisualizationSettings, } from "metabase-types/api"; -import { NULL_DISPLAY_VALUE } from "metabase/lib/constants"; +import { formatNullable } from "metabase/lib/formatting/nullable"; import { ChartColumns } from "metabase/visualizations/lib/graph/columns"; import { BarData, @@ -25,7 +25,7 @@ const getMetricColumnData = ( return { key: col.display_name, - value: value != null ? value : NULL_DISPLAY_VALUE, + value: formatNullable(value), col, }; }); @@ -40,7 +40,7 @@ const getColumnsData = ( const data = [ { key: chartColumns.dimension.column.display_name, - value: datum.dimensionValue, + value: formatNullable(datum.dimensionValue), col: chartColumns.dimension.column, }, ]; diff --git a/frontend/src/metabase/visualizations/visualizations/RowChart/utils/format.ts b/frontend/src/metabase/visualizations/visualizations/RowChart/utils/format.ts index 3bac8f274daa613a4b94aadbdcb0bbe5c8f6b975..2a29dc418aeb6034fac2050920dd7fa38ca5b176 100644 --- a/frontend/src/metabase/visualizations/visualizations/RowChart/utils/format.ts +++ b/frontend/src/metabase/visualizations/visualizations/RowChart/utils/format.ts @@ -1,3 +1,4 @@ +import { NumberLike, StringLike } from "@visx/scale"; import { DatasetColumn, RowValue, @@ -16,7 +17,7 @@ export const getFormatters = ( chartColumns: ChartColumns, settings: VisualizationSettings, ): ChartTicksFormatters => { - const yTickFormatter = (value: RowValue) => { + const yTickFormatter = (value: StringLike) => { return String( formatValue(value, { ...settings.column(chartColumns.dimension.column), @@ -27,7 +28,7 @@ export const getFormatters = ( const metricColumn = getLabelsMetricColumn(chartColumns); - const percentXTicksFormatter = (percent: any) => { + const percentXTicksFormatter = (percent: NumberLike) => { const column = metricColumn.column; const number_separators = settings.column(column)?.number_separators; @@ -42,7 +43,7 @@ export const getFormatters = ( ); }; - const xTickFormatter = (value: any) => { + const xTickFormatter = (value: NumberLike) => { return String( formatValue(value, { ...settings.column(metricColumn.column), @@ -80,6 +81,6 @@ export const getLabelsFormatter = ( }; export const getColumnValueFormatter = () => { - return (value: any, column: DatasetColumn) => + return (value: RowValue, column: DatasetColumn) => String(formatValue(value, { column })); };