feat: add stacked option for histogram
This commit is contained in:
@@ -61,9 +61,14 @@ export function Chart({ data }: Props) {
|
|||||||
range,
|
range,
|
||||||
series: reportSeries,
|
series: reportSeries,
|
||||||
breakdowns,
|
breakdowns,
|
||||||
|
options: reportOptions,
|
||||||
},
|
},
|
||||||
options: { hideXAxis, hideYAxis },
|
options: { hideXAxis, hideYAxis },
|
||||||
} = useReportChartContext();
|
} = useReportChartContext();
|
||||||
|
|
||||||
|
const histogramOptions =
|
||||||
|
reportOptions?.type === 'histogram' ? reportOptions : undefined;
|
||||||
|
const isStacked = histogramOptions?.stacked ?? false;
|
||||||
const trpc = useTRPC();
|
const trpc = useTRPC();
|
||||||
const references = useQuery(
|
const references = useQuery(
|
||||||
trpc.reference.getChartReferences.queryOptions(
|
trpc.reference.getChartReferences.queryOptions(
|
||||||
@@ -155,68 +160,70 @@ export function Chart({ data }: Props) {
|
|||||||
<div className={cn('h-full w-full', isEditMode && 'card p-4')}>
|
<div className={cn('h-full w-full', isEditMode && 'card p-4')}>
|
||||||
<ResponsiveContainer>
|
<ResponsiveContainer>
|
||||||
<BarChart data={rechartData}>
|
<BarChart data={rechartData}>
|
||||||
<CartesianGrid
|
<CartesianGrid
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
vertical={false}
|
vertical={false}
|
||||||
className="stroke-def-200"
|
className="stroke-def-200"
|
||||||
/>
|
|
||||||
<Tooltip
|
|
||||||
content={<ReportChartTooltip.Tooltip />}
|
|
||||||
cursor={<BarHover />}
|
|
||||||
/>
|
|
||||||
<YAxis {...yAxisProps} />
|
|
||||||
<XAxis {...xAxisProps} scale={'auto'} type="category" />
|
|
||||||
{previous
|
|
||||||
? series.map((serie) => {
|
|
||||||
return (
|
|
||||||
<Bar
|
|
||||||
key={`${serie.id}:prev`}
|
|
||||||
name={`${serie.id}:prev`}
|
|
||||||
dataKey={`${serie.id}:prev:count`}
|
|
||||||
fill={getChartColor(serie.index)}
|
|
||||||
fillOpacity={0.3}
|
|
||||||
radius={5}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
{series.map((serie) => {
|
|
||||||
return (
|
|
||||||
<Bar
|
|
||||||
key={serie.id}
|
|
||||||
name={serie.id}
|
|
||||||
dataKey={`${serie.id}:count`}
|
|
||||||
fill={getChartColor(serie.index)}
|
|
||||||
radius={5}
|
|
||||||
fillOpacity={1}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{references.data?.map((ref) => (
|
|
||||||
<ReferenceLine
|
|
||||||
key={ref.id}
|
|
||||||
x={ref.date.getTime()}
|
|
||||||
stroke={'oklch(from var(--foreground) l c h / 0.1)'}
|
|
||||||
strokeDasharray={'3 3'}
|
|
||||||
label={{
|
|
||||||
value: ref.title,
|
|
||||||
position: 'centerTop',
|
|
||||||
fill: '#334155',
|
|
||||||
fontSize: 12,
|
|
||||||
}}
|
|
||||||
fontSize={10}
|
|
||||||
/>
|
/>
|
||||||
))}
|
<Tooltip
|
||||||
</BarChart>
|
content={<ReportChartTooltip.Tooltip />}
|
||||||
</ResponsiveContainer>
|
cursor={<BarHover />}
|
||||||
</div>
|
/>
|
||||||
{isEditMode && (
|
<YAxis {...yAxisProps} />
|
||||||
<ReportTable
|
<XAxis {...xAxisProps} scale={'auto'} type="category" />
|
||||||
data={data}
|
{previous
|
||||||
visibleSeries={series}
|
? series.map((serie) => {
|
||||||
setVisibleSeries={setVisibleSeries}
|
return (
|
||||||
/>
|
<Bar
|
||||||
)}
|
key={`${serie.id}:prev`}
|
||||||
|
name={`${serie.id}:prev`}
|
||||||
|
dataKey={`${serie.id}:prev:count`}
|
||||||
|
fill={getChartColor(serie.index)}
|
||||||
|
fillOpacity={0.3}
|
||||||
|
radius={5}
|
||||||
|
stackId={isStacked ? 'prev' : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
{series.map((serie) => {
|
||||||
|
return (
|
||||||
|
<Bar
|
||||||
|
key={serie.id}
|
||||||
|
name={serie.id}
|
||||||
|
dataKey={`${serie.id}:count`}
|
||||||
|
fill={getChartColor(serie.index)}
|
||||||
|
radius={isStacked ? 0 : 4}
|
||||||
|
fillOpacity={1}
|
||||||
|
stackId={isStacked ? 'current' : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{references.data?.map((ref) => (
|
||||||
|
<ReferenceLine
|
||||||
|
key={ref.id}
|
||||||
|
x={ref.date.getTime()}
|
||||||
|
stroke={'oklch(from var(--foreground) l c h / 0.1)'}
|
||||||
|
strokeDasharray={'3 3'}
|
||||||
|
label={{
|
||||||
|
value: ref.title,
|
||||||
|
position: 'centerTop',
|
||||||
|
fill: '#334155',
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
fontSize={10}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
{isEditMode && (
|
||||||
|
<ReportTable
|
||||||
|
data={data}
|
||||||
|
visibleSeries={series}
|
||||||
|
setVisibleSeries={setVisibleSeries}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</ChartClickMenu>
|
</ChartClickMenu>
|
||||||
</ReportChartTooltip.TooltipProvider>
|
</ReportChartTooltip.TooltipProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -361,6 +361,17 @@ export const reportSlice = createSlice({
|
|||||||
state.options.include = action.payload;
|
state.options.include = action.payload;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
changeStacked(state, action: PayloadAction<boolean>) {
|
||||||
|
state.dirty = true;
|
||||||
|
if (!state.options || state.options.type !== 'histogram') {
|
||||||
|
state.options = {
|
||||||
|
type: 'histogram',
|
||||||
|
stacked: action.payload,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
state.options.stacked = action.payload;
|
||||||
|
}
|
||||||
|
},
|
||||||
reorderEvents(
|
reorderEvents(
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ fromIndex: number; toIndex: number }>,
|
action: PayloadAction<{ fromIndex: number; toIndex: number }>,
|
||||||
@@ -406,6 +417,7 @@ export const {
|
|||||||
changeSankeySteps,
|
changeSankeySteps,
|
||||||
changeSankeyExclude,
|
changeSankeyExclude,
|
||||||
changeSankeyInclude,
|
changeSankeyInclude,
|
||||||
|
changeStacked,
|
||||||
reorderEvents,
|
reorderEvents,
|
||||||
} = reportSlice.actions;
|
} = reportSlice.actions;
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
changeSankeyInclude,
|
changeSankeyInclude,
|
||||||
changeSankeyMode,
|
changeSankeyMode,
|
||||||
changeSankeySteps,
|
changeSankeySteps,
|
||||||
|
changeStacked,
|
||||||
changeUnit,
|
changeUnit,
|
||||||
} from '../reportSlice';
|
} from '../reportSlice';
|
||||||
|
|
||||||
@@ -33,6 +34,9 @@ export function ReportSettings() {
|
|||||||
const funnelGroup = funnelOptions?.funnelGroup;
|
const funnelGroup = funnelOptions?.funnelGroup;
|
||||||
const funnelWindow = funnelOptions?.funnelWindow;
|
const funnelWindow = funnelOptions?.funnelWindow;
|
||||||
|
|
||||||
|
const histogramOptions = options?.type === 'histogram' ? options : undefined;
|
||||||
|
const stacked = histogramOptions?.stacked ?? false;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { projectId } = useAppParams();
|
const { projectId } = useAppParams();
|
||||||
const eventNames = useEventNames({ projectId });
|
const eventNames = useEventNames({ projectId });
|
||||||
@@ -61,6 +65,10 @@ export function ReportSettings() {
|
|||||||
fields.push('sankeyInclude');
|
fields.push('sankeyInclude');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chartType === 'histogram') {
|
||||||
|
fields.push('stacked');
|
||||||
|
}
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
}, [chartType]);
|
}, [chartType]);
|
||||||
|
|
||||||
@@ -259,6 +267,15 @@ export function ReportSettings() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{fields.includes('stacked') && (
|
||||||
|
<Label className="flex items-center justify-between mb-0">
|
||||||
|
<span className="whitespace-nowrap">Stack series</span>
|
||||||
|
<Switch
|
||||||
|
checked={stacked}
|
||||||
|
onCheckedChange={(val) => dispatch(changeStacked(!!val))}
|
||||||
|
/>
|
||||||
|
</Label>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -126,14 +126,21 @@ export const zSankeyOptions = z.object({
|
|||||||
include: z.array(z.string()).optional(),
|
include: z.array(z.string()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const zHistogramOptions = z.object({
|
||||||
|
type: z.literal('histogram'),
|
||||||
|
stacked: z.boolean().default(false),
|
||||||
|
});
|
||||||
|
|
||||||
export const zReportOptions = z.discriminatedUnion('type', [
|
export const zReportOptions = z.discriminatedUnion('type', [
|
||||||
zFunnelOptions,
|
zFunnelOptions,
|
||||||
zRetentionOptions,
|
zRetentionOptions,
|
||||||
zSankeyOptions,
|
zSankeyOptions,
|
||||||
|
zHistogramOptions,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type IReportOptions = z.infer<typeof zReportOptions>;
|
export type IReportOptions = z.infer<typeof zReportOptions>;
|
||||||
export type ISankeyOptions = z.infer<typeof zSankeyOptions>;
|
export type ISankeyOptions = z.infer<typeof zSankeyOptions>;
|
||||||
|
export type IHistogramOptions = z.infer<typeof zHistogramOptions>;
|
||||||
|
|
||||||
export const zWidgetType = z.enum(['realtime', 'counter']);
|
export const zWidgetType = z.enum(['realtime', 'counter']);
|
||||||
export type IWidgetType = z.infer<typeof zWidgetType>;
|
export type IWidgetType = z.infer<typeof zWidgetType>;
|
||||||
|
|||||||
Reference in New Issue
Block a user