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

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { PureComponent, MouseEvent } from 'react';
import { PureComponent, MouseEvent, createRef } from 'react';
import {
t,
getNumberFormatter,
@@ -35,6 +35,7 @@ const defaultNumberFormatter = getNumberFormatter();
const PROPORTION = {
// text size: proportion of the chart container sans trendline
METRIC_NAME: 0.125,
KICKER: 0.1,
HEADER: 0.3,
SUBHEADER: 0.125,
@@ -42,13 +43,20 @@ const PROPORTION = {
TRENDLINE: 0.3,
};
class BigNumberVis extends PureComponent<BigNumberVizProps> {
type BigNumberVisState = {
elementsRendered: boolean;
recalculateTrigger: boolean;
};
class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
static defaultProps = {
className: '',
headerFormatter: defaultNumberFormatter,
formatTime: getTimeFormatter(SMART_DATE_VERBOSE_ID),
headerFontSize: PROPORTION.HEADER,
kickerFontSize: PROPORTION.KICKER,
metricNameFontSize: PROPORTION.METRIC_NAME,
showMetricName: true,
mainColor: BRAND_COLOR,
showTimestamp: false,
showTrendLine: false,
@@ -58,6 +66,40 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
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() {
const { className, showTrendLine, bigNumberFallback } = this.props;
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) {
const { timestamp, showTimestamp, formatTime, width } = this.props;
if (
@@ -118,6 +191,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
return (
<div
ref={this.kickerRef}
className="kicker"
style={{
fontSize,
@@ -173,6 +247,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
return (
<div
ref={this.headerRef}
className="header-line"
style={{
display: 'flex',
@@ -211,6 +286,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
return (
<div
ref={this.subheaderRef}
className="subheader-line"
style={{
fontSize,
@@ -256,6 +332,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
return (
<>
<div
ref={this.subtitleRef}
className="subtitle-line subheader-line"
style={{
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() {
const {
showTrendLine,
@@ -323,6 +429,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
kickerFontSize,
headerFontSize,
subtitleFontSize,
metricNameFontSize,
subheaderFontSize,
} = this.props;
const className = this.getClassName();
@@ -330,11 +437,31 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
if (showTrendLine) {
const chartHeight = Math.floor(PROPORTION.TRENDLINE * height);
const allTextHeight = height - chartHeight;
const shouldApplyOverflow = this.shouldApplyOverflow(allTextHeight);
return (
<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.renderMetricName(
Math.ceil(
(metricNameFontSize || 0) * (1 - PROPORTION.TRENDLINE) * height,
),
)}
{this.renderKicker(
Math.ceil(
(kickerFontSize || 0) * (1 - PROPORTION.TRENDLINE) * height,
@@ -356,16 +483,33 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
</div>
);
}
const shouldApplyOverflow = this.shouldApplyOverflow(height);
return (
<div className={className} style={{ height }}>
{this.renderFallbackWarning()}
{this.renderKicker((kickerFontSize || 0) * height)}
{this.renderHeader(Math.ceil(headerFontSize * height))}
{this.rendermetricComparisonSummary(
Math.ceil(subheaderFontSize * height),
)}
{this.renderSubtitle(Math.ceil(subtitleFontSize * height))}
<div
className={className}
style={{
height,
...(shouldApplyOverflow
? {
display: 'block',
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>
);
}
@@ -400,7 +544,12 @@ export default styled(BigNumberVis)`
.kicker {
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 {
@@ -416,12 +565,12 @@ export default styled(BigNumberVis)`
.subheader-line {
line-height: 1em;
padding-bottom: 0;
margin-bottom: ${theme.gridUnit * 2}px;
}
.subtitle-line {
line-height: 1em;
padding-bottom: 0;
margin-bottom: ${theme.gridUnit * 2}px;
}
&.is-fallback-value {