fix: metric chart total count
This commit is contained in:
@@ -2,6 +2,34 @@ import { createContext, useContext as useBaseContext } from 'react';
|
||||
|
||||
import { Tooltip as RechartsTooltip, type TooltipProps } from 'recharts';
|
||||
|
||||
export const ChartTooltipContainer = ({
|
||||
children,
|
||||
}: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<div className="min-w-[180px] col gap-2 rounded-xl border bg-background/80 p-3 shadow-xl backdrop-blur-sm">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChartTooltipHeader = ({
|
||||
children,
|
||||
}: { children: React.ReactNode }) => {
|
||||
return <div className="flex justify-between gap-8">{children}</div>;
|
||||
};
|
||||
|
||||
export const ChartTooltipItem = ({
|
||||
children,
|
||||
color,
|
||||
}: { children: React.ReactNode; color: string }) => {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<div className="w-[3px] rounded-full" style={{ background: color }} />
|
||||
<div className="col flex-1 gap-1">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function createChartTooltip<
|
||||
PropsFromTooltip,
|
||||
PropsFromContext extends Record<string, unknown>,
|
||||
@@ -31,9 +59,9 @@ export function createChartTooltip<
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-w-[180px] flex-col gap-2 rounded-xl border bg-background/80 p-3 shadow-xl backdrop-blur-sm">
|
||||
<ChartTooltipContainer>
|
||||
<Tooltip data={data} context={context} {...tooltip} />
|
||||
</div>
|
||||
</ChartTooltipContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { fancyMinutes, useNumber } from '@/hooks/use-numer-formatter';
|
||||
import { cn } from '@/utils/cn';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { Area, AreaChart } from 'recharts';
|
||||
import { Area, AreaChart, Tooltip } from 'recharts';
|
||||
|
||||
import { formatDate, timeAgo } from '@/utils/date';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
import { getPreviousMetric } from '@openpanel/common';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
ChartTooltipContainer,
|
||||
ChartTooltipHeader,
|
||||
ChartTooltipItem,
|
||||
} from '../charts/chart-tooltip';
|
||||
import {
|
||||
PreviousDiffIndicatorPure,
|
||||
getDiffIndicator,
|
||||
@@ -41,6 +48,7 @@ export function OverviewMetricCard({
|
||||
inverted = false,
|
||||
isLoading = false,
|
||||
}: MetricCardProps) {
|
||||
const [value, setValue] = useState(metric.current);
|
||||
const number = useNumber();
|
||||
const { current, previous } = metric;
|
||||
|
||||
@@ -79,7 +87,7 @@ export function OverviewMetricCard({
|
||||
<span>
|
||||
{label}:{' '}
|
||||
<span className="font-semibold">
|
||||
{renderValue(current, 'ml-1 font-light text-xl', false)}
|
||||
{renderValue(value, 'ml-1 font-light text-xl', false)}
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
@@ -97,7 +105,7 @@ export function OverviewMetricCard({
|
||||
<div className={cn('group relative p-4')}>
|
||||
<div
|
||||
className={cn(
|
||||
'pointer-events-none absolute -left-1 -right-1 bottom-0 top-0 z-0 opacity-50 transition-opacity duration-300 group-hover:opacity-100',
|
||||
'absolute -left-1 -right-1 bottom-0 top-0 z-0 opacity-50 transition-opacity duration-300 group-hover:opacity-100',
|
||||
)}
|
||||
>
|
||||
<AutoSizer>
|
||||
@@ -107,6 +115,11 @@ export function OverviewMetricCard({
|
||||
height={height / 4}
|
||||
data={data}
|
||||
style={{ marginTop: (height / 4) * 3 }}
|
||||
onMouseMove={(event) => {
|
||||
setValue(
|
||||
event.activePayload?.[0]?.payload?.current ?? current,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
@@ -128,6 +141,7 @@ export function OverviewMetricCard({
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Tooltip content={() => null} />
|
||||
<Area
|
||||
dataKey={'current'}
|
||||
type="step"
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getChartColor } from '@/utils/theme';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { isSameDay, isSameHour, isSameMonth, isSameWeek } from 'date-fns';
|
||||
import { last } from 'ramda';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import {
|
||||
Area,
|
||||
CartesianGrid,
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
|
||||
import { useDashedStroke } from '@/hooks/use-dashed-stroke';
|
||||
import { useXAxisProps, useYAxisProps } from '../common/axis';
|
||||
import { SolidToDashedGradient } from '../common/linear-gradient';
|
||||
import { ReportChartTooltip } from '../common/report-chart-tooltip';
|
||||
import { ReportTable } from '../common/report-table';
|
||||
import { SerieIcon } from '../common/serie-icon';
|
||||
|
||||
@@ -35,7 +35,9 @@ export function Chart({ data }: Props) {
|
||||
() => (isEditMode ? data.series : data.series.slice(0, limit || 10)),
|
||||
[data, isEditMode, limit],
|
||||
);
|
||||
const maxCount = Math.max(...series.map((serie) => serie.metrics[metric]));
|
||||
const maxCount = Math.max(
|
||||
...series.map((serie) => serie.metrics[metric] ?? 0),
|
||||
);
|
||||
|
||||
const tableColumns = [
|
||||
{
|
||||
|
||||
@@ -3,7 +3,11 @@ import { useNumber } from '@/hooks/use-numer-formatter';
|
||||
import type { IRechartPayloadItem } from '@/hooks/use-rechart-data-model';
|
||||
import React from 'react';
|
||||
|
||||
import { createChartTooltip } from '@/components/charts/chart-tooltip';
|
||||
import {
|
||||
ChartTooltipHeader,
|
||||
ChartTooltipItem,
|
||||
createChartTooltip,
|
||||
} from '@/components/charts/chart-tooltip';
|
||||
import type { RouterOutputs } from '@/trpc/client';
|
||||
import type { IInterval } from '@openpanel/validation';
|
||||
import {
|
||||
@@ -88,37 +92,31 @@ export const ReportChartTooltip = createChartTooltip<Data, Context>(
|
||||
const hidden = sorted.slice(limit);
|
||||
|
||||
return (
|
||||
<div className="flex min-w-[180px] flex-col gap-2">
|
||||
<>
|
||||
{visible.map((item, index) => (
|
||||
<React.Fragment key={item.id}>
|
||||
{index === 0 && item.date && (
|
||||
<div className="flex justify-between gap-8">
|
||||
<ChartTooltipHeader>
|
||||
<div>{formatDate(new Date(item.date))}</div>
|
||||
</div>
|
||||
</ChartTooltipHeader>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<div
|
||||
className="w-[3px] rounded-full"
|
||||
style={{ background: item.color }}
|
||||
/>
|
||||
<div className="col flex-1 gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<SerieIcon name={item.names} />
|
||||
<SerieName name={item.names} />
|
||||
</div>
|
||||
<div className="flex justify-between gap-8 font-mono font-medium">
|
||||
<div className="row gap-1">
|
||||
{number.formatWithUnit(item.count, unit)}
|
||||
{!!item.previous && (
|
||||
<span className="text-muted-foreground">
|
||||
({number.formatWithUnit(item.previous.value, unit)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<PreviousDiffIndicator {...item.previous} />
|
||||
</div>
|
||||
<ChartTooltipItem color={item.color}>
|
||||
<div className="flex items-center gap-1">
|
||||
<SerieIcon name={item.names} />
|
||||
<SerieName name={item.names} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between gap-8 font-mono font-medium">
|
||||
<div className="row gap-1">
|
||||
{number.formatWithUnit(item.count, unit)}
|
||||
{!!item.previous && (
|
||||
<span className="text-muted-foreground">
|
||||
({number.formatWithUnit(item.previous.value, unit)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<PreviousDiffIndicator {...item.previous} />
|
||||
</div>
|
||||
</ChartTooltipItem>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{hidden.length > 0 && (
|
||||
@@ -142,7 +140,7 @@ export const ReportChartTooltip = createChartTooltip<Data, Context>(
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Chart({ data }: Props) {
|
||||
() =>
|
||||
series.map((s) => ({
|
||||
country: s.names[0]?.toLowerCase() ?? '',
|
||||
value: s.metrics[metric],
|
||||
value: s.metrics[metric] ?? 0,
|
||||
})),
|
||||
[series, metric],
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ interface Props {
|
||||
export function Chart({ data }: Props) {
|
||||
const {
|
||||
isEditMode,
|
||||
report: { metric, unit },
|
||||
report: { unit },
|
||||
} = useReportChartContext();
|
||||
const { series } = useVisibleSeries(data, isEditMode ? 20 : 4);
|
||||
return (
|
||||
@@ -27,7 +27,7 @@ export function Chart({ data }: Props) {
|
||||
<MetricCard
|
||||
key={serie.id}
|
||||
serie={serie}
|
||||
metric={metric}
|
||||
metric={'count'}
|
||||
unit={unit}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2,10 +2,17 @@ import { fancyMinutes, useNumber } from '@/hooks/use-numer-formatter';
|
||||
import type { IChartData } from '@/trpc/client';
|
||||
import { cn } from '@/utils/cn';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { Area, AreaChart } from 'recharts';
|
||||
import { Area, AreaChart, Tooltip } from 'recharts';
|
||||
|
||||
import type { IChartMetric } from '@openpanel/validation';
|
||||
|
||||
import {
|
||||
ChartTooltipContainer,
|
||||
ChartTooltipHeader,
|
||||
ChartTooltipItem,
|
||||
} from '@/components/charts/chart-tooltip';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
import {
|
||||
PreviousDiffIndicator,
|
||||
getDiffIndicator,
|
||||
@@ -20,6 +27,27 @@ interface MetricCardProps {
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
const TooltipContent = (props: { payload?: any[] }) => {
|
||||
const number = useNumber();
|
||||
return (
|
||||
<ChartTooltipContainer>
|
||||
{props.payload?.map((item) => {
|
||||
const { date, count } = item.payload;
|
||||
return (
|
||||
<div key={item.id} className="col gap-2">
|
||||
<ChartTooltipHeader>
|
||||
<div>{formatDate(new Date(date))}</div>
|
||||
</ChartTooltipHeader>
|
||||
<ChartTooltipItem color={getChartColor(0)}>
|
||||
<div>{number.format(count)}</div>
|
||||
</ChartTooltipItem>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</ChartTooltipContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export function MetricCard({
|
||||
serie,
|
||||
color: _color,
|
||||
@@ -32,7 +60,11 @@ export function MetricCard({
|
||||
} = useReportChartContext();
|
||||
const number = useNumber();
|
||||
|
||||
const renderValue = (value: number, unitClassName?: string) => {
|
||||
const renderValue = (value: number | undefined, unitClassName?: string) => {
|
||||
if (!value) {
|
||||
return <div className="text-muted-foreground">N/A</div>;
|
||||
}
|
||||
|
||||
if (unit === 'min') {
|
||||
return <>{fancyMinutes(value)}</>;
|
||||
}
|
||||
@@ -62,7 +94,7 @@ export function MetricCard({
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'pointer-events-none absolute -left-1 -right-1 bottom-0 top-0 z-0 opacity-50 transition-opacity duration-300 group-hover:opacity-100',
|
||||
'absolute -left-1 -right-1 bottom-0 top-0 z-0 opacity-100 transition-opacity duration-300 group-hover:opacity-100',
|
||||
)}
|
||||
>
|
||||
<AutoSizer>
|
||||
@@ -89,6 +121,7 @@ export function MetricCard({
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Tooltip content={TooltipContent} />
|
||||
<Area
|
||||
dataKey="count"
|
||||
type="step"
|
||||
|
||||
@@ -7,6 +7,11 @@ import { truncate } from '@/utils/truncate';
|
||||
import { Fragment } from 'react';
|
||||
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
|
||||
|
||||
import {
|
||||
ChartTooltipContainer,
|
||||
ChartTooltipHeader,
|
||||
ChartTooltipItem,
|
||||
} from '@/components/charts/chart-tooltip';
|
||||
import { useNumber } from '@/hooks/use-numer-formatter';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { AXIS_FONT_PROPS } from '../common/axis';
|
||||
@@ -24,43 +29,37 @@ interface Props {
|
||||
const PieTooltip = (props: { payload?: any[] }) => {
|
||||
const number = useNumber();
|
||||
return (
|
||||
<div className="bg-background/80 p-2 rounded-md backdrop-blur-md border min-w-[180px]">
|
||||
<ChartTooltipContainer>
|
||||
{props.payload?.map((serie, index) => {
|
||||
const item = serie.payload;
|
||||
return (
|
||||
<Fragment key={item.id}>
|
||||
{index === 0 && item.date && (
|
||||
<div className="flex justify-between gap-8">
|
||||
<ChartTooltipHeader>
|
||||
<div>{formatDate(new Date(item.date))}</div>
|
||||
</div>
|
||||
</ChartTooltipHeader>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<div
|
||||
className="w-[3px] rounded-full"
|
||||
style={{ background: item.color }}
|
||||
/>
|
||||
<div className="col flex-1 gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<SerieIcon name={item.name} />
|
||||
<SerieName name={item.names} className="font-medium" />
|
||||
</div>
|
||||
<div className="flex justify-between gap-8 font-mono font-medium">
|
||||
<div className="row gap-1">
|
||||
{number.formatWithUnit(item.count)}
|
||||
{!!item.previous && (
|
||||
<span className="text-muted-foreground">
|
||||
({number.formatWithUnit(item.previous.sum.value)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<PreviousDiffIndicator {...item.previous?.sum} />
|
||||
</div>
|
||||
<ChartTooltipItem color={item.color}>
|
||||
<div className="flex items-center gap-1">
|
||||
<SerieIcon name={item.name} />
|
||||
<SerieName name={item.names} className="font-medium" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between gap-8 font-mono font-medium">
|
||||
<div className="row gap-1">
|
||||
{number.formatWithUnit(item.count)}
|
||||
{!!item.previous && (
|
||||
<span className="text-muted-foreground">
|
||||
({number.formatWithUnit(item.previous.sum.value)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<PreviousDiffIndicator {...item.previous?.sum} />
|
||||
</div>
|
||||
</ChartTooltipItem>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ChartTooltipContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user