chore: refactor SparklineCell for react 17 and react 18 upgrade (#23145)

This commit is contained in:
Lily Kuang
2023-02-24 12:49:53 -08:00
committed by GitHub
parent 3484e8ea7b
commit a70b7ac3b0
4 changed files with 1231 additions and 154 deletions

View File

@@ -18,39 +18,37 @@
* under the License.
*/
import React from 'react';
import moment from 'moment';
import {
Sparkline,
formatNumber,
formatTime,
getTextDimension,
useTheme,
} from '@superset-ui/core';
import { GridRows } from '@visx/grid';
import { LinearScaleConfig, scaleLinear } from '@visx/scale';
import { AxisScaleOutput } from '@visx/axis';
import {
Axis,
LineSeries,
PointSeries,
HorizontalReferenceLine,
VerticalReferenceLine,
WithTooltip,
} from '@data-ui/sparkline';
import { getTextDimension, formatNumber } from '@superset-ui/core';
Tooltip,
XYChart,
buildChartTheme,
} from '@visx/xychart';
interface Props {
ariaLabel: string;
dataKey: string;
className?: string;
data: Array<number>;
entries: Array<any>;
height: number;
numberFormat: string;
renderTooltip: ({ index }: { index: number }) => void;
dateFormat: string;
renderTooltip: ({ index }: { index: number }) => React.ReactNode;
showYAxis: boolean;
width: number;
yAxisBounds: Array<number>;
data: Array<number>;
}
interface TooltipProps {
onMouseLeave: () => void;
onMouseMove: () => void;
tooltipData: {
index: number;
};
}
interface Yscale {
min?: number;
max?: number;
yAxisBounds: Array<number | undefined>;
}
const MARGIN = {
@@ -59,12 +57,6 @@ const MARGIN = {
bottom: 8,
left: 8,
};
const tooltipProps = {
style: {
opacity: 0.8,
},
offsetTop: 0,
};
function getSparklineTextWidth(text: string) {
return (
@@ -88,114 +80,164 @@ function isValidBoundValue(value?: number | string) {
);
}
class SparklineCell extends React.Component<Props, {}> {
renderHorizontalReferenceLine(value?: number, label?: string) {
return (
<HorizontalReferenceLine
reference={value}
labelPosition="right"
renderLabel={() => label}
stroke="#bbb"
strokeDasharray="3 3"
strokeWidth={1}
/>
const SparklineCell = ({
ariaLabel,
dataKey,
data,
width = 300,
height = 50,
numberFormat = '',
dateFormat = '',
yAxisBounds = [undefined, undefined],
showYAxis = false,
entries = [],
}: Props) => {
const theme = useTheme();
const xyTheme = buildChartTheme({
backgroundColor: `${theme.colors.grayscale.light5}`,
colors: [`${theme.colors.grayscale.base}`],
gridColor: `${theme.colors.grayscale.light1}`,
gridColorDark: `${theme.colors.grayscale.base}`,
tickLength: 6,
});
const yScaleConfig: LinearScaleConfig<AxisScaleOutput> = {
type: 'linear',
zero: false,
};
let hasMinBound = false;
let hasMaxBound = false;
let min: number = data.reduce(
(acc, current) => Math.min(acc, current),
data[0],
);
let max: number = data.reduce(
(acc, current) => Math.max(acc, current),
data[0],
);
if (yAxisBounds) {
const [minBound, maxBound] = yAxisBounds;
hasMinBound = isValidBoundValue(minBound);
if (hasMinBound) {
if (minBound !== undefined && minBound <= 0) {
yScaleConfig.zero = true;
}
min = minBound || min;
}
hasMaxBound = isValidBoundValue(maxBound);
if (hasMaxBound) {
max = maxBound || max;
}
yScaleConfig.domain = [min, max];
}
let minLabel: string;
let maxLabel: string;
let labelLength = 0;
if (showYAxis) {
yScaleConfig.domain = [min, max];
minLabel = formatNumber(numberFormat, min);
maxLabel = formatNumber(numberFormat, max);
labelLength = Math.max(
getSparklineTextWidth(minLabel),
getSparklineTextWidth(maxLabel),
);
}
render() {
const {
width = 300,
height = 50,
data,
ariaLabel,
numberFormat = undefined,
yAxisBounds = [undefined, undefined],
showYAxis = false,
renderTooltip = () => <div />,
} = this.props;
const margin = {
...MARGIN,
right: MARGIN.right + labelLength,
};
const innerWidth = width - margin.left - margin.right;
const chartData = data.map((num, idx) => ({
x: idx,
y: num,
}));
const yScale: Yscale = {};
let hasMinBound = false;
let hasMaxBound = false;
const xAccessor = (d: any) => d.x;
const yAccessor = (d: any) => d.y;
if (yAxisBounds) {
const [minBound, maxBound] = yAxisBounds;
hasMinBound = isValidBoundValue(minBound);
if (hasMinBound) {
yScale.min = minBound;
}
hasMaxBound = isValidBoundValue(maxBound);
if (hasMaxBound) {
yScale.max = maxBound;
}
}
let min: number | undefined;
let max: number | undefined;
let minLabel: string;
let maxLabel: string;
let labelLength = 0;
if (showYAxis) {
const [minBound, maxBound] = yAxisBounds;
min = hasMinBound
? minBound
: data.reduce((acc, current) => Math.min(acc, current), data[0]);
max = hasMaxBound
? maxBound
: data.reduce((acc, current) => Math.max(acc, current), data[0]);
minLabel = formatNumber(numberFormat, min);
maxLabel = formatNumber(numberFormat, max);
labelLength = Math.max(
getSparklineTextWidth(minLabel),
getSparklineTextWidth(maxLabel),
);
}
const margin = {
...MARGIN,
right: MARGIN.right + labelLength,
};
return (
<WithTooltip
tooltipProps={tooltipProps}
hoverStyles={null}
renderTooltip={renderTooltip}
return (
<>
<XYChart
accessibilityLabel={ariaLabel}
width={width}
height={height}
margin={margin}
yScale={{
...yScaleConfig,
}}
xScale={{ type: 'band', paddingInner: 0.5 }}
theme={xyTheme}
>
{({ onMouseLeave, onMouseMove, tooltipData }: TooltipProps) => (
<Sparkline
ariaLabel={ariaLabel}
width={width}
height={height}
margin={margin}
data={data}
onMouseLeave={onMouseLeave}
onMouseMove={onMouseMove}
{...yScale}
>
{showYAxis && this.renderHorizontalReferenceLine(min, minLabel)}
{showYAxis && this.renderHorizontalReferenceLine(max, maxLabel)}
<LineSeries showArea={false} stroke="#767676" />
{tooltipData && (
<VerticalReferenceLine
reference={tooltipData.index}
strokeDasharray="3 3"
strokeWidth={1}
/>
)}
{tooltipData && (
<PointSeries
points={[tooltipData.index]}
fill="#767676"
strokeWidth={1}
/>
)}
</Sparkline>
{showYAxis && (
<Axis
hideAxisLine
hideTicks
numTicks={2}
orientation="right"
tickFormat={(d: any) => formatNumber(numberFormat, d)}
tickValues={[min, max]}
/>
)}
</WithTooltip>
);
}
}
{showYAxis && min !== undefined && max !== undefined && (
<GridRows
left={margin.left}
scale={scaleLinear({
range: [height - margin.top, margin.bottom],
domain: [min, max],
})}
width={innerWidth}
strokeDasharray="3 3"
stroke={`${theme.colors.grayscale.light1}`}
tickValues={[min, max]}
/>
)}
<LineSeries
data={chartData}
dataKey={dataKey}
xAccessor={xAccessor}
yAccessor={yAccessor}
/>
<Tooltip
glyphStyle={{ strokeWidth: 1 }}
showDatumGlyph
showVerticalCrosshair
snapTooltipToDatumX
snapTooltipToDatumY
verticalCrosshairStyle={{
stroke: `${theme.colors.grayscale.dark1}`,
strokeDasharray: '3 3',
strokeWidth: 1,
}}
renderTooltip={({ tooltipData }) => {
const idx = tooltipData?.datumByKey[dataKey].index;
return (
<div>
<strong>
{idx !== undefined && formatNumber(numberFormat, data[idx])}
</strong>
<div>
{idx !== undefined &&
formatTime(
dateFormat,
moment.utc(entries[idx].time).toDate(),
)}
</div>
</div>
);
}}
/>
</XYChart>
<style>
{`svg:not(:root) {
overflow: visible;
}`}
</style>
</>
);
};
export default SparklineCell;

View File

@@ -21,12 +21,11 @@ import PropTypes from 'prop-types';
import Mustache from 'mustache';
import { scaleLinear } from 'd3-scale';
import TableView from 'src/components/TableView';
import { formatNumber, formatTime, styled, t } from '@superset-ui/core';
import { styled, t } from '@superset-ui/core';
import {
InfoTooltipWithTrigger,
MetricOption,
} from '@superset-ui/chart-controls';
import moment from 'moment';
import sortNumericValues from 'src/utils/sortNumericValues';
import FormattedNumber from './FormattedNumber';
@@ -163,25 +162,16 @@ const TimeTable = ({
return (
<SparklineCell
ariaLabel={`spark-${valueField}`}
width={parseInt(column.width, 10) || 300}
height={parseInt(column.height, 10) || 50}
data={sparkData}
data-value={sparkData[sparkData.length - 1]}
ariaLabel={`spark-${valueField}`}
dataKey={`spark-${valueField}`}
dateFormat={column.dateFormat}
numberFormat={column.d3format}
yAxisBounds={column.yAxisBounds}
showYAxis={column.showYAxis}
renderTooltip={({ index }) => (
<div>
<strong>{formatNumber(column.d3format, sparkData[index])}</strong>
<div>
{formatTime(
column.dateFormat,
moment.utc(entries[index].time).toDate(),
)}
</div>
</div>
)}
entries={entries}
/>
);
};