recharts#Area TypeScript Examples
The following examples show how to use
recharts#Area.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.tsx From vvs-ui with GNU General Public License v3.0 | 5 votes |
LineChart = ({ data, setHoverValue, setHoverDate }: LineChartProps) => {
const { theme } = useTheme()
if (!data || data.length === 0) {
return <LineChartLoader />
}
return (
<ResponsiveContainer>
<AreaChart
data={data}
width={300}
height={308}
margin={{
top: 5,
right: 15,
left: 0,
bottom: 5,
}}
onMouseLeave={() => {
if (setHoverDate) setHoverDate(undefined)
if (setHoverValue) setHoverValue(undefined)
}}
>
<XAxis
dataKey="time"
axisLine={false}
tickLine={false}
tickFormatter={(time) => format(time, 'dd')}
minTickGap={10}
/>
<YAxis
dataKey="value"
tickCount={6}
scale="linear"
axisLine={false}
tickLine={false}
fontSize="12px"
tickFormatter={(val) => `$${formatAmount(val)}`}
orientation="right"
tick={{ dx: 10, fill: theme.colors.textSubtle }}
/>
<Tooltip
cursor={{ stroke: theme.colors.primary }}
contentStyle={{ display: 'none' }}
formatter={(tooltipValue, name, props) => (
<HoverUpdater payload={props.payload} setHoverValue={setHoverValue} setHoverDate={setHoverDate} />
)}
/>
<Area
dataKey="value"
type="monotone"
stroke={theme.colors.primary}
fill={theme.colors.lightBlue}
strokeWidth={2}
/>
</AreaChart>
</ResponsiveContainer>
)
}
Example #2
Source File: Question.tsx From project-loved-web with MIT License | 5 votes |
function ComparingChart({ answers, comparingStatistic }: ComparingChartProps) {
const intl = useIntl();
const colors = useColors();
const xAxisProps: XAxisProps = {
dataKey: 'statistic',
interval: 'preserveStartEnd',
stroke: colors.content,
tick: { fill: colors.content },
tickLine: { stroke: colors.content },
};
if (comparingStatistic === 'rank') {
xAxisProps.domain = ['dataMin', 'dataMax'];
xAxisProps.scale = 'log';
xAxisProps.tickFormatter = (value) => intl.formatNumber(value);
xAxisProps.type = 'number';
}
return (
<AreaChart data={answers} width={700} height={175}>
<defs>
<linearGradient id='answerColor' x1='0' y1='0' x2='0' y2='1'>
<stop offset='0%' stopColor={colors['rating-2']} stopOpacity={0.9} />
<stop offset='20%' stopColor={colors['rating-1']} stopOpacity={0.8} />
<stop offset='40%' stopColor={colors['rating-0']} stopOpacity={0.7} />
<stop offset='60%' stopColor={colors['rating--1']} stopOpacity={0.6} />
<stop offset='80%' stopColor={colors['rating--2']} stopOpacity={0.5} />
<stop offset='100%' stopColor={colors['rating--2']} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis {...xAxisProps} />
<YAxis dataKey='average' domain={[0, 5]} hide />
<Area
type='monotone'
dataKey='average'
stroke={colors.content}
fillOpacity={1}
fill='url(#answerColor)'
/>
</AreaChart>
);
}
Example #3
Source File: index.tsx From glide-frontend with GNU General Public License v3.0 | 5 votes |
LineChart = ({ data, setHoverValue, setHoverDate }: LineChartProps) => {
const { theme } = useTheme()
if (!data || data.length === 0) {
return <LineChartLoader />
}
return (
<ResponsiveContainer>
<AreaChart
data={data}
width={300}
height={308}
margin={{
top: 5,
right: 15,
left: 0,
bottom: 5,
}}
onMouseLeave={() => {
if (setHoverDate) setHoverDate(undefined)
if (setHoverValue) setHoverValue(undefined)
}}
>
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={theme.colors.inputSecondary} stopOpacity={0.5} />
<stop offset="100%" stopColor={theme.colors.secondary} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis
dataKey="time"
axisLine={false}
tickLine={false}
tickFormatter={(time) => format(time, 'dd')}
minTickGap={10}
/>
<YAxis
dataKey="value"
tickCount={6}
scale="linear"
axisLine={false}
tickLine={false}
fontSize="12px"
tickFormatter={(val) => `$${formatAmount(val)}`}
orientation="right"
tick={{ dx: 10, fill: theme.colors.textSubtle }}
/>
<Tooltip
cursor={{ stroke: theme.colors.secondary }}
contentStyle={{ display: 'none' }}
formatter={(tooltipValue, name, props) => (
<HoverUpdater payload={props.payload} setHoverValue={setHoverValue} setHoverDate={setHoverDate} />
)}
/>
<Area dataKey="value" type="monotone" stroke={theme.colors.secondary} fill="url(#gradient)" strokeWidth={2} />
</AreaChart>
</ResponsiveContainer>
)
}
Example #4
Source File: ChartDetailsSinglestat.tsx From kubenav with MIT License | 5 votes |
ChartDetailsSinglestat: React.FunctionComponent<IChartDetailsSinglestatProps> = ({
unit,
results,
}: IChartDetailsSinglestatProps) => {
const context = useContext<IContext>(AppContext);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const series: any = [];
for (let i = 0; i < results.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = [];
for (let j = 0; j < results[i].values.length; j++) {
data.push({
time: results[i].values[j][0],
value: parseFloat(results[i].values[j][1]),
});
}
series.push({ name: results[i].label, data: data });
}
return (
<IonRow
style={{
fontSize: '20px',
fontWeight: 500,
color: isDarkMode(context.settings.theme)
? isPlatform('ios')
? '#ffffff'
: '#dbdbdb'
: isPlatform('ios')
? '#000000'
: '#262626',
height: '100px',
width: '100%',
}}
>
{series.length > 0 ? (
<div
style={{
margin: 'auto',
left: 0,
right: 0,
top: 40,
position: 'absolute',
display: 'flex',
justifyContent: 'center',
zIndex: 1000,
}}
>
{series[0].data[series[0].data.length - 1].value.toFixed(2)} {unit ? unit : ''}
</div>
) : null}
<IonCol style={{ padding: '0px' }}>
<ResponsiveContainer>
<AreaChart>
{series.map((serie, index) => (
<Area
key={index}
dataKey="value"
// NOTE: https://github.com/recharts/recharts/issues/2487
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
data={serie.data}
name={serie.name}
stroke="#326ce5"
fill="#326ce5"
fillOpacity={0.2}
/>
))}
</AreaChart>
</ResponsiveContainer>
</IonCol>
</IonRow>
);
}
Example #5
Source File: OrderChart.tsx From ra-enterprise-demo with MIT License | 5 votes |
OrderChart: FC<{ orders?: Order[] }> = ({ orders }) => {
const translate = useTranslate();
if (!orders) return null;
return (
<Card>
<CardHeader title={translate('pos.dashboard.month_history')} />
<CardContent>
<div style={{ width: '100%', height: 300 }}>
<ResponsiveContainer>
<AreaChart data={getRevenuePerDay(orders)}>
<defs>
<linearGradient
id="colorUv"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor="#8884d8"
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor="#8884d8"
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis
dataKey="date"
name="Date"
type="number"
scale="time"
domain={[
addDays(aMonthAgo, 1).getTime(),
new Date().getTime(),
]}
tickFormatter={dateFormatter}
/>
<YAxis dataKey="total" name="Revenue" unit="€" />
<CartesianGrid strokeDasharray="3 3" />
<Tooltip
cursor={{ strokeDasharray: '3 3' }}
formatter={(value): string =>
new Intl.NumberFormat(undefined, {
style: 'currency',
currency: 'USD',
}).format(value as any)
}
labelFormatter={(label: any): string =>
dateFormatter(label)
}
/>
<Area
type="monotone"
dataKey="total"
stroke="#8884d8"
strokeWidth={2}
fill="url(#colorUv)"
/>
</AreaChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
);
}
Example #6
Source File: WithdrawGraph.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 5 votes |
WithdrawGraph: FC = () => {
const { data } = useStakedTokenQuery() ?? {}
const weightedTimestamp = data?.stakedToken?.accounts?.[0]?.balance?.weightedTimestamp ?? nowUnix
const graphData = useMemo(() => {
const weeksStaked = (nowUnix - weightedTimestamp) / WEEK
const data = generateData(weeksStaked)
const ticks = [...new Set(data.map(d => d.week))]
return { data, ticks }
}, [weightedTimestamp])
return (
<Container>
<ResponsiveContainer width="100%" aspect={1.75}>
<AreaChart data={graphData.data}>
<defs>
<linearGradient id="area" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={Color.blue} stopOpacity={0.5} />
<stop offset="95%" stopColor={Color.blue} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis
dataKey="week"
tickFormatter={w => `${w / WEEK}`}
axisLine={false}
padding={{ left: 16 }}
tickLine={false}
ticks={graphData.ticks}
/>
<YAxis tickCount={2} tickFormatter={m => `${m}%`} axisLine={false} padding={{ bottom: 16 }} tickLine={false} width={24} />
<Tooltip
cursor
labelFormatter={week => `+${(week as number) / WEEK} weeks`}
formatter={fee => `${(fee as number).toFixed(4)}%`}
separator=""
contentStyle={{
fontSize: '14px',
padding: '8px',
background: 'rgba(255, 255, 255, 0.8)',
textAlign: 'right',
border: 'none',
borderRadius: '4px',
color: Color.black,
}}
wrapperStyle={{
top: 0,
left: 0,
}}
/>
<Area type="monotone" name={'Fee: '} dataKey="fee" stroke={Color.blue} strokeWidth={2} fill="url(#area)" />
</AreaChart>
</ResponsiveContainer>
</Container>
)
}
Example #7
Source File: ClaimGraph.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 5 votes |
ClaimGraph: FC = () => {
const rewardsEarned = useRewardsEarned()
const data = useMemo<DataType[]>(() => {
return [
{
mta: 0,
ordering: 0,
},
{
mta: rewardsEarned?.rewards ?? 0,
ordering: 1,
},
]
}, [rewardsEarned])
return (
<Container>
<ResponsiveContainer width="100%" aspect={1.75}>
<AreaChart data={data}>
<defs>
<linearGradient id="area" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={Color.blue} stopOpacity={0.5} />
<stop offset="95%" stopColor={Color.blue} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis
dataKey="ordering"
tickFormatter={ordering => (ordering === 0 ? 'Last claim' : 'Now')}
axisLine={false}
padding={{ left: 16 }}
tickLine={false}
/>
<YAxis tickCount={2} tickFormatter={m => `${m}`} axisLine={false} padding={{ bottom: 16 }} tickLine={false} width={32} />
<Tooltip
cursor
label=""
labelFormatter={w => (w === 0 ? 'Last claim' : 'Available to claim')}
formatter={mta => `${(mta as number).toFixed(2)} MTA`}
separator=""
contentStyle={{
fontSize: '14px',
padding: '8px',
background: 'rgba(255, 255, 255, 0.8)',
textAlign: 'right',
border: 'none',
borderRadius: '4px',
color: Color.black,
}}
wrapperStyle={{
top: 0,
left: 0,
}}
/>
<Area type="monotone" name={'Earned: '} dataKey="mta" stroke={Color.blue} strokeWidth={2} fill="url(#area)" />
</AreaChart>
</ResponsiveContainer>
</Container>
)
}
Example #8
Source File: StakeGraph.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 5 votes |
StakeGraph: FC = () => {
const { data: tokenData } = useStakedTokenQuery()
const weightedTimestamp = tokenData?.stakedToken?.accounts?.[0]?.balance?.weightedTimestamp
const data = generateData(weightedTimestamp)
const ticks = removeDuplicatesBy(x => x.multiplier, data).map(v => v.week)
return (
<Container>
<ResponsiveContainer width="100%" aspect={1.75}>
<AreaChart data={data}>
<defs>
<linearGradient id="area" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={Color.blue} stopOpacity={0.5} />
<stop offset="95%" stopColor={Color.blue} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis dataKey="week" tickFormatter={w => `${w / WEEK}`} axisLine={false} padding={{ left: 16 }} tickLine={false} ticks={ticks} />
<YAxis
domain={['dataMin', 'dataMax']}
tickCount={4}
tickFormatter={m => `${m}x`}
axisLine={false}
padding={{ bottom: 16 }}
tickLine={false}
width={32}
/>
<Tooltip
cursor
labelFormatter={week => `+${(week as number) / WEEK} weeks`}
formatter={multiplier => multiplier}
separator=""
contentStyle={{
fontSize: '14px',
padding: '8px',
background: 'rgba(255, 255, 255, 0.8)',
textAlign: 'right',
border: 'none',
borderRadius: '4px',
color: Color.black,
}}
wrapperStyle={{
top: 0,
left: 0,
}}
/>
<Area type="stepAfter" name={`multiplier: `} dataKey="multiplier" stroke={Color.blue} strokeWidth={2} fill="url(#area)" />
</AreaChart>
</ResponsiveContainer>
</Container>
)
}
Example #9
Source File: LinearGraphWidget.tsx From console with GNU Affero General Public License v3.0 | 4 votes |
LinearGraphWidget = ({
classes,
title,
timeStart,
timeEnd,
propLoading,
panelItem,
apiPrefix,
hideYAxis = false,
areaWidget = false,
yAxisFormatter = (item: string) => item,
xAxisFormatter = (item: string) => item,
zoomActivated = false,
}: ILinearGraphWidget) => {
const dispatch = useDispatch();
const [loading, setLoading] = useState<boolean>(true);
const [data, setData] = useState<object[]>([]);
const [dataMax, setDataMax] = useState<number>(0);
const [result, setResult] = useState<IDashboardPanel | null>(null);
useEffect(() => {
if (propLoading) {
setLoading(true);
}
}, [propLoading]);
useEffect(() => {
if (loading) {
let stepCalc = 0;
if (timeStart !== null && timeEnd !== null) {
const secondsInPeriod = timeEnd.unix() - timeStart.unix();
const periods = Math.floor(secondsInPeriod / 60);
stepCalc = periods < 1 ? 15 : periods;
}
api
.invoke(
"GET",
`/api/v1/${apiPrefix}/info/widgets/${
panelItem.id
}/?step=${stepCalc}&${
timeStart !== null ? `&start=${timeStart.unix()}` : ""
}${timeStart !== null && timeEnd !== null ? "&" : ""}${
timeEnd !== null ? `end=${timeEnd.unix()}` : ""
}`
)
.then((res: any) => {
const widgetsWithValue = widgetDetailsToPanel(res, panelItem);
setData(widgetsWithValue.data);
setResult(widgetsWithValue);
setLoading(false);
let maxVal = 0;
for (const dp of widgetsWithValue.data) {
for (const key in dp) {
if (key === "name") {
continue;
}
let val = parseInt(dp[key]);
if (isNaN(val)) {
val = 0;
}
if (maxVal < val) {
maxVal = val;
}
}
}
setDataMax(maxVal);
})
.catch((err: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(err));
setLoading(false);
});
}
}, [loading, panelItem, timeEnd, timeStart, dispatch, apiPrefix]);
let intervalCount = Math.floor(data.length / 5);
const linearConfiguration = result
? (result?.widgetConfiguration as ILinearGraphConfiguration[])
: [];
const CustomizedDot = (prop: any) => {
const { cx, cy, index } = prop;
if (index % 3 !== 0) {
return null;
}
return <circle cx={cx} cy={cy} r={3} strokeWidth={0} fill="#07264A" />;
};
const theme = useTheme();
const biggerThanMd = useMediaQuery(theme.breakpoints.up("md"));
return (
<Box className={zoomActivated ? "" : classes.singleValueContainer}>
{!zoomActivated && (
<div className={classes.titleContainer}>
{title} <ExpandGraphLink panelItem={panelItem} />
</div>
)}
<Box
sx={
zoomActivated
? { flexDirection: "column" }
: {
height: "100%",
display: "grid",
gridTemplateColumns: {
md: "1fr 1fr",
sm: "1fr",
},
}
}
style={areaWidget ? { gridTemplateColumns: "1fr" } : {}}
>
{loading && <Loader className={classes.loadingAlign} />}
{!loading && (
<React.Fragment>
<div
className={
zoomActivated ? classes.zoomChartCont : classes.chartCont
}
>
<ResponsiveContainer width="99%">
<AreaChart
data={data}
margin={{
top: 5,
right: 20,
left: hideYAxis ? 20 : 5,
bottom: 0,
}}
>
{areaWidget && (
<defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#2781B0" stopOpacity={1} />
<stop
offset="100%"
stopColor="#ffffff"
stopOpacity={0}
/>
<stop
offset="95%"
stopColor="#ffffff"
stopOpacity={0.8}
/>
</linearGradient>
</defs>
)}
<CartesianGrid
strokeDasharray={areaWidget ? "2 2" : "5 5"}
strokeWidth={1}
strokeOpacity={1}
stroke={"#eee0e0"}
vertical={!areaWidget}
/>
<XAxis
dataKey="name"
tickFormatter={(value: any) => xAxisFormatter(value)}
interval={intervalCount}
tick={{
fontSize: "68%",
fontWeight: "normal",
color: "#404143",
}}
tickCount={10}
stroke={"#082045"}
/>
<YAxis
type={"number"}
domain={[0, dataMax * 1.1]}
hide={hideYAxis}
tickFormatter={(value: any) => yAxisFormatter(value)}
tick={{
fontSize: "68%",
fontWeight: "normal",
color: "#404143",
}}
stroke={"#082045"}
/>
{linearConfiguration.map((section, index) => {
return (
<Area
key={`area-${section.dataKey}-${index.toString()}`}
type="monotone"
dataKey={section.dataKey}
isAnimationActive={false}
stroke={!areaWidget ? section.lineColor : "#D7E5F8"}
fill={areaWidget ? "url(#colorUv)" : section.fillColor}
fillOpacity={areaWidget ? 0.65 : 0}
strokeWidth={!areaWidget ? 3 : 0}
strokeLinecap={"round"}
dot={areaWidget ? <CustomizedDot /> : false}
/>
);
})}
<Tooltip
content={
<LineChartTooltip
linearConfiguration={linearConfiguration}
yAxisFormatter={yAxisFormatter}
/>
}
wrapperStyle={{
zIndex: 5000,
}}
/>
</AreaChart>
</ResponsiveContainer>
</div>
{!areaWidget && (
<Fragment>
{zoomActivated && (
<Fragment>
<strong>Series</strong>
<br />
<br />
</Fragment>
)}
{biggerThanMd && (
<div className={classes.legendChart}>
{linearConfiguration.map((section, index) => {
return (
<div
className={classes.singleLegendContainer}
key={`legend-${section.keyLabel}-${index.toString()}`}
>
<div
className={classes.colorContainer}
style={{ backgroundColor: section.lineColor }}
/>
<div className={classes.legendLabel}>
{section.keyLabel}
</div>
</div>
);
})}
</div>
)}
</Fragment>
)}
</React.Fragment>
)}
</Box>
</Box>
);
}
Example #10
Source File: index.tsx From korona-info with MIT License | 4 votes |
Index: NextPage<{ groupedCoronaData: GroupedData, hospitalised: HospitalData[] }> = ({
groupedCoronaData, hospitalised
}: {
groupedCoronaData: GroupedData;
hospitalised: HospitalData[];
}) => {
const [selectedHealthCareDistrict, selectHealthCareDistrict] = useState<
string
>('all');
const confirmed = groupedCoronaData[selectedHealthCareDistrict].confirmed;
const deaths = groupedCoronaData[selectedHealthCareDistrict].deaths;
const recovered = groupedCoronaData[selectedHealthCareDistrict].recovered;
const allConfirmed = groupedCoronaData.all.confirmed;
const toast = useToast()
const latestInfection = confirmed.length
? format(
utcToZonedTime(
new Date(confirmed[confirmed.length - 1].date),
timeZone
),
'dd.MM.yyyy - HH:mm',
{ timeZone }
)
: null;
const latestInfectionDistrict =
confirmed[confirmed.length - 1]?.healthCareDistrict;
const latestDeath = deaths.length
? format(
utcToZonedTime(new Date(deaths[deaths.length - 1].date), timeZone),
'd.M.yyyy'
)
: null;
const latestDeathDistrict = deaths.length
? deaths[deaths.length - 1].area
: null;
const latestRecoveredDistrict = recovered.length
? recovered[recovered.length - 1].healthCareDistrict
: null;
const latestRecovered = recovered.length
? format(
utcToZonedTime(
new Date(recovered[recovered.length - 1].date),
timeZone
),
'd.M.yyyy'
)
: null;
const infectionsToday = getInfectionsToday(confirmed);
const [cumulativeChartScale, setCumulativeChartScale] = useState<
'linear' | 'log'
>('linear');
const [forecastChartScale, setForecaseChartScale] = useState<
'linear' | 'log'
>('linear');
// Map data to show development of infections
const {
infectionDevelopmentData,
infectionDevelopmentData30Days
} = groupedCoronaData[selectedHealthCareDistrict].timeSeries;
const maxValues =
infectionDevelopmentData30Days[infectionDevelopmentData30Days.length - 1];
const dataMaxValue = Math.max(
maxValues?.deaths ?? 0,
maxValues?.infections ?? 0,
maxValues?.infections ?? 0
);
const {
infectionsByDistrict,
infectionsByDistrictPercentage,
areas
} = getTnfectionsByDistrict(allConfirmed);
const { infectionsBySourceCountry } = getInfectionsBySourceCountry(confirmed);
const networkGraphData = getNetworkGraphData(confirmed);
const { t } = useContext(UserContext);
const humanizeHealthcareDistrict = (district: string) => {
if (district === 'all') {
return t('All healthcare districts');
} else if (district === 'unknown') {
return t('unknown');
} else {
return district;
}
};
const reversedConfirmed = confirmed
// @ts-ignore
.map((i, index) => ({
index: index + 1,
...i,
healthCareDistrict: humanizeHealthcareDistrict(i.healthCareDistrict)
}))
.reverse();
const humanizedHealthCareDistrict = humanizeHealthcareDistrict(
selectedHealthCareDistrict
);
useEffect(() => {
if (typeof window !== undefined) {
toast({
position: 'bottom',
title: 'Datan lähteenä nyt THL',
description: 'HS:n datan lähde on vaihtunut THL:ään. THL:n tiedotussyklistä johtuen tiedot päivittyvät aiempaa harvemmin. Myös vanhemmissa tapauksissa voi olla päivämääräkohtaisia eroja, johtuen muuttuneesta raportointitavasta.',
status: "info",
isClosable: true,
duration: 14000,
});
}
}, [])
return (
<>
<Head>
<title>
{t('finland corona status')} - {t('cases')} : {confirmed.length || 0}{' '}
- {t('recovered')}: {recovered.length || 0} - {t('deaths')}:{' '}
{deaths.length || 0}
</title>
<meta
name="description"
content={`Suomen koronavirus-tartuntatilanne – tartunnat: ${confirmed.length ||
0} - parantuneet: ${recovered.length ||
0} - menehtyneet: ${deaths.length || 0}`}
/>
<meta property="og:title" content={t('finland corona status')} />
<meta
property="og:description"
content={`Tartuntoja tällä hetkellä: ${confirmed.length ||
0} - parantuneet: ${recovered.length ||
0} - menehtyneet: ${deaths.length || 0}`}
/>
<meta
property="og:site_name"
content="Suomen koronavirus-tartuntatilanne"
/>
<meta property="og:locale" content="fi_FI" />
<meta property="og:type" content="website" />
<meta property="og:image" content="/images/corona-virus.png" />
<meta property="og:image:width" content="1920" />
<meta property="og:image:height" content="1928" />
<meta property="og:url" content="https://korona.kans.io" />
</Head>
<Layout>
<Flex
alignItems="center"
flexDirection="column"
flex="1"
width={'100%'}
maxWidth="1440px"
margin="auto"
>
<Header />
<Flex
flexWrap="wrap"
flexDirection="row"
justifyContent="left"
alignItems="stretch"
flex="1"
width={'100%'}
>
<Box width={['100%', '100%', 1 / 3, 1 / 3]} p={3}>
<Select
value={selectedHealthCareDistrict ?? undefined}
onChange={event => selectHealthCareDistrict(event.target.value)}
>
<option key={'all'} value={'all'}>
{t('All healthcare districts')}
</option>
{healtCareDistricts.map(healthcareDistrict => (
<option
key={healthcareDistrict.name}
value={healthcareDistrict.name}
>
{healthcareDistrict.name}
</option>
))}
))}
</Select>
</Box>
</Flex>
<Flex
flexWrap="wrap"
flexDirection="row"
justifyContent="center"
alignItems="stretch"
flex="1"
width={'100%'}
>
<Box width={['100%', '100%', 1 / 2, 1 / 2]} p={3}>
<Block
title={t('cases') + ` (${humanizedHealthCareDistrict})`}
textAlign="center"
extraInfo={`${t('New cases today')} ${infectionsToday} ${t(
'person'
)}`}
footer={`${t(
'latest case'
)} ${latestInfection} (${humanizeHealthcareDistrict(
latestInfectionDistrict
)})`}
>
<StatBlock
count={confirmed.length}
helpText={`${t('New cases today')}: ${infectionsToday} ${t(
'person'
)}`}
/>
</Block>
</Box>
<Box width={['100%', '100%', 1 / 2, 1 / 2]} p={3}>
<Block
title={t('deaths') + ` (${humanizedHealthCareDistrict})`}
footer={
latestDeath
? `${t(
'last death'
)} ${latestDeath} (${humanizeHealthcareDistrict(
latestDeathDistrict!
)})`
: t('no death')
}
>
<StatBlock count={deaths.length || 0} />
</Block>
</Box>
{/* <Box width={['100%', '100%', 1 / 3, 1 / 3]} p={3}>
<Block
title={t('recovered') + ` (${humanizedHealthCareDistrict})`}
footer={
`${latestRecovered
? `${t(
'latest recovery'
)} ${latestRecovered} (${humanizeHealthcareDistrict(latestRecoveredDistrict!)}).`
: ' '} ${t('recoveredNotice')}`}
>
<StatBlock count={recovered.length || 0} />
</Block>
</Box> */}
<Box width={['100%']} p={3}>
<Block
title={
t('accumulated change') + ` (${humanizedHealthCareDistrict})`
}
footer={t('cases recovered and death in past 30 days')}
>
<ButtonGroup
spacing={0}
alignSelf="center"
display="flex"
justifyContent="center"
marginTop="-15px"
>
<Button
size="xs"
fontFamily="Space Grotesk Regular"
px={3}
letterSpacing="1px"
borderRadius="4px 0px 0px 4px"
borderWidth="0px"
isActive={cumulativeChartScale === 'linear'}
onClick={() => setCumulativeChartScale('linear')}
>
{t('linear')}
</Button>
<Button
size="xs"
fontFamily="Space Grotesk Regular"
px={3}
letterSpacing="1px"
borderRadius="0px 4px 4px 0px"
borderWidth="0px"
isActive={cumulativeChartScale === 'log'}
onClick={() => setCumulativeChartScale('log')}
>
{t('logarithmic')}
</Button>
</ButtonGroup>
<ResponsiveContainer width={'100%'} height={380}>
<ComposedChart
data={
cumulativeChartScale === 'log'
? infectionDevelopmentData30Days.map(zerosToNulls)
: infectionDevelopmentData30Days
}
margin={{ top: 20, right: 30, left: 0, bottom: 30 }}
>
<defs>
<linearGradient
id="colorInfection"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={colors[8]}
stopOpacity={0.6}
/>
<stop
offset="95%"
stopColor={colors[8]}
stopOpacity={0}
/>
</linearGradient>
<linearGradient
id="colorRecovered"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={colors[7]}
stopOpacity={0.6}
/>
<stop
offset="95%"
stopColor={colors[7]}
stopOpacity={0}
/>
</linearGradient>
<linearGradient
id="colorDeaths"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={colors[0]}
stopOpacity={0.6}
/>
<stop
offset="95%"
stopColor={colors[0]}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis
tickFormatter={d => format(new Date(d), 'd.M.')}
tick={<CustomizedAxisTick isDate />}
dataKey="date"
domain={['dataMin', 'dataMax']}
type="number"
scale="time"
/>
<YAxis
scale={cumulativeChartScale}
dataKey="infections"
domain={[
cumulativeChartScale === 'log' ? 1 : 0,
dataMaxValue + 10
]}
unit={' ' + t('person')}
tick={{ fontSize: 12 }}
name={t('cases')}
/>
<CartesianGrid opacity={0.2} />
<Tooltip
labelFormatter={v => format(new Date(v), 'dd.MM.yyyy')}
/>
<Bar
isAnimationActive={false}
fill={colors[1]}
opacity={0.4}
dataKey="infectionsDaily"
name={t('cases of the day')}
unit={' ' + t('person')}
/>
<Area
isAnimationActive={false}
type="monotone"
unit={' ' + t('person')}
name={t('total cases')}
dataKey="infections"
stroke={colors[8]}
fillOpacity={1}
fill="url(#colorInfection)"
/>
{/* <Area
isAnimationActive={false}
type="monotone"
unit={' ' + t('person')}
name={t('total recovered')}
dataKey="recovered"
stroke={colors[7]}
fillOpacity={1}
fill="url(#colorRecovered)"
/> */}
<Area
isAnimationActive={false}
type="monotone"
unit={' ' + t('person')}
name={t('total deaths')}
dataKey="deaths"
stroke={colors[0]}
fillOpacity={1}
fill="url(#colorDeaths)"
/>
<Legend wrapperStyle={{ bottom: '10px' }} />
</ComposedChart>
</ResponsiveContainer>
</Block>
</Box>
{/*
<Box width={['100%']} p={3}>
<Block title="Tartuntojen kumulatiivinen ennustemalli" footer={`Tartuntojen kehityksen ennustemalli 60 päivää. Laskee ennustetun eksponentiaalisen kasvun käyttämällä aiemmin luotuja tietoja. Käytetty <a style="color: #319795;" href="https://github.com/mljs/regression-exponential" target="_blank">exponential-regression</a> kirjastoa.`}>
<ButtonGroup spacing={0} alignSelf="center" display="flex" justifyContent="center" marginTop="-15px">
<Button size="xs" fontFamily="Space Grotesk Regular" px={3} letterSpacing="1px" borderRadius="4px 0px 0px 4px" borderWidth="0px" isActive={forecastChartScale === 'linear'} onClick={() => setForecaseChartScale('linear')}>
Lineaarinen
</Button>
<Button size="xs" fontFamily="Space Grotesk Regular" px={3} letterSpacing="1px" borderRadius="0px 4px 4px 0px" borderWidth="0px" isActive={forecastChartScale === 'log'} onClick={() => setForecaseChartScale('log')}>
Logaritminen
</Button>
</ButtonGroup>
<ResponsiveContainer width={'100%'} height={350}>
<AreaChart
data={prediction60Days}
margin={{ top: 20, right: 30, left: 0, bottom: 20 }}
>
<defs>
<linearGradient id="colorInfection" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={colors[8]} stopOpacity={0.6} />
<stop offset="95%" stopColor={colors[8]} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis tickFormatter={d => format(new Date(d), 'd.M.')} tick={<CustomizedAxisTick isDate />} dataKey="date" domain={['dataMin', 'dataMax']} type="number" scale="time" />
<YAxis scale={forecastChartScale} dataKey="infections" domain={['auto', 'auto']} unit={' ' + t('person') } tick={{ fontSize: 12 }} name="Tartunnat" />
<CartesianGrid opacity={0.2} />
<ReferenceLine
x={today}
stroke="rgba(0,0,0,.5)"
// @ts-ignore
label={{ position: 'top', value: 'Nyt', fill: 'rgba(0,0,0,0.5)', fontSize: 12 }}
strokeDasharray="3 3" />
<Tooltip labelFormatter={v => format(new Date(v), 'dd.MM.yyyy')} />
<Area type="monotone" name="Ennuste" unit={' ' + t('person') } dataKey="infections" stroke={colors[8]} fillOpacity={1} fill="url(#colorInfection)" />
</AreaChart>
</ResponsiveContainer>
</Block>
</Box>
*/}
<Box width={['100%', '100%', '100%', '100%', 1 / 2]} p={3}>
<Block
title={t('Cases by district')}
footer={t('Helsinki metropolitan area is shown as HUS')}
>
<ResponsiveContainer width={'100%'} height={350}>
<BarChart
data={infectionsByDistrict}
margin={{
top: 20,
right: 30,
left: 0,
bottom: 85
}}
>
<XAxis
interval={0}
dataKey="name"
tick={<CustomizedAxisTick />}
/>
<YAxis
yAxisId="left"
unit={' ' + t('person')}
dataKey="infections"
tick={{ fontSize: 12 }}
/>
<Tooltip />
<Bar
isAnimationActive={false}
dataKey="infections"
name={t('cases')}
unit={' ' + t('person')}
yAxisId="left"
>
{areas.map((area, index) => (
<Cell key={area} fill={colors[index % colors.length]} />
))}
<LabelList
dataKey="infections"
position="top"
formatter={e => e}
/>
</Bar>
</BarChart>
</ResponsiveContainer>
</Block>
</Box>
<Box width={['100%', '100%', '100%', '100%', 1 / 2]} p={3}>
<Block
title={t('infectionsPerDisrictAndSize')}
footer={t('infectionsPerDisrictAndSize')}
>
<ResponsiveContainer width={'100%'} height={350}>
<BarChart
data={infectionsByDistrictPercentage}
margin={{
top: 20,
right: 30,
left: 0,
bottom: 85
}}
>
<XAxis
interval={0}
dataKey="name"
tick={<CustomizedAxisTick />}
/>
<YAxis
unit=" %"
dataKey="perDistrict"
tick={{ fontSize: 12 }}
/>
<Tooltip />
<Bar isAnimationActive={false} dataKey="perDistrict" name="%-osuus väestöstä" unit=" %">
{areas.map((area, index) => (
<Cell key={area} fill={colors[index % colors.length]} />
))}
<LabelList
dataKey="perDistict"
position="top"
formatter={e => e}
/>
</Bar>
</BarChart>
</ResponsiveContainer>
</Block>
</Box>
<Box width={['100%', '100%', '100%', '100%', 1 / 2]} p={3}>
<Block
title={t('log') + ` (${humanizedHealthCareDistrict})`}
footer={t('logFooter')}
>
<Table
height={500}
data={reversedConfirmed}
columns={useMemo(() => infectionColumns, [])}
/>
</Block>
</Box>
<BubbleChart data={groupedCoronaData} />
{/* <Box width={['100%', '100%', '100%', '100%', 1 / 2]} p={3}>
<Block
title={
t('infectionNetwork') + ` (${humanizedHealthCareDistrict})`
}
footer={t('infectionNetworkFooter')}
>
<NetworkGraph data={networkGraphData} />
</Block>
</Box> */}
<Box width={['100%']} p={3}>
<Block
title={
t('hospitalizedData') + ` (${t('All healthcare districts')})`
}
>
<ResponsiveContainer width={'100%'} height={350}>
<BarChart
data={hospitalised.slice(Math.max(hospitalised.length - 30, 0))}
margin={{
top: 20,
right: 30,
left: 0,
bottom: 85
}}
>
<XAxis
interval={0}
dataKey="dateString"
tick={<CustomizedAxisTick />}
padding={{ left: 50, right: 50 }}
/>
<YAxis
unit={' ' + t('person')}
dataKey="totalHospitalised"
tick={{ fontSize: 12 }}
/>
<Tooltip />
<Bar
isAnimationActive={false}
stackId="a"
dataKey="inIcu"
name={t("inIcu")}
unit={' ' + t('person')}
fill="#F3858D"
/>
<Bar
isAnimationActive={false}
stackId="a"
dataKey="inWard"
name={t("inWard")}
unit={' ' + t('person')}
fill="#2FAB8E"
/>
<Bar
isAnimationActive={false}
stackId="a"
dataKey="totalHospitalised"
opacity={0}
name={t("inHospital")}
unit={' ' + t('person')}
fill="rgba(0,0,0,1)"
strokeWidth={0}
legendType="none"
/>
<Legend wrapperStyle={{ bottom: '15px' }} />
</BarChart>
</ResponsiveContainer>
</Block>
</Box>
</Flex>
<Copyright />
</Flex>
</Layout>
</>
);
}
Example #11
Source File: LiquidityChart.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
Chart: FC<{
aggregateMetrics: {
type: string
enabled: boolean
label: string
color: string
}[]
}> = ({ aggregateMetrics }) => {
const poolAddress = useSelectedFeederPoolAddress()
const data = useTotalLiquidity(poolAddress)
const dateFilter = useDateFilter()
const { metrics } = useMetricsState()
return (
<RechartsContainer>
{data.length < 2 && <NoData>No data yet</NoData>}
<ResponsiveContainer aspect={2} debounce={1} width="99%">
<AreaChart margin={{ top: 40, right: 0, bottom: 0, left: 0 }} barCategoryGap={1} data={data.length > 1 ? data : []}>
<defs>
{aggregateMetrics.map(({ type, color }) => (
<linearGradient id={type} key={type} x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={color} stopOpacity={0.5} />
<stop offset="95%" stopColor={color} stopOpacity={0} />
</linearGradient>
))}
</defs>
<XAxis
dataKey="timestamp"
axisLine={false}
xAxisId={0}
height={0}
tick={false}
tickFormatter={timestamp => (timestamp ? format(timestamp * 1000, periodFormatMapping[dateFilter.period]) : '')}
/>
<YAxis
type="number"
orientation="left"
tickFormatter={toK}
axisLine={false}
interval="preserveEnd"
yAxisId={0}
tick={false}
width={0}
/>
<Tooltip
cursor
labelFormatter={timestamp => format((timestamp as number) * 1000, 'yyyy-MM-dd HH:mm')}
formatter={toK as never}
separator=""
contentStyle={{
fontSize: '14px',
padding: '8px',
background: 'rgba(255, 255, 255, 0.8)',
textAlign: 'right',
border: 'none',
borderRadius: '4px',
color: Color.black,
}}
wrapperStyle={{
top: 0,
left: 0,
}}
/>
{metrics.map(({ color, type, label, enabled }) => (
<Area
animationDuration={100}
key={type}
type="monotone"
hide={!enabled}
dataKey={type}
name={`${label} `}
opacity={1}
dot={false}
yAxisId={0}
stroke={color}
strokeWidth={2}
fill={`url(#${type})`}
/>
))}
</AreaChart>
</ResponsiveContainer>
</RechartsContainer>
)
}
Example #12
Source File: ClaimGraph.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
ClaimGraph: FC = () => {
const rewardStreams = useRewardStreams()
const chart = useMemo<Chart>(() => {
if (!rewardStreams) return { maxY: 0, groups: [] }
// Filter for selected types
const filtered = rewardStreams.chartData
// Group into ranges
const ranges = filtered
.map(({ t }) => t)
.reduce<[number, number][]>((prev, x, idx) => {
if (idx === 0) return [[x, x]]
const last = prev[prev.length - 1]
// More than 2 months? Too much of a gap, make a new group
// Otherwise the chart is hard to read
// TODO needs more logic here... if I didn't claim for more than that
// time since I started earning, it won't show correctly
// if (x - last[1] > 5184000) {
// return [...prev, [x, x]];
// }
return [...prev.slice(0, -1), [last[0], x]]
}, [])
// Find the max Y value to use a common scale
const maxY = filtered.reduce(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(prev, { t, ...datum }) => Math.max(prev, ...(Object.values(datum) as number[])),
0,
)
return {
maxY,
groups: ranges
.map(range => {
const data = filtered.filter(datum => datum.t >= range[0] && datum.t <= range[1])
const types = Array.from(
new Set(
data.reduce<StreamType[]>(
(prev, datum) => [
...prev,
...Object.entries(datum)
.filter(([k, v]) => k !== 't' && v)
.map(([k]) => k as unknown as StreamType),
],
[],
),
),
)
return {
range,
data,
types,
}
})
.filter(group => group.data.length > 1),
}
}, [rewardStreams])
if (!rewardStreams) return null
return (
<ChartContainer key={chart.groups.length}>
{chart.groups.map(({ data, types, range }) => (
<ResponsiveContainer maxHeight={200} aspect={3} key={range[0]}>
<AreaChart data={data} margin={{ top: 20, left: 0, right: 0, bottom: 0 }}>
<defs>
{Object.keys(dataTypes).map(streamType => (
<linearGradient id={`type-${streamType}`} key={streamType} x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={rewardsColorMapping[streamType as unknown as StreamType].fill1} />
<stop offset="95%" stopColor={rewardsColorMapping[streamType as unknown as StreamType].fill2} />
</linearGradient>
))}
</defs>
<Tooltip
cursor
labelFormatter={tooltipFormatter}
formatter={toK as never}
separator=" "
contentStyle={{
fontSize: '14px',
padding: '8px',
background: 'rgba(255, 255, 255, 0.8)',
textAlign: 'right',
border: 'none',
borderRadius: '4px',
color: Color.black,
}}
wrapperStyle={{
top: 0,
left: 0,
}}
/>
<XAxis
scale="time"
interval="preserveStartEnd"
domain={[range[0], 'auto']}
type="number"
dataKey="t"
tickFormatter={xAxisFormatter}
stroke={Color.greyTransparent}
padding={{ left: 40, right: 80 }}
/>
<YAxis
type="number"
domain={[0, chart.maxY]}
orientation="left"
tickFormatter={toK}
axisLine={false}
interval={100}
// interval="preserveEnd"
tick={false}
/>
<ReferenceLine x={rewardStreams?.currentTime} stroke={Color.greyTransparent}>
<Label position="insideTopRight" value="Unclaimed" fontSize={14} dx={6} dy={-20} />
</ReferenceLine>
{rewardStreams?.nextUnlock && (
<ReferenceLine x={rewardStreams.nextUnlock} stroke={Color.greyTransparent}>
<Label position="insideTopLeft" value="Next unlock" fontSize={14} dy={-20} dx={-6} />
</ReferenceLine>
)}
{rewardStreams && (
<ReferenceLine x={rewardStreams.previewStream.start} stroke={Color.greyTransparent}>
<Label position="insideTopLeft" value="New unlock" fontSize={14} />
</ReferenceLine>
)}
{types.flatMap(streamType =>
(dataTypes[streamType].subTypes ?? [streamType]).map(subType => (
<Area
key={subType}
dataKey={subType}
name={dataTypes[subType].label}
dot={false}
strokeWidth={0}
stroke={rewardsColorMapping[subType].point}
stackId={1}
fill={`url(#type-${subType})`}
fillOpacity={1}
/>
)),
)}
</AreaChart>
</ResponsiveContainer>
))}
</ChartContainer>
)
}
Example #13
Source File: VolumeChart.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
Chart: FC = () => {
const dateFilter = useDateFilter()
const data = useVolumeMetrics()
const { metrics } = useMetricsState()
return (
<RechartsContainer>
{data && data.length ? (
<ResponsiveContainer aspect={2}>
<AreaChart margin={{ top: 0, right: 16, bottom: 16, left: 16 }} barCategoryGap={1} data={data}>
<defs>
{volumeMetrics.map(({ type, color }) => (
<linearGradient id={type} key={type} x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={color} stopOpacity={0.5} />
<stop offset="95%" stopColor={color} stopOpacity={0} />
</linearGradient>
))}
</defs>
<XAxis
dataKey="timestamp"
axisLine={false}
xAxisId={0}
tickSize={12}
padding={{ left: 16 }}
minTickGap={16}
tickLine
tickFormatter={timestamp => (timestamp ? format(timestamp * 1000, periodFormatMapping[dateFilter.period]) : '')}
/>
<YAxis
type="number"
orientation="left"
tickFormatter={toK}
axisLine={false}
tickLine
tickSize={12}
padding={{ bottom: 16 }}
interval="preserveEnd"
minTickGap={8}
yAxisId={0}
/>
<YAxis
type="number"
hide={!metrics.find(m => m.type === TransactionType.MassetPaidFee && m.enabled)}
orientation="right"
tickFormatter={toK}
axisLine={false}
tickLine
tickSize={12}
name="Fees"
padding={{ bottom: 16 }}
interval="preserveEnd"
minTickGap={8}
yAxisId={1}
/>
<Tooltip
cursor
labelFormatter={timestamp => format((timestamp as number) * 1000, 'yyyy-MM-dd HH:mm')}
formatter={toK as never}
separator=""
contentStyle={{
fontSize: '14px',
padding: '8px',
background: 'rgba(255, 255, 255, 0.8)',
textAlign: 'right',
border: 'none',
borderRadius: '4px',
color: Color.black,
}}
wrapperStyle={{
top: 0,
left: 0,
}}
/>
{metrics.map(({ color, type, label, enabled }) => (
<Area
isAnimationActive={false}
key={type}
type="monotone"
hide={!enabled}
dataKey={type}
name={`${label} `}
opacity={1}
dot={false}
yAxisId={type === TransactionType.MassetPaidFee ? 1 : 0}
stroke={color}
strokeWidth={2}
fill={`url(#${type})`}
/>
))}
</AreaChart>
</ResponsiveContainer>
) : (
<ThemedSkeleton height={270} />
)}
</RechartsContainer>
)
}
Example #14
Source File: DailyApys.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
DailyApysChart: FC<{
shimmerHeight?: number
tick?: boolean
marginTop?: number
aspect?: number
className?: string
color?: string
strokeWidth?: number
hoverEnabled?: boolean
}> = ({ shimmerHeight = 270, tick, className, marginTop, color, aspect = 2, strokeWidth = 2, hoverEnabled = true }) => {
const dateFilter = useDateFilter()
const { DailyApy, UtilisationRate } = useMetrics<MetricTypes>()
const blockTimes = useBlockTimesForDates(dateFilter.dates)
const savingsContractState = useSelectedSavingsContractState()
const dailyApys = useDailyApysForBlockTimes(savingsContractState?.address, blockTimes)
if (dailyApys.some(value => value.dailyAPY > 1000) || dailyApys.every(value => value.dailyAPY === 0)) {
return <NoData className={className}>No data available yet</NoData>
}
return (
<RechartsContainer className={className}>
{dailyApys && dailyApys.length ? (
<ResponsiveContainer aspect={aspect} debounce={1} width="99%">
<AreaChart
margin={tick ? { top: marginTop, right: 16, bottom: 16, left: 16 } : { top: marginTop, right: 0, bottom: 0, left: 0 }}
barCategoryGap={1}
data={dailyApys}
>
{hoverEnabled && (
<defs>
<linearGradient id="utilisationRate" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={Color.blackTransparent} stopOpacity={0.5} />
<stop offset="95%" stopColor={Color.blackTransparent} stopOpacity={0} />
</linearGradient>
<linearGradient id="dailyAPY" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={color ?? Color.gold} stopOpacity={0.5} />
<stop offset="95%" stopColor={color ?? Color.gold} stopOpacity={0} />
</linearGradient>
</defs>
)}
<XAxis
dataKey="timestamp"
axisLine={false}
tick={tick}
xAxisId={0}
tickSize={12}
padding={tick ? { left: 16 } : { left: 0 }}
minTickGap={16}
tickLine
height={!tick ? 0 : undefined}
tickFormatter={(timestamp: number) => (timestamp ? format(timestamp * 1000, periodFormatMapping[dateFilter.period]) : '')}
/>
<YAxis
type="number"
orientation="left"
tickFormatter={formatApy}
axisLine={false}
tick={tick}
tickLine
tickSize={12}
padding={tick ? { bottom: 16 } : { bottom: 0 }}
interval="preserveEnd"
minTickGap={8}
yAxisId={0}
width={!tick ? 0 : undefined}
/>
{hoverEnabled && (
<Tooltip
cursor
labelFormatter={timestamp => format((timestamp as number) * 1000, 'yyyy-MM-dd HH:mm')}
formatter={formatApy as never}
separator=""
contentStyle={{
fontSize: '14px',
padding: '8px',
background: 'rgba(255, 255, 255, 0.8)',
textAlign: 'right',
border: 'none',
borderRadius: '4px',
color: Color.black,
}}
wrapperStyle={{
top: 0,
left: 0,
}}
/>
)}
<Area
hide={!UtilisationRate.enabled}
strokeWidth={strokeWidth}
type="monotone"
dataKey="utilisationRate"
name="SAVE Utilisation "
opacity={1}
fill="url(#utilisationRate)"
yAxisId={0}
stroke={Color.blackTransparent}
/>
<Area
hide={!DailyApy.enabled}
strokeWidth={strokeWidth}
type="monotone"
dataKey="dailyAPY"
name="Daily APY "
opacity={1}
yAxisId={0}
fill="url(#dailyAPY)"
stroke={color ?? DailyApy.color}
/>
</AreaChart>
</ResponsiveContainer>
) : (
<ThemedSkeleton height={shimmerHeight} />
)}
</RechartsContainer>
)
}
Example #15
Source File: AggregateChart.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
Chart: FC<{
aggregateMetrics: {
type: string
enabled: boolean
label: string
color: string
}[]
}> = ({ aggregateMetrics }) => {
const data = useAggregateMetrics()
const dateFilter = useDateFilter()
const { metrics } = useMetricsState()
return (
<RechartsContainer>
{data && data.length ? (
<ResponsiveContainer aspect={2}>
<AreaChart margin={{ top: 0, right: 16, bottom: 16, left: 16 }} barCategoryGap={1} data={data}>
<defs>
{aggregateMetrics.map(({ type, color }) => (
<linearGradient id={type} key={type} x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={color} stopOpacity={0.5} />
<stop offset="95%" stopColor={color} stopOpacity={0} />
</linearGradient>
))}
</defs>
<XAxis
dataKey="timestamp"
axisLine={false}
xAxisId={0}
tickSize={12}
padding={{ left: 16 }}
minTickGap={16}
tickLine
tickFormatter={timestamp => (timestamp ? format(timestamp * 1000, periodFormatMapping[dateFilter.period]) : '')}
/>
<YAxis
type="number"
orientation="left"
tickFormatter={toK}
axisLine={false}
tickLine
tickSize={12}
padding={{ bottom: 16 }}
interval="preserveEnd"
minTickGap={8}
yAxisId={0}
/>
<Tooltip
cursor
labelFormatter={timestamp => format((timestamp as number) * 1000, 'yyyy-MM-dd HH:mm')}
formatter={toK as never}
separator=""
contentStyle={{
fontSize: '14px',
padding: '8px',
background: 'rgba(255, 255, 255, 0.8)',
textAlign: 'right',
border: 'none',
borderRadius: '4px',
color: Color.black,
}}
wrapperStyle={{
top: 0,
left: 0,
}}
/>
{metrics.map(({ color, type, label, enabled }) => (
<Area
isAnimationActive={false}
key={type}
type="monotone"
hide={!enabled}
dataKey={type}
name={`${label} `}
opacity={1}
dot={false}
yAxisId={0}
stroke={color}
strokeWidth={2}
fill={`url(#${type})`}
/>
))}
</AreaChart>
</ResponsiveContainer>
) : (
<ThemedSkeleton height={270} />
)}
</RechartsContainer>
)
}
Example #16
Source File: STResults.tsx From console with GNU Affero General Public License v3.0 | 4 votes |
STResults = ({ classes, results, start }: ISTResults) => {
const [jsonView, setJsonView] = useState<boolean>(false);
const finalRes = results[results.length - 1] || [];
const getServers: STServer[] = get(finalRes, "GETStats.servers", []) || [];
const putServers: STServer[] = get(finalRes, "PUTStats.servers", []) || [];
const getThroughput = get(finalRes, "GETStats.throughputPerSec", 0);
const getObjects = get(finalRes, "GETStats.objectsPerSec", 0);
const putThroughput = get(finalRes, "PUTStats.throughputPerSec", 0);
const putObjects = get(finalRes, "PUTStats.objectsPerSec", 0);
let statJoin: IndvServerMetric[] = [];
getServers.forEach((item) => {
const hostName = item.endpoint;
const putMetric = putServers.find((item) => item.endpoint === hostName);
let itemJoin: IndvServerMetric = {
getUnit: "-",
getValue: "N/A",
host: item.endpoint,
putUnit: "-",
putValue: "N/A",
};
if (item.err && item.err !== "") {
itemJoin.getError = item.err;
itemJoin.getUnit = "-";
itemJoin.getValue = "N/A";
} else {
const niceGet = calculateBytes(item.throughputPerSec.toString());
itemJoin.getUnit = niceGet.unit;
itemJoin.getValue = niceGet.total.toString();
}
if (putMetric) {
if (putMetric.err && putMetric.err !== "") {
itemJoin.putError = putMetric.err;
itemJoin.putUnit = "-";
itemJoin.putValue = "N/A";
} else {
const nicePut = calculateBytes(putMetric.throughputPerSec.toString());
itemJoin.putUnit = nicePut.unit;
itemJoin.putValue = nicePut.total.toString();
}
}
statJoin.push(itemJoin);
});
const downloadResults = () => {
const date = new Date();
let element = document.createElement("a");
element.setAttribute(
"href",
"data:text/plain;charset=utf-8," + JSON.stringify(finalRes)
);
element.setAttribute(
"download",
`speedtest_results-${date.toISOString()}.log`
);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};
const toggleJSONView = () => {
setJsonView(!jsonView);
};
const finalResJSON = finalRes ? JSON.stringify(finalRes, null, 4) : "";
const clnMetrics = cleanMetrics(results);
return (
<Fragment>
<Grid container className={classes.objectGeneral}>
<Grid item xs={12} md={6} lg={6}>
<Grid container className={classes.objectGeneral}>
<Grid item xs={12} md={6} lg={6}>
<SpeedTestUnit
icon={
<div className={classes.download}>
<DownloadStatIcon />
</div>
}
title={"GET"}
throughput={getThroughput}
objects={getObjects}
/>
</Grid>
<Grid item xs={12} md={6} lg={6}>
<SpeedTestUnit
icon={
<div className={classes.upload}>
<UploadStatIcon />
</div>
}
title={"PUT"}
throughput={putThroughput}
objects={putObjects}
/>
</Grid>
</Grid>
</Grid>
<Grid item xs={12} md={6} lg={6}>
<ResponsiveContainer width="99%">
<AreaChart data={clnMetrics}>
<defs>
<linearGradient id="colorPut" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#2781B0" stopOpacity={0.9} />
<stop offset="95%" stopColor="#fff" stopOpacity={0} />
</linearGradient>
<linearGradient id="colorGet" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#4CCB92" stopOpacity={0.9} />
<stop offset="95%" stopColor="#fff" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid
strokeDasharray={"0 0"}
strokeWidth={1}
strokeOpacity={0.5}
stroke={"#F1F1F1"}
vertical={false}
/>
<Area
type="monotone"
dataKey={"get"}
stroke={"#4CCB92"}
fill={"url(#colorGet)"}
fillOpacity={0.3}
strokeWidth={2}
dot={false}
/>
<Area
type="monotone"
dataKey={"put"}
stroke={"#2781B0"}
fill={"url(#colorPut)"}
fillOpacity={0.3}
strokeWidth={2}
dot={false}
/>
</AreaChart>
</ResponsiveContainer>
</Grid>
</Grid>
<br />
{clnMetrics.length > 1 && (
<Fragment>
<Grid container>
<Grid item xs={12} md={6} className={classes.descriptorLabel}>
{start ? (
<Fragment>Preliminar Results:</Fragment>
) : (
<Fragment>
{jsonView ? "JSON Results:" : "Detailed Results:"}
</Fragment>
)}
</Grid>
<Grid item xs={12} md={6} className={classes.actionButtons}>
{!start && (
<Fragment>
<BoxIconButton
aria-label="Download"
onClick={downloadResults}
size="large"
>
<DownloadIcon />
</BoxIconButton>
<BoxIconButton
aria-label="Download"
onClick={toggleJSONView}
size="large"
>
<JSONIcon />
</BoxIconButton>
</Fragment>
)}
</Grid>
</Grid>
<Grid container className={classes.resultsContainer}>
{jsonView ? (
<Fragment>
<CodeMirrorWrapper
value={finalResJSON}
readOnly
onBeforeChange={() => {}}
/>
</Fragment>
) : (
<Fragment>
<Grid
item
xs={12}
sm={12}
md={1}
lg={1}
className={classes.resultsIcon}
alignItems={"flex-end"}
>
<ComputerLineIcon width={45} />
</Grid>
<Grid
item
xs={12}
sm={6}
md={3}
lg={2}
className={classes.detailedItem}
>
Nodes: <strong>{finalRes.servers}</strong>
</Grid>
<Grid
item
xs={12}
sm={6}
md={3}
lg={2}
className={classes.detailedItem}
>
Drives: <strong>{finalRes.disks}</strong>
</Grid>
<Grid
item
xs={12}
sm={6}
md={3}
lg={2}
className={classes.detailedItem}
>
Concurrent: <strong>{finalRes.concurrent}</strong>
</Grid>
<Grid
item
xs={12}
sm={12}
md={12}
lg={5}
className={classes.detailedVersion}
>
<span className={classes.versionIcon}>
<VersionIcon />
</span>{" "}
MinIO VERSION <strong>{finalRes.version}</strong>
</Grid>
<Grid item xs={12} className={classes.tableOverflow}>
<table
className={classes.serversTable}
cellSpacing={0}
cellPadding={0}
>
<thead>
<tr>
<th colSpan={2}>Servers</th>
<th>GET</th>
<th>PUT</th>
</tr>
</thead>
<tbody>
{statJoin.map((stats, index) => (
<tr key={`storage-${index.toString()}`}>
<td className={classes.serverIcon}>
<StorageIcon />
</td>
<td className={classes.serverHost}>{stats.host}</td>
{stats.getError && stats.getError !== "" ? (
<td>{stats.getError}</td>
) : (
<Fragment>
<td className={classes.serverValue}>
{prettyNumber(parseFloat(stats.getValue))}
{stats.getUnit}/s.
</td>
</Fragment>
)}
{stats.putError && stats.putError !== "" ? (
<td>{stats.putError}</td>
) : (
<Fragment>
<td className={classes.serverValue}>
{prettyNumber(parseFloat(stats.putValue))}
{stats.putUnit}/s.
</td>
</Fragment>
)}
</tr>
))}
</tbody>
</table>
</Grid>
</Fragment>
)}
</Grid>
</Fragment>
)}
</Fragment>
);
}
Example #17
Source File: ArtistListeningRepartition.tsx From your_spotify with GNU General Public License v3.0 | 4 votes |
export default function ArtistListeningRepartition({ className }: ArtistListeningRepartitionProps) {
const { interval } = useSelector(selectRawIntervalDetail);
const results = useAPI(api.mostListenedArtist, interval.start, interval.end, interval.timesplit);
const resultsWithCount = useMemo(
() =>
results?.map((res) => ({
_id: res._id,
artists: res.artists.reduce<Record<string, number>>((acc, curr, idx) => {
acc[curr.id] = res.counts[idx];
return acc;
}, {}),
})),
[results],
);
const allArtists = useMemo(() => {
const all: Record<string, Artist> = {};
results?.forEach((res) => {
res.artists.forEach((art) => {
if (!(art.id in all)) {
all[art.id] = art;
}
});
});
return all;
}, [results]);
const data = useMemo(() => {
if (!resultsWithCount) {
return [];
}
const d = resultsWithCount.map((curr, idx) => {
const obj: { x: number; _id: DateId } & any = {
x: idx,
_id: curr._id as DateId,
};
const total = Object.values(curr.artists).reduce((acc, count) => acc + count, 0);
Object.values(allArtists).forEach((art) => {
obj[art.id] = (curr.artists[art.id] ?? 0) / total;
});
return obj;
}, []);
return buildXYDataObjSpread(d, Object.keys(allArtists), interval.start, interval.end, false);
}, [allArtists, interval, resultsWithCount]);
const tooltipLabelFormatter = useRawTooltipLabelFormatter(formatXAxisDateTooltip, false);
const tooltipValueFormatter = useCallback(
(value: number, label: string) => {
if (value === 0) {
return [<span />];
}
return [`${allArtists[label].name}: ${Math.floor(value * 1000) / 10}%`];
},
[allArtists],
);
const tooltipSorter = useCallback((a: any) => {
return -a.payload[a.dataKey];
}, []);
const formatX = useFormatXAxis(data);
if (!results) {
return <LoadingImplementedChart title="Artist listening distribution" className={className} />;
}
return (
<ChartCard title="Artist listening distribution" className={className}>
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={data}>
<XAxis dataKey="x" tickFormatter={formatX} style={{ fontWeight: 'bold' }} />
<YAxis domain={[0, 1]} tickFormatter={formatYAxis} />
<Tooltip
formatter={tooltipValueFormatter}
labelFormatter={tooltipLabelFormatter}
wrapperStyle={{ zIndex: 1000 }}
contentStyle={{ background: 'var(--background)' }}
itemSorter={tooltipSorter}
/>
{Object.values(allArtists).map((art, idx) => (
<Area
type="monotone"
dataKey={art.id}
key={art.id}
stackId={-1}
stroke={getColor(idx)}
fill={getColor(idx)}
/>
))}
</AreaChart>
</ResponsiveContainer>
</ChartCard>
);
}
Example #18
Source File: LiveMetricsItem.tsx From kubenav with MIT License | 4 votes |
LiveMetricsItem: React.FunctionComponent<ILiveMetricsItemProps> = ({
show,
hide,
item,
}: ILiveMetricsItemProps) => {
const context = useContext<IContext>(AppContext);
const [metrics, setMetrics] = useState<ILiveMetric[]>([]);
const fetchMetrics = async () => {
try {
if (show) {
const data: IPodMetrics = await kubernetesRequest(
'GET',
`/apis/metrics.k8s.io/v1beta1/namespaces/${
item.metadata && item.metadata.namespace ? item.metadata.namespace : ''
}/pods/${item.metadata && item.metadata.name ? item.metadata.name : ''}`,
'',
context.settings,
await context.kubernetesAuthWrapper(''),
);
const containerMetrics: ILiveMetric[] = [];
if (data.containers) {
for (const container of data.containers) {
containerMetrics.push({
timestamp: 0,
cpu: parseInt(formatResourceValue('cpu', container?.usage?.cpu || '0')),
memory: parseInt(formatResourceValue('memory', container?.usage?.memory || '0')),
});
}
}
setMetrics((prevMetrics) => {
if (prevMetrics.length > 14) {
return [
...prevMetrics.slice(-14),
{
timestamp: Math.floor(new Date().getTime() / 1000),
cpu: containerMetrics.map((m) => m.cpu).reduce(reducer),
memory: containerMetrics.map((m) => m.memory).reduce(reducer),
},
];
} else {
return [
...prevMetrics,
{
timestamp: Math.floor(new Date().getTime() / 1000),
cpu: containerMetrics.map((m) => m.cpu).reduce(reducer),
memory: containerMetrics.map((m) => m.memory).reduce(reducer),
},
];
}
});
}
} catch (err) {
throw err;
}
};
const formatTime = (time: number): string => {
const d = new Date(time * 1000);
return `${('0' + d.getHours()).slice(-2)}:${('0' + d.getMinutes()).slice(-2)}:${('0' + d.getSeconds()).slice(-2)}`;
};
useEffect(() => {
const timer = setTimeout(() => {
fetchMetrics();
}, 3000);
return () => clearTimeout(timer);
});
return (
<React.Fragment>
<IonModal isOpen={show} onDidDismiss={hide}>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonButton onClick={hide}>
<IonIcon slot="icon-only" icon={close} />
</IonButton>
</IonButtons>
<IonTitle>{item.metadata ? item.metadata.name : ''}</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonCard>
<IonCardHeader>
<IonCardTitle>CPU Usage</IonCardTitle>
</IonCardHeader>
<IonCardContent>
<div style={{ height: '300px', width: '100%' }}>
{metrics.length > 0 && (
<ResponsiveContainer>
<AreaChart data={metrics}>
<XAxis
dataKey="timestamp"
scale="time"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={formatTime}
/>
<YAxis dataKey="cpu" unit="m" />
<Area
dataKey="cpu"
// NOTE: https://github.com/recharts/recharts/issues/2487
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
data={metrics}
name="CPU Usage"
stroke="#326ce5"
fill="#326ce5"
fillOpacity={0.2}
isAnimationActive={false}
/>
</AreaChart>
</ResponsiveContainer>
)}
</div>
</IonCardContent>
</IonCard>
<IonCard>
<IonCardHeader>
<IonCardTitle>Memory Usage</IonCardTitle>
</IonCardHeader>
<IonCardContent>
<div style={{ height: '300px', width: '100%' }}>
{metrics.length > 0 && (
<ResponsiveContainer>
<AreaChart data={metrics}>
<XAxis
dataKey="timestamp"
scale="time"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={formatTime}
/>
<YAxis dataKey="memory" unit="Mi" />
<Area
dataKey="memory"
// NOTE: https://github.com/recharts/recharts/issues/2487
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
data={metrics}
name="Memory Usage"
stroke="#326ce5"
fill="#326ce5"
fillOpacity={0.2}
isAnimationActive={false}
/>
</AreaChart>
</ResponsiveContainer>
)}
</div>
</IonCardContent>
</IonCard>
</IonContent>
</IonModal>
</React.Fragment>
);
}
Example #19
Source File: ChartDetailsArea.tsx From kubenav with MIT License | 4 votes |
ChartDetailsArea: React.FunctionComponent<IChartDetailsAreaProps> = ({
unit,
timeDiff,
results,
}: IChartDetailsAreaProps) => {
const context = useContext<IContext>(AppContext);
const [selected, setSelected] = useState<string>('');
// formatTime is use to formate the shown values for the x axis. If the user selected a time span lower then 24h we
// are showing the "hh:mm" for timespans larger then 24h we are showing "MM/DD hh:mm".
const formatTime = (time: number): string => {
const d = new Date(time * 1000);
if (timeDiff >= 86400) {
return `${('0' + (d.getMonth() + 1)).slice(-2)}/${('0' + d.getDate()).slice(-2)} ${('0' + d.getHours()).slice(
-2,
)}:${('0' + d.getMinutes()).slice(-2)}`;
} else {
return `${('0' + d.getHours()).slice(-2)}:${('0' + d.getMinutes()).slice(-2)}`;
}
};
// The series variables contains the transformed Prometheus results. This is necessary, because the returned formate
// from Prometheus can not directly used with Recharts.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const series: any = [];
for (let i = 0; i < results.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = [];
for (let j = 0; j < results[i].values.length; j++) {
data.push({
time: results[i].values[j][0],
value: parseFloat(results[i].values[j][1]),
});
}
series.push({ name: results[i].label, data: data });
}
return (
<IonRow style={{ height: '300px', width: '100%' }}>
<IonCol style={{ padding: '0px' }}>
<ResponsiveContainer>
<AreaChart data={series[0].data}>
<XAxis
dataKey="time"
scale="time"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={formatTime}
/>
<YAxis dataKey="value" unit={unit} />
<Legend
height={40}
wrapperStyle={{ overflowY: 'auto' }}
onClick={(e) => (selected === e.payload.name ? setSelected('') : setSelected(e.payload.name))}
/>
{!isPlatform('hybrid') ? (
<Tooltip
cursor={{ stroke: '#949494', strokeWidth: 2 }}
contentStyle={
isDarkMode(context.settings.theme)
? isPlatform('ios')
? { backgroundColor: '1c1c1c', borderColor: '#949494' }
: { backgroundColor: '#1A1B1E', borderColor: '#949494' }
: { backgroundColor: '#ffffff', borderColor: '#949494' }
}
formatter={(value) => {
return `${value.toFixed(5)} ${unit ? unit : ''}`;
}}
labelFormatter={formatTime}
/>
) : null}
{selected === ''
? series.map((serie, index) => (
<Area
key={index}
dataKey="value"
// NOTE: https://github.com/recharts/recharts/issues/2487
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
data={serie.data}
name={serie.name}
stroke={getColor(index, isDarkMode(context.settings.theme))}
fill={getColor(index, isDarkMode(context.settings.theme))}
fillOpacity={0.2}
/>
))
: series
.filter((serie) => serie.name === selected)
.map((serie, index) => (
<Area
key={index}
dataKey="value"
// NOTE: https://github.com/recharts/recharts/issues/2487
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
data={serie.data}
name={serie.name}
stroke={getColor(
series.findIndex((s) => s.name === selected),
isDarkMode(context.settings.theme),
)}
fill={getColor(
series.findIndex((s) => s.name === selected),
isDarkMode(context.settings.theme),
)}
fillOpacity={0.2}
/>
))}
</AreaChart>
</ResponsiveContainer>
</IonCol>
</IonRow>
);
}
Example #20
Source File: CostOverviewChart.tsx From backstage with Apache License 2.0 | 4 votes |
CostOverviewChart = ({
dailyCostData,
metric,
metricData,
responsive = true,
}: CostOverviewChartProps) => {
const theme = useTheme<CostInsightsTheme>();
const styles = useStyles(theme);
const data = {
dailyCost: {
dataKey: 'dailyCost',
name: `Daily Cost`,
format: 'currency',
data: dailyCostData,
},
metric: {
dataKey: metric?.kind ?? 'Unknown',
name: metric?.name ?? 'Unknown',
format: metricData?.format ?? 'number',
data: metricData,
},
};
const metricsByDate = data.metric.data
? data.metric.data.aggregation.reduce(groupByDate, {})
: {};
const chartData: ChartData[] = data.dailyCost.data.aggregation
.slice()
.sort(aggregationSort)
.map(entry => ({
date: Date.parse(entry.date),
trend: trendFrom(data.dailyCost.data.trendline!, Date.parse(entry.date)),
dailyCost: entry.amount,
...(metric && data.metric.data
? { [data.metric.dataKey]: metricsByDate[`${entry.date}`] }
: {}),
}));
const tooltipRenderer: ContentRenderer<TooltipProps> = ({
label,
payload = [],
}) => {
if (isInvalid({ label, payload })) return null;
const dataKeys = [data.dailyCost.dataKey, data.metric.dataKey];
const date =
typeof label === 'number'
? DateTime.fromMillis(label)
: DateTime.fromISO(label!);
const title = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);
const items = payload
.filter(p => dataKeys.includes(p.dataKey as string))
.map(p => ({
label:
p.dataKey === data.dailyCost.dataKey
? data.dailyCost.name
: data.metric.name,
value:
p.dataKey === data.dailyCost.dataKey
? formatGraphValue(p.value as number, data.dailyCost.format)
: formatGraphValue(p.value as number, data.metric.format),
fill:
p.dataKey === data.dailyCost.dataKey
? theme.palette.blue
: theme.palette.magenta,
}));
return (
<Tooltip title={title}>
{items.map((item, index) => (
<TooltipItem key={`${item.label}-${index}`} item={item} />
))}
</Tooltip>
);
};
return (
<Box display="flex" flexDirection="column">
<CostOverviewLegend
dailyCostData={dailyCostData}
metric={metric}
metricData={metricData}
/>
<ResponsiveContainer
width={responsive ? '100%' : styles.container.width}
height={styles.container.height}
className="cost-overview-chart"
>
<ComposedChart margin={styles.chart.margin} data={chartData}>
<CartesianGrid stroke={styles.cartesianGrid.stroke} />
<XAxis
dataKey="date"
domain={['dataMin', 'dataMax']}
tickFormatter={overviewGraphTickFormatter}
tickCount={6}
type="number"
stroke={styles.axis.fill}
/>
<YAxis
domain={[() => 0, 'dataMax']}
tick={{ fill: styles.axis.fill }}
tickFormatter={formatGraphValue}
width={styles.yAxis.width}
yAxisId={data.dailyCost.dataKey}
/>
{metric && (
<YAxis
hide
domain={[() => 0, toDataMax(data.metric.dataKey, chartData)]}
width={styles.yAxis.width}
yAxisId={data.metric.dataKey}
/>
)}
<Area
dataKey={data.dailyCost.dataKey}
isAnimationActive={false}
fill={theme.palette.blue}
fillOpacity={0.4}
stroke="none"
yAxisId={data.dailyCost.dataKey}
/>
<Line
activeDot={false}
dataKey="trend"
dot={false}
isAnimationActive={false}
label={false}
strokeWidth={2}
stroke={theme.palette.blue}
yAxisId={data.dailyCost.dataKey}
/>
{metric && (
<Line
dataKey={data.metric.dataKey}
dot={false}
isAnimationActive={false}
label={false}
strokeWidth={2}
stroke={theme.palette.magenta}
yAxisId={data.metric.dataKey}
/>
)}
<RechartsTooltip content={tooltipRenderer} animationDuration={100} />
</ComposedChart>
</ResponsiveContainer>
</Box>
);
}
Example #21
Source File: CostOverviewBreakdownChart.tsx From backstage with Apache License 2.0 | 4 votes |
CostOverviewBreakdownChart = ({
costBreakdown,
}: CostOverviewBreakdownChartProps) => {
const theme = useTheme<CostInsightsTheme>();
const classes = useStyles(theme);
const lastCompleteBillingDate = useLastCompleteBillingDate();
const { duration } = useFilters(mapFiltersToProps);
const [isExpanded, setExpanded] = useState(false);
if (!costBreakdown) {
return null;
}
const flattenedAggregation = costBreakdown
.map(cost => cost.aggregation)
.flat();
const totalCost = aggregationSum(flattenedAggregation);
const previousPeriodTotal = getPreviousPeriodTotalCost(
flattenedAggregation,
duration,
lastCompleteBillingDate,
);
const currentPeriodTotal = totalCost - previousPeriodTotal;
const canExpand = costBreakdown.length >= 8;
const otherCategoryIds: string[] = [];
const breakdownsByDate = costBreakdown.reduce(
(breakdownByDate, breakdown) => {
const breakdownTotal = aggregationSum(breakdown.aggregation);
// Group breakdown items with less than 10% of the total cost into "Other" category if needed
const isOtherCategory =
canExpand && breakdownTotal < totalCost * LOW_COST_THRESHOLD;
const updatedBreakdownByDate = { ...breakdownByDate };
if (isOtherCategory) {
otherCategoryIds.push(breakdown.id);
}
breakdown.aggregation.forEach(curAggregation => {
const costsForDate = updatedBreakdownByDate[curAggregation.date] || {};
updatedBreakdownByDate[curAggregation.date] = {
...costsForDate,
[breakdown.id]:
(costsForDate[breakdown.id] || 0) + curAggregation.amount,
};
});
return updatedBreakdownByDate;
},
{} as Record<string, Record<string, number>>,
);
const chartData: Record<string, number>[] = Object.keys(breakdownsByDate).map(
date => {
const costsForDate = Object.keys(breakdownsByDate[date]).reduce(
(dateCosts, breakdown) => {
// Group costs for items that belong to 'Other' in the chart.
const cost = breakdownsByDate[date][breakdown];
const breakdownCost =
!isExpanded && otherCategoryIds.includes(breakdown)
? { Other: (dateCosts.Other || 0) + cost }
: { [breakdown]: cost };
return { ...dateCosts, ...breakdownCost };
},
{} as Record<string, number>,
);
return {
...costsForDate,
date: Date.parse(date),
};
},
);
const sortedBreakdowns = costBreakdown.sort(
(a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation),
);
const renderAreas = () => {
const separatedBreakdowns = sortedBreakdowns
// Check that the breakdown is a separate group and hasn't been added to 'Other'
.filter(
breakdown =>
breakdown.id !== 'Other' && !otherCategoryIds.includes(breakdown.id),
)
.map(breakdown => breakdown.id);
// Keep 'Other' category at the bottom of the stack
const breakdownsToDisplay = isExpanded
? sortedBreakdowns.map(breakdown => breakdown.id)
: ['Other', ...separatedBreakdowns];
return breakdownsToDisplay.map((breakdown, i) => {
// Logic to handle case where there are more items than data viz colors.
const color =
theme.palette.dataViz[
(breakdownsToDisplay.length - 1 - i) %
(theme.palette.dataViz.length - 1)
];
return (
<Area
key={breakdown}
dataKey={breakdown}
isAnimationActive={false}
stackId="1"
stroke={color}
fill={color}
onClick={() => setExpanded(true)}
style={{
cursor: breakdown === 'Other' && !isExpanded ? 'pointer' : null,
}}
/>
);
});
};
const tooltipRenderer: ContentRenderer<TooltipProps> = ({
label,
payload = [],
}) => {
if (isInvalid({ label, payload })) return null;
const date =
typeof label === 'number'
? DateTime.fromMillis(label)
: DateTime.fromISO(label!);
const dateTitle = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);
const items = payload.map(p => ({
label: p.dataKey as string,
value: formatGraphValue(p.value as number),
fill: p.fill!,
}));
const expandText = (
<Box>
<Divider
style={{
backgroundColor: emphasize(theme.palette.divider, 1),
margin: '10px 0',
}}
/>
<Box display="flex" justifyContent="space-between" alignItems="center">
<FullScreenIcon />
<Typography>Click to expand</Typography>
</Box>
</Box>
);
return (
<Tooltip title={dateTitle}>
{items.reverse().map((item, index) => (
<TooltipItem key={`${item.label}-${index}`} item={item} />
))}
{canExpand && !isExpanded ? expandText : null}
</Tooltip>
);
};
const options: Partial<BarChartLegendOptions> = {
previousName: formatPeriod(duration, lastCompleteBillingDate, false),
currentName: formatPeriod(duration, lastCompleteBillingDate, true),
hideMarker: true,
};
return (
<Box display="flex" flexDirection="column">
<Box display="flex" flexDirection="row">
<BarChartLegend
costStart={previousPeriodTotal}
costEnd={currentPeriodTotal}
options={options}
/>
</Box>
<ResponsiveContainer
width={classes.container.width}
height={classes.container.height}
>
<AreaChart
data={chartData}
margin={{
top: 16,
right: 30,
bottom: 40,
}}
>
<CartesianGrid stroke={classes.cartesianGrid.stroke} />
<XAxis
dataKey="date"
domain={['dataMin', 'dataMax']}
tickFormatter={overviewGraphTickFormatter}
tickCount={6}
type="number"
stroke={classes.axis.fill}
/>
<YAxis
domain={[() => 0, 'dataMax']}
tick={{ fill: classes.axis.fill }}
tickFormatter={formatGraphValue}
width={classes.yAxis.width}
/>
{renderAreas()}
<RechartsTooltip content={tooltipRenderer} animationDuration={100} />
</AreaChart>
</ResponsiveContainer>
</Box>
);
}
Example #22
Source File: status-chart.tsx From backstage with Apache License 2.0 | 4 votes |
export function StatusChart(props: StatusChartProps) {
const { analysis } = props;
const { zoomFilterValues } = useZoom();
const { zoomProps, getZoomArea } = useZoomArea();
const values = useMemo(() => {
return analysis.daily.values.map(value => {
const totTriggers = analysis.daily.triggerReasons.reduce(
(prev, cur) => prev + (value[cur as TriggerReason] ?? 0),
0,
);
if (!totTriggers) {
return value;
}
return {
...value,
...Object.fromEntries(
analysis.daily.triggerReasons.map(reason => [
reason,
(value[reason as TriggerReason] ?? 0) / totTriggers,
]),
),
};
});
}, [analysis.daily]);
const triggerReasonLegendPayload = useMemo(
(): NonNullable<LegendProps['payload']> =>
analysis.daily.triggerReasons.map(reason => ({
value: humanTriggerReason(reason),
type: 'line',
id: reason,
color: triggerColorMap[reason as TriggerReason] ?? '',
})),
[analysis.daily.triggerReasons],
);
const statusesLegendPayload = useMemo(
(): NonNullable<LegendProps['payload']> =>
analysis.daily.statuses.map(status => ({
value: capitalize(status),
type: 'line',
id: status,
color: statusColorMap[status as FilterStatusType] ?? '',
})),
[analysis.daily.statuses],
);
const legendPayload = useMemo(
(): NonNullable<LegendProps['payload']> => [
...triggerReasonLegendPayload,
...statusesLegendPayload,
],
[statusesLegendPayload, triggerReasonLegendPayload],
);
const tooltipFormatter = useMemo(() => {
const reasonSet = new Set(analysis.daily.triggerReasons);
return (percentOrCount: number, name: string) => {
const label = reasonSet.has(name)
? humanTriggerReason(name)
: capitalize(name);
const valueText = reasonSet.has(name)
? `${(percentOrCount * 100).toFixed(0)}%`
: percentOrCount;
return [
<span>
{label}: {valueText}
</span>,
null,
];
};
}, [analysis.daily.triggerReasons]);
const zoomFilteredValues = useMemo(
() => zoomFilterValues(values),
[values, zoomFilterValues],
);
const barSize = getBarSize(analysis.daily.values.length);
return (
<Accordion defaultExpanded={analysis.daily.statuses.length > 1}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>
Build count per status over build trigger reason
</Typography>
</AccordionSummary>
<AccordionDetails>
{values.length === 0 ? (
<Alert severity="info">No data</Alert>
) : (
<ResponsiveContainer width="100%" height={140}>
<ComposedChart data={zoomFilteredValues} {...zoomProps}>
<Legend payload={legendPayload} />
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="__epoch"
type="category"
tickFormatter={tickFormatterX}
/>
<YAxis yAxisId={1} type="number" tickCount={5} name="Count" />
<YAxis yAxisId={2} type="number" name="Triggers" hide />
<Tooltip
labelFormatter={labelFormatterWithoutTime}
formatter={tooltipFormatter}
/>
{triggerReasonLegendPayload.map(reason => (
<Fragment key={reason.id}>
<Area
isAnimationActive={false}
type="monotone"
dataKey={reason.id!}
stackId="triggers"
yAxisId={2}
stroke={triggerColorMap[reason.id as TriggerReason] ?? ''}
fillOpacity={0.5}
fill={triggerColorMap[reason.id as TriggerReason] ?? ''}
/>
</Fragment>
))}
{[...analysis.daily.statuses].reverse().map(status => (
<Fragment key={status}>
<Bar
isAnimationActive={false}
type="monotone"
barSize={barSize}
dataKey={status}
stackId="statuses"
yAxisId={1}
stroke={statusColorMap[status as FilterStatusType] ?? ''}
fillOpacity={0.8}
fill={statusColorMap[status as FilterStatusType] ?? ''}
/>
</Fragment>
))}
{getZoomArea({ yAxisId: 1 })}
</ComposedChart>
</ResponsiveContainer>
)}
</AccordionDetails>
</Accordion>
);
}
Example #23
Source File: stage-chart.tsx From backstage with Apache License 2.0 | 4 votes |
export function StageChart(props: StageChartProps) {
const { stage, ...chartOptions } = props;
const {
chartTypes,
defaultCollapsed = 0,
defaultHidden = 0,
zeroYAxis = false,
} = chartOptions;
const { zoomFilterValues } = useZoom();
const { zoomProps, getZoomArea } = useZoomArea();
const ticks = useMemo(
() => pickElements(stage.values, 8).map(val => val.__epoch),
[stage.values],
);
const domainY = useMemo(
() => [zeroYAxis ? 0 : 'auto', 'auto'] as YAxisProps['domain'],
[zeroYAxis],
);
const statuses = useMemo(
() => statusTypes.filter(status => stage.statusSet.has(status)),
[stage.statusSet],
);
const legendPayload = useMemo(
(): LegendProps['payload'] =>
statuses.map(status => ({
value: capitalize(status),
type: 'line',
id: status,
color: statusColorMap[status],
})),
[statuses],
);
const subStages = useMemo(
() =>
new Map<string, ChartableStage>(
[...stage.stages.entries()].filter(
([_name, subStage]) => subStage.combinedAnalysis.max > defaultHidden,
),
),
[stage.stages, defaultHidden],
);
const zoomFilteredValues = useMemo(
() => zoomFilterValues(stage.values),
[stage.values, zoomFilterValues],
);
return stage.combinedAnalysis.max < defaultHidden ? null : (
<Accordion
defaultExpanded={stage.combinedAnalysis.max > defaultCollapsed}
TransitionProps={transitionProps}
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>
{stage.name} (med {formatDuration(stage.combinedAnalysis.med)}, avg{' '}
{formatDuration(stage.combinedAnalysis.avg)})
</Typography>
</AccordionSummary>
<AccordionDetails>
{stage.values.length === 0 ? (
<Alert severity="info">No data</Alert>
) : (
<Grid container direction="column">
<Grid item style={noUserSelect}>
<ResponsiveContainer width="100%" height={140}>
<ComposedChart data={zoomFilteredValues} {...zoomProps}>
<defs>
<linearGradient id="colorDur" x1="0" y1="0" x2="0" y2="1">
{fireColors.map(([percent, color]) => (
<stop
key={percent}
offset={percent}
stopColor={color}
stopOpacity={0.8}
/>
))}
</linearGradient>
</defs>
{statuses.length > 1 && <Legend payload={legendPayload} />}
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="__epoch"
type="category"
ticks={ticks}
tickFormatter={tickFormatterX}
/>
<YAxis
yAxisId={1}
tickFormatter={tickFormatterY}
type="number"
tickCount={5}
name="Duration"
domain={domainY}
/>
<YAxis
yAxisId={2}
orientation="right"
type="number"
tickCount={5}
name="Count"
/>
<Tooltip
formatter={tooltipValueFormatter}
labelFormatter={labelFormatter}
/>
{statuses.reverse().map(status => (
<Fragment key={status}>
{!chartTypes[status].includes('duration') ? null : (
<>
<Area
isAnimationActive={false}
yAxisId={1}
type="monotone"
dataKey={status}
stackId={status}
stroke={
statuses.length > 1
? statusColorMap[status]
: colorStroke
}
fillOpacity={statuses.length > 1 ? 0.5 : 1}
fill={
statuses.length > 1
? statusColorMap[status]
: 'url(#colorDur)'
}
connectNulls
/>
<Line
isAnimationActive={false}
yAxisId={1}
type="monotone"
dataKey={`${status} avg`}
stroke={
statuses.length > 1
? statusColorMap[status]
: colorStrokeAvg
}
opacity={0.8}
strokeWidth={2}
dot={false}
connectNulls
/>
</>
)}
{!chartTypes[status].includes('count') ? null : (
<Bar
isAnimationActive={false}
yAxisId={2}
type="monotone"
dataKey={`${status} count`}
stackId="1"
stroke={statusColorMap[status] ?? ''}
fillOpacity={0.5}
fill={statusColorMap[status] ?? ''}
/>
)}
</Fragment>
))}
{getZoomArea({ yAxisId: 1 })}
</ComposedChart>
</ResponsiveContainer>
</Grid>
{subStages.size === 0 ? null : (
<Grid item>
<Accordion
defaultExpanded={false}
TransitionProps={transitionProps}
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>Sub stages ({subStages.size})</Typography>
</AccordionSummary>
<AccordionDetails>
<div style={fullWidth}>
{[...subStages.values()].map(subStage => (
<StageChart
key={subStage.name}
{...chartOptions}
stage={subStage}
/>
))}
</div>
</AccordionDetails>
</Accordion>
</Grid>
)}
</Grid>
)}
</AccordionDetails>
</Accordion>
);
}