web: histogram
This commit is contained in:
@@ -10,16 +10,16 @@ export function ReportDateRange() {
|
||||
|
||||
return (
|
||||
<RadioGroup className="overflow-auto">
|
||||
{timeRanges.map((item) => {
|
||||
{Object.values(timeRanges).map((key) => {
|
||||
return (
|
||||
<RadioGroupItem
|
||||
key={item.range}
|
||||
active={item.range === range}
|
||||
key={key}
|
||||
active={key === range}
|
||||
onClick={() => {
|
||||
dispatch(changeDateRanges(item.range));
|
||||
dispatch(changeDateRanges(key));
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
{key}
|
||||
</RadioGroupItem>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -38,7 +38,11 @@ export function ReportInterval() {
|
||||
{
|
||||
value: 'month',
|
||||
label: 'Month',
|
||||
disabled: range < 1,
|
||||
disabled:
|
||||
range === 'today' ||
|
||||
range === '24h' ||
|
||||
range === '1h' ||
|
||||
range === '30min',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
107
apps/web/src/components/report/chart/ReportHistogramChart.tsx
Normal file
107
apps/web/src/components/report/chart/ReportHistogramChart.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { AutoSizer } from '@/components/AutoSizer';
|
||||
import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
|
||||
import type { IChartData, IInterval } from '@/types';
|
||||
import { alphabetIds } from '@/utils/constants';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import { useChartContext } from './ChartProvider';
|
||||
import { ReportLineChartTooltip } from './ReportLineChartTooltip';
|
||||
import { ReportTable } from './ReportTable';
|
||||
|
||||
interface ReportHistogramChartProps {
|
||||
data: IChartData;
|
||||
interval: IInterval;
|
||||
}
|
||||
|
||||
export function ReportHistogramChart({
|
||||
interval,
|
||||
data,
|
||||
}: ReportHistogramChartProps) {
|
||||
const { editMode } = useChartContext();
|
||||
const [visibleSeries, setVisibleSeries] = useState<string[]>([]);
|
||||
const formatDate = useFormatDateInterval(interval);
|
||||
|
||||
const ref = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!ref.current && data) {
|
||||
const max = 20;
|
||||
|
||||
setVisibleSeries(
|
||||
data?.series?.slice(0, max).map((serie) => serie.name) ?? []
|
||||
);
|
||||
// ref.current = true;
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const rel = data.series[0]?.data.map(({ date }) => {
|
||||
return {
|
||||
date,
|
||||
...data.series.reduce((acc, serie, idx) => {
|
||||
return {
|
||||
...acc,
|
||||
...serie.data.reduce(
|
||||
(acc2, item) => {
|
||||
const id = alphabetIds[idx];
|
||||
if (item.date === date) {
|
||||
acc2[`${id}:count`] = item.count;
|
||||
acc2[`${id}:label`] = item.label;
|
||||
}
|
||||
return acc2;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
),
|
||||
};
|
||||
}, {}),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="max-sm:-mx-3">
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }) => (
|
||||
<BarChart
|
||||
width={width}
|
||||
height={Math.min(Math.max(width * 0.5, 250), 400)}
|
||||
data={rel}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<Tooltip content={<ReportLineChartTooltip />} />
|
||||
<XAxis
|
||||
fontSize={12}
|
||||
dataKey="date"
|
||||
tickFormatter={formatDate}
|
||||
tickLine={false}
|
||||
/>
|
||||
{data.series.map((serie, index) => {
|
||||
const id = alphabetIds[index];
|
||||
return (
|
||||
<>
|
||||
<YAxis dataKey={`${id}:count`} fontSize={12}></YAxis>
|
||||
<Bar
|
||||
stackId={id}
|
||||
key={serie.name}
|
||||
isAnimationActive={false}
|
||||
name={serie.name}
|
||||
dataKey={`${id}:count`}
|
||||
fill={getChartColor(index)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</BarChart>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
{editMode && (
|
||||
<ReportTable
|
||||
data={data}
|
||||
visibleSeries={visibleSeries}
|
||||
setVisibleSeries={setVisibleSeries}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
|
||||
import { useMappings } from '@/hooks/useMappings';
|
||||
import { useSelector } from '@/redux';
|
||||
import type { IToolTipProps } from '@/types';
|
||||
import { alphabetIds } from '@/utils/constants';
|
||||
|
||||
type ReportLineChartTooltipProps = IToolTipProps<{
|
||||
color: string;
|
||||
@@ -10,7 +11,7 @@ type ReportLineChartTooltipProps = IToolTipProps<{
|
||||
date: Date;
|
||||
count: number;
|
||||
label: string;
|
||||
};
|
||||
} & Record<string, any>;
|
||||
}>;
|
||||
|
||||
export function ReportLineChartTooltip({
|
||||
@@ -34,11 +35,13 @@ export function ReportLineChartTooltip({
|
||||
const visible = sorted.slice(0, limit);
|
||||
const hidden = sorted.slice(limit);
|
||||
const first = visible[0]!;
|
||||
const isBarChart = first.payload.count === undefined;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 rounded-xl border bg-white p-3 text-sm shadow-xl">
|
||||
{formatDate(new Date(first.payload.date))}
|
||||
{visible.map((item) => {
|
||||
{visible.map((item, index) => {
|
||||
const id = alphabetIds[index];
|
||||
return (
|
||||
<div key={item.payload.label} className="flex gap-2">
|
||||
<div
|
||||
@@ -47,9 +50,13 @@ export function ReportLineChartTooltip({
|
||||
></div>
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-0 max-w-[200px] overflow-hidden text-ellipsis whitespace-nowrap font-medium">
|
||||
{getLabel(item.payload.label)}
|
||||
{isBarChart
|
||||
? item.payload[`${id}:label`]
|
||||
: getLabel(item.payload.label)}
|
||||
</div>
|
||||
<div>
|
||||
{isBarChart ? item.payload[`${id}:count`] : item.payload.count}
|
||||
</div>
|
||||
<div>{item.payload.count}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { api } from '@/utils/api';
|
||||
import { ChartAnimation, ChartAnimationContainer } from './ChartAnimation';
|
||||
import { withChartProivder } from './ChartProvider';
|
||||
import { ReportBarChart } from './ReportBarChart';
|
||||
import { ReportHistogramChart } from './ReportHistogramChart';
|
||||
import { ReportLineChart } from './ReportLineChart';
|
||||
|
||||
export type ReportChartProps = IChartInput;
|
||||
@@ -88,6 +89,10 @@ export const Chart = memo(
|
||||
);
|
||||
}
|
||||
|
||||
if (chartType === 'histogram') {
|
||||
return <ReportHistogramChart interval={interval} data={chart.data} />;
|
||||
}
|
||||
|
||||
if (chartType === 'bar') {
|
||||
return <ReportBarChart data={chart.data} />;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const initialState: InitialState = {
|
||||
interval: 'day',
|
||||
breakdowns: [],
|
||||
events: [],
|
||||
range: 30,
|
||||
range: '1m',
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
};
|
||||
@@ -149,11 +149,11 @@ export const reportSlice = createSlice({
|
||||
changeDateRanges: (state, action: PayloadAction<IChartRange>) => {
|
||||
state.dirty = true;
|
||||
state.range = action.payload;
|
||||
if (action.payload === 0.3 || action.payload === 0.6) {
|
||||
if (action.payload === '30min' || action.payload === '1h') {
|
||||
state.interval = 'minute';
|
||||
} else if (action.payload === 0 || action.payload === 1) {
|
||||
} else if (action.payload === 'today' || action.payload === '24h') {
|
||||
state.interval = 'hour';
|
||||
} else if (action.payload <= 30) {
|
||||
} else if (action.payload === '7d' || action.payload === '14d') {
|
||||
state.interval = 'day';
|
||||
} else {
|
||||
state.interval = 'month';
|
||||
|
||||
@@ -108,6 +108,10 @@ export function ReportEvents() {
|
||||
value: 'user_average',
|
||||
label: 'Unique users (average)',
|
||||
},
|
||||
{
|
||||
value: 'one_event_per_user',
|
||||
label: 'One event per user',
|
||||
},
|
||||
]}
|
||||
label="Segment"
|
||||
>
|
||||
@@ -118,7 +122,11 @@ export function ReportEvents() {
|
||||
</>
|
||||
) : event.segment === 'user_average' ? (
|
||||
<>
|
||||
<Users size={12} /> Average per user
|
||||
<Users size={12} /> Unique users (average)
|
||||
</>
|
||||
) : event.segment === 'one_event_per_user' ? (
|
||||
<>
|
||||
<Users size={12} /> One event per user
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user