mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
Co-authored-by: Fardin Mustaque <fardinmustaque@Fardins-Mac-mini.local>
This commit is contained in:
@@ -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 && (
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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'],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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'],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -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', () => ({
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user