mirror of
https://github.com/apache/superset.git
synced 2026-04-21 17:14:57 +00:00
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com> Co-authored-by: Diego Pucci <diegopucci.me@gmail.com> Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me> Co-authored-by: Geido <60598000+geido@users.noreply.github.com> Co-authored-by: Alexandru Soare <37236580+alexandrusoare@users.noreply.github.com> Co-authored-by: Damian Pendrak <dpendrak@gmail.com> Co-authored-by: Pius Iniobong <67148161+payose@users.noreply.github.com> Co-authored-by: Enzo Martellucci <enzomartellucci@gmail.com> Co-authored-by: Kamil Gabryjelski <kamil.gabryjelski@gmail.com>
244 lines
6.1 KiB
TypeScript
244 lines
6.1 KiB
TypeScript
/* eslint-disable class-methods-use-this */
|
|
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { ReactNode } from 'react';
|
|
import {
|
|
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,
|
|
Tooltip,
|
|
XYChart,
|
|
buildChartTheme,
|
|
} from '@visx/xychart';
|
|
import { extendedDayjs } from '@superset-ui/core/utils/dates';
|
|
|
|
interface Props {
|
|
ariaLabel: string;
|
|
dataKey: string;
|
|
className?: string;
|
|
data: Array<number>;
|
|
entries: Array<any>;
|
|
height: number;
|
|
numberFormat: string;
|
|
dateFormat: string;
|
|
renderTooltip: ({ index }: { index: number }) => ReactNode;
|
|
showYAxis: boolean;
|
|
width: number;
|
|
yAxisBounds: Array<number | undefined>;
|
|
}
|
|
|
|
const MARGIN = {
|
|
top: 8,
|
|
right: 8,
|
|
bottom: 8,
|
|
left: 8,
|
|
};
|
|
|
|
function getSparklineTextWidth(text: string) {
|
|
return (
|
|
getTextDimension({
|
|
text,
|
|
style: {
|
|
fontSize: '12px',
|
|
fontWeight: 200,
|
|
letterSpacing: 0.4,
|
|
},
|
|
}).width + 5
|
|
);
|
|
}
|
|
|
|
function isValidBoundValue(value?: number | string) {
|
|
return (
|
|
value !== null &&
|
|
value !== undefined &&
|
|
value !== '' &&
|
|
!Number.isNaN(value)
|
|
);
|
|
}
|
|
|
|
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),
|
|
);
|
|
}
|
|
|
|
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 xAccessor = (d: any) => d.x;
|
|
const yAccessor = (d: any) => d.y;
|
|
|
|
return (
|
|
<>
|
|
<XYChart
|
|
accessibilityLabel={ariaLabel}
|
|
width={width}
|
|
height={height}
|
|
margin={margin}
|
|
yScale={{
|
|
...yScaleConfig,
|
|
}}
|
|
xScale={{ type: 'band', paddingInner: 0.5 }}
|
|
theme={xyTheme}
|
|
>
|
|
{showYAxis && (
|
|
<Axis
|
|
hideAxisLine
|
|
hideTicks
|
|
numTicks={2}
|
|
orientation="right"
|
|
tickFormat={(d: any) => formatNumber(numberFormat, d)}
|
|
tickValues={[min, max]}
|
|
/>
|
|
)}
|
|
{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.colorText}`,
|
|
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,
|
|
extendedDayjs.utc(entries[idx].time).toDate(),
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}}
|
|
/>
|
|
</XYChart>
|
|
<style>
|
|
{`svg:not(:root) {
|
|
overflow: visible;
|
|
}`}
|
|
</style>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default SparklineCell;
|