mirror of
https://github.com/apache/superset.git
synced 2026-04-20 08:34:37 +00:00
chore: refactor SparklineCell for react 17 and react 18 upgrade (#23145)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user