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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
                  &nbsp;
                  <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:&nbsp;<strong>{finalRes.servers}</strong>
                </Grid>
                <Grid
                  item
                  xs={12}
                  sm={6}
                  md={3}
                  lg={2}
                  className={classes.detailedItem}
                >
                  Drives:&nbsp;<strong>{finalRes.disks}</strong>
                </Grid>
                <Grid
                  item
                  xs={12}
                  sm={6}
                  md={3}
                  lg={2}
                  className={classes.detailedItem}
                >
                  Concurrent:&nbsp;<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&nbsp;<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))}&nbsp;
                                {stats.getUnit}/s.
                              </td>
                            </Fragment>
                          )}
                          {stats.putError && stats.putError !== "" ? (
                            <td>{stats.putError}</td>
                          ) : (
                            <Fragment>
                              <td className={classes.serverValue}>
                                {prettyNumber(parseFloat(stats.putValue))}&nbsp;
                                {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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
  );
}