feat: add metric name for big number chart types #33013 (#33099)

Co-authored-by: Fardin Mustaque <fardinmustaque@Fardins-Mac-mini.local>
This commit is contained in:
Fardin Mustaque
2025-05-07 20:26:02 +05:30
committed by GitHub
parent afaaf64f52
commit a928f8cd9e
15 changed files with 384 additions and 126 deletions

View File

@@ -36,13 +36,25 @@ import {
} from './types'; } from './types';
import { useOverflowDetection } from './useOverflowDetection'; import { useOverflowDetection } from './useOverflowDetection';
const MetricNameText = styled.div<{ metricNameFontSize?: number }>`
${({ theme, metricNameFontSize }) => `
font-family: ${theme.typography.families.sansSerif};
font-weight: ${theme.typography.weights.normal};
font-size: ${metricNameFontSize || theme.typography.sizes.s * 2}px;
text-align: center;
margin-bottom: ${theme.gridUnit * 3}px;
`}
`;
const NumbersContainer = styled.div` const NumbersContainer = styled.div`
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
height: 100%;
overflow: auto; overflow: auto;
padding: 12px;
`; `;
const ComparisonValue = styled.div<PopKPIComparisonValueStyleProps>` const ComparisonValue = styled.div<PopKPIComparisonValueStyleProps>`
@@ -73,6 +85,8 @@ export default function PopKPI(props: PopKPIProps) {
prevNumber, prevNumber,
valueDifference, valueDifference,
percentDifferenceFormattedString, percentDifferenceFormattedString,
metricName,
metricNameFontSize,
headerFontSize, headerFontSize,
subheaderFontSize, subheaderFontSize,
comparisonColorEnabled, comparisonColorEnabled,
@@ -84,8 +98,8 @@ export default function PopKPI(props: PopKPIProps) {
subtitle, subtitle,
subtitleFontSize, subtitleFontSize,
dashboardTimeRange, dashboardTimeRange,
showMetricName,
} = props; } = props;
const [comparisonRange, setComparisonRange] = useState<string>(''); const [comparisonRange, setComparisonRange] = useState<string>('');
useEffect(() => { useEffect(() => {
@@ -260,9 +274,16 @@ export default function PopKPI(props: PopKPIProps) {
width: fit-content; width: fit-content;
margin: auto; margin: auto;
align-items: flex-start; align-items: flex-start;
overflow: auto;
` `
} }
> >
{showMetricName && metricName && (
<MetricNameText metricNameFontSize={metricNameFontSize}>
{metricName}
</MetricNameText>
)}
<div css={bigValueContainerStyles}> <div css={bigValueContainerStyles}>
{bigNumber} {bigNumber}
{percentDifferenceNumber !== 0 && ( {percentDifferenceNumber !== 0 && (

View File

@@ -28,6 +28,8 @@ import {
subheaderFontSize, subheaderFontSize,
subtitleControl, subtitleControl,
subtitleFontSize, subtitleFontSize,
showMetricNameControl,
metricNameFontSizeWithVisibility,
} from '../sharedControls'; } from '../sharedControls';
import { ColorSchemeEnum } from './types'; import { ColorSchemeEnum } from './types';
@@ -70,6 +72,8 @@ const config: ControlPanelConfig = {
], ],
[subtitleControl], [subtitleControl],
[subtitleFontSize], [subtitleFontSize],
[showMetricNameControl],
[metricNameFontSizeWithVisibility],
[ [
{ {
...subheaderFontSize, ...subheaderFontSize,

View File

@@ -26,7 +26,13 @@ import {
SimpleAdhocFilter, SimpleAdhocFilter,
ensureIsArray, ensureIsArray,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { getComparisonFontSize, getHeaderFontSize } from './utils'; import {
getComparisonFontSize,
getHeaderFontSize,
getMetricNameFontSize,
} from './utils';
import { getOriginalLabel } from '../utils';
dayjs.extend(utc); dayjs.extend(utc);
@@ -83,6 +89,7 @@ export default function transformProps(chartProps: ChartProps) {
headerFontSize, headerFontSize,
headerText, headerText,
metric, metric,
metricNameFontSize,
yAxisFormat, yAxisFormat,
currencyFormat, currencyFormat,
subheaderFontSize, subheaderFontSize,
@@ -91,11 +98,14 @@ export default function transformProps(chartProps: ChartProps) {
percentDifferenceFormat, percentDifferenceFormat,
subtitle = '', subtitle = '',
subtitleFontSize, subtitleFontSize,
columnConfig, columnConfig = {},
} = formData; } = formData;
const { data: dataA = [] } = queriesData[0]; const { data: dataA = [] } = queriesData[0];
const data = dataA; const data = dataA;
const metricName = metric ? getMetricLabel(metric) : ''; const metricName = metric ? getMetricLabel(metric) : '';
const metrics = chartProps.datasource?.metrics || [];
const originalLabel = getOriginalLabel(metric, metrics);
const showMetricName = chartProps.rawFormData?.show_metric_name ?? false;
const timeComparison = ensureIsArray(chartProps.rawFormData?.time_compare)[0]; const timeComparison = ensureIsArray(chartProps.rawFormData?.time_compare)[0];
const startDateOffset = chartProps.rawFormData?.start_date_offset; const startDateOffset = chartProps.rawFormData?.start_date_offset;
const currentTimeRangeFilter = chartProps.rawFormData?.adhoc_filters?.filter( const currentTimeRangeFilter = chartProps.rawFormData?.adhoc_filters?.filter(
@@ -179,7 +189,7 @@ export default function transformProps(chartProps: ChartProps) {
width, width,
height, height,
data, data,
metricName, metricName: originalLabel,
bigNumber, bigNumber,
prevNumber, prevNumber,
valueDifference, valueDifference,
@@ -187,6 +197,8 @@ export default function transformProps(chartProps: ChartProps) {
boldText, boldText,
subtitle, subtitle,
subtitleFontSize, subtitleFontSize,
showMetricName,
metricNameFontSize: getMetricNameFontSize(metricNameFontSize),
headerFontSize: getHeaderFontSize(headerFontSize), headerFontSize: getHeaderFontSize(headerFontSize),
subheaderFontSize: getComparisonFontSize(subheaderFontSize), subheaderFontSize: getComparisonFontSize(subheaderFontSize),
headerText, headerText,

View File

@@ -61,6 +61,8 @@ export type PopKPIProps = PopKPIStylesProps &
data: TimeseriesDataRecord[]; data: TimeseriesDataRecord[];
metrics: Metric[]; metrics: Metric[];
metricName: string; metricName: string;
metricNameFontSize?: number;
showMetricName: boolean;
bigNumber: string; bigNumber: string;
prevNumber: string; prevNumber: string;
subtitle?: string; subtitle?: string;

View File

@@ -16,10 +16,19 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { headerFontSize, subheaderFontSize } from '../sharedControls'; import {
headerFontSize,
subheaderFontSize,
metricNameFontSize,
} from '../sharedControls';
const headerFontSizes = [16, 20, 30, 48, 60]; const headerFontSizes = [16, 20, 30, 48, 60];
const comparisonFontSizes = [16, 20, 26, 32, 40]; const sharedFontSizes = [16, 20, 26, 32, 40];
const metricNameProportionValues =
metricNameFontSize.config.options.map(
(option: { label: string; value: number }) => option.value,
) ?? [];
const headerProportionValues = const headerProportionValues =
headerFontSize.config.options.map( headerFontSize.config.options.map(
@@ -40,6 +49,10 @@ const getFontSizeMapping = (
return acc; return acc;
}, {}); }, {});
const metricNameFontSizesMapping = getFontSizeMapping(
metricNameProportionValues,
sharedFontSizes,
);
const headerFontSizesMapping = getFontSizeMapping( const headerFontSizesMapping = getFontSizeMapping(
headerProportionValues, headerProportionValues,
headerFontSizes, headerFontSizes,
@@ -47,13 +60,17 @@ const headerFontSizesMapping = getFontSizeMapping(
const comparisonFontSizesMapping = getFontSizeMapping( const comparisonFontSizesMapping = getFontSizeMapping(
subheaderProportionValues, subheaderProportionValues,
comparisonFontSizes, sharedFontSizes,
); );
export const getMetricNameFontSize = (proportionValue: number) =>
metricNameFontSizesMapping[proportionValue] ??
sharedFontSizes[sharedFontSizes.length - 1];
export const getHeaderFontSize = (proportionValue: number) => export const getHeaderFontSize = (proportionValue: number) =>
headerFontSizesMapping[proportionValue] ?? headerFontSizesMapping[proportionValue] ??
headerFontSizes[headerFontSizes.length - 1]; headerFontSizes[headerFontSizes.length - 1];
export const getComparisonFontSize = (proportionValue: number) => export const getComparisonFontSize = (proportionValue: number) =>
comparisonFontSizesMapping[proportionValue] ?? comparisonFontSizesMapping[proportionValue] ??
comparisonFontSizes[comparisonFontSizes.length - 1]; sharedFontSizes[sharedFontSizes.length - 1];

View File

@@ -28,6 +28,8 @@ import {
headerFontSize, headerFontSize,
subtitleFontSize, subtitleFontSize,
subtitleControl, subtitleControl,
showMetricNameControl,
metricNameFontSizeWithVisibility,
} from '../sharedControls'; } from '../sharedControls';
export default { export default {
@@ -44,6 +46,8 @@ export default {
[headerFontSize], [headerFontSize],
[subtitleControl], [subtitleControl],
[subtitleFontSize], [subtitleFontSize],
[showMetricNameControl],
[metricNameFontSizeWithVisibility],
['y_axis_format'], ['y_axis_format'],
['currency_format'], ['currency_format'],
[ [

View File

@@ -36,6 +36,7 @@ jest.mock('@superset-ui/core', () => ({
jest.mock('../utils', () => ({ jest.mock('../utils', () => ({
getDateFormatter: jest.fn(() => (v: any) => `${v}pm`), getDateFormatter: jest.fn(() => (v: any) => `${v}pm`),
parseMetricValue: jest.fn(val => Number(val)), parseMetricValue: jest.fn(val => Number(val)),
getOriginalLabel: jest.fn((metric, metrics) => metric),
})); }));
describe('BigNumberTotal transformProps', () => { describe('BigNumberTotal transformProps', () => {

View File

@@ -29,7 +29,7 @@ import {
getValueFormatter, getValueFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { BigNumberTotalChartProps, BigNumberVizProps } from '../types'; import { BigNumberTotalChartProps, BigNumberVizProps } from '../types';
import { getDateFormatter, parseMetricValue } from '../utils'; import { getDateFormatter, getOriginalLabel, parseMetricValue } from '../utils';
import { Refs } from '../../types'; import { Refs } from '../../types';
export default function transformProps( export default function transformProps(
@@ -45,6 +45,7 @@ export default function transformProps(
datasource: { currencyFormats = {}, columnFormats = {} }, datasource: { currencyFormats = {}, columnFormats = {} },
} = chartProps; } = chartProps;
const { const {
metricNameFontSize,
headerFontSize, headerFontSize,
metric = 'value', metric = 'value',
subtitle, subtitle,
@@ -58,9 +59,12 @@ export default function transformProps(
subheaderFontSize, subheaderFontSize,
} = formData; } = formData;
const refs: Refs = {}; const refs: Refs = {};
const { data = [], coltypes = [] } = queriesData[0]; const { data = [], coltypes = [] } = queriesData[0] || {};
const granularity = extractTimegrain(rawFormData as QueryFormData); const granularity = extractTimegrain(rawFormData as QueryFormData);
const metrics = chartProps.datasource?.metrics || [];
const originalLabel = getOriginalLabel(metric, metrics);
const metricName = getMetricLabel(metric); const metricName = getMetricLabel(metric);
const showMetricName = chartProps.rawFormData?.show_metric_name ?? false;
const formattedSubtitle = subtitle?.trim() ? subtitle : subheader || ''; const formattedSubtitle = subtitle?.trim() ? subtitle : subheader || '';
const formattedSubtitleFontSize = subtitle?.trim() const formattedSubtitleFontSize = subtitle?.trim()
? (subtitleFontSize ?? 1) ? (subtitleFontSize ?? 1)
@@ -103,7 +107,6 @@ export default function transformProps(
const colorThresholdFormatters = const colorThresholdFormatters =
getColorFormatters(conditionalFormatting, data, false) ?? getColorFormatters(conditionalFormatting, data, false) ??
defaultColorFormatters; defaultColorFormatters;
return { return {
width, width,
height, height,
@@ -116,5 +119,8 @@ export default function transformProps(
onContextMenu, onContextMenu,
refs, refs,
colorThresholdFormatters, colorThresholdFormatters,
metricName: originalLabel,
showMetricName,
metricNameFontSize,
}; };
} }

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { PureComponent, MouseEvent } from 'react'; import { PureComponent, MouseEvent, createRef } from 'react';
import { import {
t, t,
getNumberFormatter, getNumberFormatter,
@@ -35,6 +35,7 @@ const defaultNumberFormatter = getNumberFormatter();
const PROPORTION = { const PROPORTION = {
// text size: proportion of the chart container sans trendline // text size: proportion of the chart container sans trendline
METRIC_NAME: 0.125,
KICKER: 0.1, KICKER: 0.1,
HEADER: 0.3, HEADER: 0.3,
SUBHEADER: 0.125, SUBHEADER: 0.125,
@@ -42,13 +43,20 @@ const PROPORTION = {
TRENDLINE: 0.3, TRENDLINE: 0.3,
}; };
class BigNumberVis extends PureComponent<BigNumberVizProps> { type BigNumberVisState = {
elementsRendered: boolean;
recalculateTrigger: boolean;
};
class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
static defaultProps = { static defaultProps = {
className: '', className: '',
headerFormatter: defaultNumberFormatter, headerFormatter: defaultNumberFormatter,
formatTime: getTimeFormatter(SMART_DATE_VERBOSE_ID), formatTime: getTimeFormatter(SMART_DATE_VERBOSE_ID),
headerFontSize: PROPORTION.HEADER, headerFontSize: PROPORTION.HEADER,
kickerFontSize: PROPORTION.KICKER, kickerFontSize: PROPORTION.KICKER,
metricNameFontSize: PROPORTION.METRIC_NAME,
showMetricName: true,
mainColor: BRAND_COLOR, mainColor: BRAND_COLOR,
showTimestamp: false, showTimestamp: false,
showTrendLine: false, showTrendLine: false,
@@ -58,6 +66,40 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
timeRangeFixed: false, timeRangeFixed: false,
}; };
// Create refs for each component to measure heights
metricNameRef = createRef<HTMLDivElement>();
kickerRef = createRef<HTMLDivElement>();
headerRef = createRef<HTMLDivElement>();
subheaderRef = createRef<HTMLDivElement>();
subtitleRef = createRef<HTMLDivElement>();
state = {
elementsRendered: false,
recalculateTrigger: false,
};
componentDidMount() {
// Wait for elements to render and then calculate heights
setTimeout(() => {
this.setState({ elementsRendered: true });
}, 0);
}
componentDidUpdate(prevProps: BigNumberVizProps) {
if (
prevProps.height !== this.props.height ||
prevProps.showTrendLine !== this.props.showTrendLine
) {
this.setState(prevState => ({
recalculateTrigger: !prevState.recalculateTrigger,
}));
}
}
getClassName() { getClassName() {
const { className, showTrendLine, bigNumberFallback } = this.props; const { className, showTrendLine, bigNumberFallback } = this.props;
const names = `superset-legacy-chart-big-number ${className} ${ const names = `superset-legacy-chart-big-number ${className} ${
@@ -92,6 +134,37 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
); );
} }
renderMetricName(maxHeight: number) {
const { metricName, width, showMetricName } = this.props;
if (!showMetricName || !metricName) return null;
const text = metricName;
const container = this.createTemporaryContainer();
document.body.append(container);
const fontSize = computeMaxFontSize({
text,
maxWidth: width,
maxHeight,
className: 'metric-name',
container,
});
container.remove();
return (
<div
ref={this.metricNameRef}
className="metric-name"
style={{
fontSize,
height: 'auto',
}}
>
{text}
</div>
);
}
renderKicker(maxHeight: number) { renderKicker(maxHeight: number) {
const { timestamp, showTimestamp, formatTime, width } = this.props; const { timestamp, showTimestamp, formatTime, width } = this.props;
if ( if (
@@ -118,6 +191,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
return ( return (
<div <div
ref={this.kickerRef}
className="kicker" className="kicker"
style={{ style={{
fontSize, fontSize,
@@ -173,6 +247,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
return ( return (
<div <div
ref={this.headerRef}
className="header-line" className="header-line"
style={{ style={{
display: 'flex', display: 'flex',
@@ -211,6 +286,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
return ( return (
<div <div
ref={this.subheaderRef}
className="subheader-line" className="subheader-line"
style={{ style={{
fontSize, fontSize,
@@ -256,6 +332,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
return ( return (
<> <>
<div <div
ref={this.subtitleRef}
className="subtitle-line subheader-line" className="subtitle-line subheader-line"
style={{ style={{
fontSize: `${fontSize}px`, fontSize: `${fontSize}px`,
@@ -316,6 +393,35 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
); );
} }
getTotalElementsHeight() {
const marginPerElement = 8; // theme.gridUnit = 4, so margin-bottom = 8px
const refs = [
this.metricNameRef,
this.kickerRef,
this.headerRef,
this.subheaderRef,
this.subtitleRef,
];
// Filter refs to only those with a current element
const visibleRefs = refs.filter(ref => ref.current);
const totalHeight = visibleRefs.reduce((sum, ref, index) => {
const height = ref.current?.offsetHeight || 0;
const margin = index < visibleRefs.length - 1 ? marginPerElement : 0;
return sum + height + margin;
}, 0);
return totalHeight;
}
shouldApplyOverflow(availableHeight: number) {
if (!this.state.elementsRendered) return false;
const totalHeight = this.getTotalElementsHeight();
return totalHeight > availableHeight;
}
render() { render() {
const { const {
showTrendLine, showTrendLine,
@@ -323,6 +429,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
kickerFontSize, kickerFontSize,
headerFontSize, headerFontSize,
subtitleFontSize, subtitleFontSize,
metricNameFontSize,
subheaderFontSize, subheaderFontSize,
} = this.props; } = this.props;
const className = this.getClassName(); const className = this.getClassName();
@@ -330,11 +437,31 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
if (showTrendLine) { if (showTrendLine) {
const chartHeight = Math.floor(PROPORTION.TRENDLINE * height); const chartHeight = Math.floor(PROPORTION.TRENDLINE * height);
const allTextHeight = height - chartHeight; const allTextHeight = height - chartHeight;
const shouldApplyOverflow = this.shouldApplyOverflow(allTextHeight);
return ( return (
<div className={className}> <div className={className}>
<div className="text-container" style={{ height: allTextHeight }}> <div
className="text-container"
style={{
height: allTextHeight,
...(shouldApplyOverflow
? {
display: 'block',
boxSizing: 'border-box',
overflowX: 'hidden',
overflowY: 'auto',
width: '100%',
}
: {}),
}}
>
{this.renderFallbackWarning()} {this.renderFallbackWarning()}
{this.renderMetricName(
Math.ceil(
(metricNameFontSize || 0) * (1 - PROPORTION.TRENDLINE) * height,
),
)}
{this.renderKicker( {this.renderKicker(
Math.ceil( Math.ceil(
(kickerFontSize || 0) * (1 - PROPORTION.TRENDLINE) * height, (kickerFontSize || 0) * (1 - PROPORTION.TRENDLINE) * height,
@@ -356,16 +483,33 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
</div> </div>
); );
} }
const shouldApplyOverflow = this.shouldApplyOverflow(height);
return ( return (
<div className={className} style={{ height }}> <div
{this.renderFallbackWarning()} className={className}
{this.renderKicker((kickerFontSize || 0) * height)} style={{
{this.renderHeader(Math.ceil(headerFontSize * height))} height,
{this.rendermetricComparisonSummary( ...(shouldApplyOverflow
Math.ceil(subheaderFontSize * height), ? {
)} display: 'block',
{this.renderSubtitle(Math.ceil(subtitleFontSize * height))} boxSizing: 'border-box',
overflowX: 'hidden',
overflowY: 'auto',
width: '100%',
}
: {}),
}}
>
<div className="text-container">
{this.renderFallbackWarning()}
{this.renderMetricName((metricNameFontSize || 0) * height)}
{this.renderKicker((kickerFontSize || 0) * height)}
{this.renderHeader(Math.ceil(headerFontSize * height))}
{this.rendermetricComparisonSummary(
Math.ceil(subheaderFontSize * height),
)}
{this.renderSubtitle(Math.ceil(subtitleFontSize * height))}
</div>
</div> </div>
); );
} }
@@ -400,7 +544,12 @@ export default styled(BigNumberVis)`
.kicker { .kicker {
line-height: 1em; line-height: 1em;
padding-bottom: 2em; margin-bottom: ${theme.gridUnit * 2}px;
}
.metric-name {
line-height: 1em;
margin-bottom: ${theme.gridUnit * 2}px;
} }
.header-line { .header-line {
@@ -416,12 +565,12 @@ export default styled(BigNumberVis)`
.subheader-line { .subheader-line {
line-height: 1em; line-height: 1em;
padding-bottom: 0; margin-bottom: ${theme.gridUnit * 2}px;
} }
.subtitle-line { .subtitle-line {
line-height: 1em; line-height: 1em;
padding-bottom: 0; margin-bottom: ${theme.gridUnit * 2}px;
} }
&.is-fallback-value { &.is-fallback-value {

View File

@@ -31,6 +31,8 @@ import {
subheaderFontSize, subheaderFontSize,
subtitleFontSize, subtitleFontSize,
subtitleControl, subtitleControl,
showMetricNameControl,
metricNameFontSizeWithVisibility,
} from '../sharedControls'; } from '../sharedControls';
const config: ControlPanelConfig = { const config: ControlPanelConfig = {
@@ -141,6 +143,8 @@ const config: ControlPanelConfig = {
[subheaderFontSize], [subheaderFontSize],
[subtitleControl], [subtitleControl],
[subtitleFontSize], [subtitleFontSize],
[showMetricNameControl],
[metricNameFontSizeWithVisibility],
['y_axis_format'], ['y_axis_format'],
['currency_format'], ['currency_format'],
[ [

View File

@@ -39,6 +39,7 @@ jest.mock('@superset-ui/core', () => ({
jest.mock('../utils', () => ({ jest.mock('../utils', () => ({
getDateFormatter: jest.fn(() => (v: any) => `${v}pm`), getDateFormatter: jest.fn(() => (v: any) => `${v}pm`),
parseMetricValue: jest.fn(val => Number(val)), parseMetricValue: jest.fn(val => Number(val)),
getOriginalLabel: jest.fn((metric, metrics) => metric),
})); }));
jest.mock('../../utils/tooltip', () => ({ jest.mock('../../utils/tooltip', () => ({

View File

@@ -35,7 +35,7 @@ import {
BigNumberWithTrendlineChartProps, BigNumberWithTrendlineChartProps,
TimeSeriesDatum, TimeSeriesDatum,
} from '../types'; } from '../types';
import { getDateFormatter, parseMetricValue } from '../utils'; import { getDateFormatter, parseMetricValue, getOriginalLabel } from '../utils';
import { getDefaultTooltip } from '../../utils/tooltip'; import { getDefaultTooltip } from '../../utils/tooltip';
import { Refs } from '../../types'; import { Refs } from '../../types';
@@ -62,6 +62,7 @@ export default function transformProps(
compareLag: compareLag_, compareLag: compareLag_,
compareSuffix = '', compareSuffix = '',
timeFormat, timeFormat,
metricNameFontSize,
headerFontSize, headerFontSize,
metric = 'value', metric = 'value',
showTimestamp, showTimestamp,
@@ -96,6 +97,9 @@ export default function transformProps(
const aggregatedData = hasAggregatedData ? aggregatedQueryData.data[0] : null; const aggregatedData = hasAggregatedData ? aggregatedQueryData.data[0] : null;
const refs: Refs = {}; const refs: Refs = {};
const metricName = getMetricLabel(metric); const metricName = getMetricLabel(metric);
const metrics = chartProps.datasource?.metrics || [];
const originalLabel = getOriginalLabel(metric, metrics);
const showMetricName = chartProps.rawFormData?.show_metric_name ?? false;
const compareLag = Number(compareLag_) || 0; const compareLag = Number(compareLag_) || 0;
let formattedSubheader = subheader; let formattedSubheader = subheader;
@@ -303,6 +307,9 @@ export default function transformProps(
headerFormatter, headerFormatter,
formatTime, formatTime,
formData, formData,
metricName: originalLabel,
showMetricName,
metricNameFontSize,
headerFontSize, headerFontSize,
subtitleFontSize, subtitleFontSize,
subtitle, subtitle,

View File

@@ -21,106 +21,68 @@
import { t } from '@superset-ui/core'; import { t } from '@superset-ui/core';
import { CustomControlItem } from '@superset-ui/chart-controls'; import { CustomControlItem } from '@superset-ui/chart-controls';
export const headerFontSize: CustomControlItem = { const FONT_SIZE_OPTIONS_SMALL = [
name: 'header_font_size', { label: t('Tiny'), value: 0.125 },
config: { { label: t('Small'), value: 0.15 },
type: 'SelectControl', { label: t('Normal'), value: 0.2 },
label: t('Big Number Font Size'), { label: t('Large'), value: 0.3 },
renderTrigger: true, { label: t('Huge'), value: 0.4 },
clearable: false, ];
default: 0.4,
// Values represent the percentage of space a header should take
options: [
{
label: t('Tiny'),
value: 0.2,
},
{
label: t('Small'),
value: 0.3,
},
{
label: t('Normal'),
value: 0.4,
},
{
label: t('Large'),
value: 0.5,
},
{
label: t('Huge'),
value: 0.6,
},
],
},
};
export const subtitleFontSize: CustomControlItem = { const FONT_SIZE_OPTIONS_LARGE = [
name: 'subtitle_font_size', { label: t('Tiny'), value: 0.2 },
config: { { label: t('Small'), value: 0.3 },
type: 'SelectControl', { label: t('Normal'), value: 0.4 },
label: t('Subtitle Font Size'), { label: t('Large'), value: 0.5 },
renderTrigger: true, { label: t('Huge'), value: 0.6 },
clearable: false, ];
default: 0.15,
// Values represent the percentage of space a subtitle should take function makeFontSizeControl(
options: [ name: string,
{ label: string,
label: t('Tiny'), defaultValue: number,
value: 0.125, options: { label: string; value: number }[],
}, ): CustomControlItem {
{ return {
label: t('Small'), name,
value: 0.15, config: {
}, type: 'SelectControl',
{ label: t(label),
label: t('Normal'), renderTrigger: true,
value: 0.2, clearable: false,
}, default: defaultValue,
{ options,
label: t('Large'), },
value: 0.3, };
}, }
{
label: t('Huge'), export const headerFontSize = makeFontSizeControl(
value: 0.4, 'header_font_size',
}, 'Big Number Font Size',
], 0.4,
}, FONT_SIZE_OPTIONS_LARGE,
}; );
export const subheaderFontSize: CustomControlItem = {
name: 'subheader_font_size', export const subtitleFontSize = makeFontSizeControl(
config: { 'subtitle_font_size',
type: 'SelectControl', 'Subtitle Font Size',
label: t('Subheader Font Size'), 0.15,
renderTrigger: true, FONT_SIZE_OPTIONS_SMALL,
clearable: false, );
default: 0.15,
// Values represent the percentage of space a subheader should take export const subheaderFontSize = makeFontSizeControl(
options: [ 'subheader_font_size',
{ 'Subheader Font Size',
label: t('Tiny'), 0.15,
value: 0.125, FONT_SIZE_OPTIONS_SMALL,
}, );
{
label: t('Small'), export const metricNameFontSize = makeFontSizeControl(
value: 0.15, 'metric_name_font_size',
}, 'Metric Name Font Size',
{ 0.15,
label: t('Normal'), FONT_SIZE_OPTIONS_SMALL,
value: 0.2, );
},
{
label: t('Large'),
value: 0.3,
},
{
label: t('Huge'),
value: 0.4,
},
],
},
};
export const subtitleControl: CustomControlItem = { export const subtitleControl: CustomControlItem = {
name: 'subtitle', name: 'subtitle',
@@ -131,3 +93,23 @@ export const subtitleControl: CustomControlItem = {
description: t('Description text that shows up below your Big Number'), description: t('Description text that shows up below your Big Number'),
}, },
}; };
export const showMetricNameControl: CustomControlItem = {
name: 'show_metric_name',
config: {
type: 'CheckboxControl',
label: t('Show Metric Name'),
renderTrigger: true,
default: false,
description: t('Whether to display the metric name'),
},
};
export const metricNameFontSizeWithVisibility: CustomControlItem = {
...metricNameFontSize,
config: {
...metricNameFontSize.config,
visibility: ({ controls }) => controls?.show_metric_name?.value === true,
resetOnHide: false,
},
};

View File

@@ -75,6 +75,10 @@ export type BigNumberVizProps = {
bigNumberFallback?: TimeSeriesDatum; bigNumberFallback?: TimeSeriesDatum;
headerFormatter: ValueFormatter | TimeFormatter; headerFormatter: ValueFormatter | TimeFormatter;
formatTime?: TimeFormatter; formatTime?: TimeFormatter;
metricName?: string;
friendlyMetricName?: string;
metricNameFontSize?: number;
showMetricName?: boolean;
headerFontSize: number; headerFontSize: number;
kickerFontSize?: number; kickerFontSize?: number;
subheader?: string; subheader?: string;

View File

@@ -22,6 +22,10 @@ import utc from 'dayjs/plugin/utc';
import { import {
getTimeFormatter, getTimeFormatter,
getTimeFormatterForGranularity, getTimeFormatterForGranularity,
isAdhocMetricSimple,
isSavedMetric,
Metric,
QueryFormMetric,
SMART_DATE_ID, SMART_DATE_ID,
TimeGranularity, TimeGranularity,
} from '@superset-ui/core'; } from '@superset-ui/core';
@@ -47,3 +51,43 @@ export const getDateFormatter = (
timeFormat === SMART_DATE_ID timeFormat === SMART_DATE_ID
? getTimeFormatterForGranularity(granularity) ? getTimeFormatterForGranularity(granularity)
: getTimeFormatter(timeFormat ?? fallbackFormat); : getTimeFormatter(timeFormat ?? fallbackFormat);
export function getOriginalLabel(
metric: QueryFormMetric,
metrics: Metric[] = [],
): string {
const metricLabel = typeof metric === 'string' ? metric : metric.label || '';
if (isSavedMetric(metric)) {
const metricEntry = metrics.find(m => m.metric_name === metric);
return (
metricEntry?.verbose_name ||
metricEntry?.metric_name ||
metric ||
'Unknown Metric'
);
}
if (isAdhocMetricSimple(metric)) {
const column = metric.column || {};
const columnName = column.column_name || 'unknown_column';
const verboseName = column.verbose_name || columnName;
const aggregate = metric.aggregate || 'UNKNOWN';
return metric.hasCustomLabel && metric.label
? metric.label
: `${aggregate}(${verboseName})`;
}
if (
typeof metric === 'object' &&
'expressionType' in metric &&
metric.expressionType === 'SQL' &&
'sqlExpression' in metric
) {
return metric.hasCustomLabel && metric.label
? metric.label
: metricLabel || 'Custom Metric';
}
return metricLabel || 'Unknown Metric';
}