Skip to content

Commit

Permalink
add linear regression confidence band
Browse files Browse the repository at this point in the history
  • Loading branch information
xvvvyz committed Sep 10, 2024
1 parent 91417d2 commit 447f7cd
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 76 deletions.
46 changes: 38 additions & 8 deletions app/_components/insight-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import Button from '@/_components/button';
import Checkbox from '@/_components/checkbox';
import IconButton from '@/_components/icon-button';
import Input from '@/_components/input';
import InsightPlot from '@/_components/insight-plot';
import PageModalBackButton from '@/_components/page-modal-back-button';
import PlotFigure from '@/_components/plot-figure';
import * as Popover from '@/_components/popover';
import Select, { IOption } from '@/_components/select';
import UnsavedChangesBanner from '@/_components/unsaved-changes-banner';
Expand All @@ -24,8 +24,8 @@ import getInputDetailsFromEvents from '@/_utilities/get-input-details-from-event
import getInsightOptionsFromEvents from '@/_utilities/get-insight-options-from-events';
import { ArrowsPointingInIcon } from '@heroicons/react/24/outline';
import AdjustmentsHorizontalIcon from '@heroicons/react/24/outline/AdjustmentsHorizontalIcon';
import EllipsisVerticalIcon from '@heroicons/react/24/outline/EllipsisVerticalIcon';
import FunnelIcon from '@heroicons/react/24/outline/FunnelIcon';
import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
import { useRouter } from 'next/navigation';
import { useTransition } from 'react';
import { Controller } from 'react-hook-form';
Expand Down Expand Up @@ -98,6 +98,8 @@ const InsightForm = ({ events, insight, subjectId }: InsightFormProps) => {
showDots: config?.showDots ?? true,
showLine: config?.showLine ?? false,
showLinearRegression: config?.showLinearRegression ?? false,
showLinearRegressionConfidence:
config?.showLinearRegressionConfidence ?? false,
type: config?.type ?? ChartType.TimeSeries,
},
});
Expand Down Expand Up @@ -181,7 +183,7 @@ const InsightForm = ({ events, insight, subjectId }: InsightFormProps) => {
/>
</div>
<div className="rounded border border-alpha-1 bg-bg-3 drop-shadow-2xl">
<PlotFigure
<InsightPlot
barInterval={form.watch('barInterval')}
barReducer={form.watch('barReducer')}
defaultHeight={200}
Expand All @@ -199,6 +201,9 @@ const InsightForm = ({ events, insight, subjectId }: InsightFormProps) => {
showDots={form.watch('showDots')}
showLine={showLine}
showLinearRegression={form.watch('showLinearRegression')}
showLinearRegressionConfidence={form.watch(
'showLinearRegressionConfidence',
)}
subjectId={subjectId}
title={form.watch('name')}
type={form.watch('type')}
Expand Down Expand Up @@ -327,7 +332,7 @@ const InsightForm = ({ events, insight, subjectId }: InsightFormProps) => {
<IconButton
className="w-full shrink p-2.5"
colorScheme="transparent"
icon={<PlusIcon className="m-0 size-5" />}
icon={<EllipsisVerticalIcon className="m-0 size-5" />}
label="Margins"
variant="primary"
/>
Expand All @@ -337,10 +342,35 @@ const InsightForm = ({ events, insight, subjectId }: InsightFormProps) => {
className="mr-0 w-96 space-y-4 p-8 pt-7"
side="top"
>
<Checkbox
label="Linear regression"
{...form.register('showLinearRegression')}
/>
<div className="flex w-full items-end">
<Checkbox
className="w-full"
inputClassName="rounded-r-none"
label="Linear regression"
{...form.register('showLinearRegression')}
/>
<Popover.Root>
<Popover.Trigger asChild>
<IconButton
className="rounded-l-none border-l-0 p-2.5"
colorScheme="transparent"
icon={<AdjustmentsHorizontalIcon className="w-5" />}
label="Linear regression settings"
variant="primary"
/>
</Popover.Trigger>
<Popover.Content
align="end"
className="mr-0 w-64 space-y-6 p-8 pt-7"
side="top"
>
<Checkbox
label="Confidence band"
{...form.register('showLinearRegressionConfidence')}
/>
</Popover.Content>
</Popover.Root>
</div>
</Popover.Content>
</Popover.Root>
<Popover.Root>
Expand Down
5 changes: 3 additions & 2 deletions app/_components/insight-page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import InsightPlot from '@/_components/insight-plot';
import * as Modal from '@/_components/modal';
import PageModalHeader from '@/_components/page-modal-header';
import PlotFigure from '@/_components/plot-figure';
import Number from '@/_constants/enum-number';
import getInsight from '@/_queries/get-insight';
import getPublicInsight from '@/_queries/get-public-insight';
Expand Down Expand Up @@ -45,7 +45,7 @@ const InsightPage = async ({
return (
<Modal.Content className="max-w-4xl bg-bg-3">
<PageModalHeader className="-mb-8" title={insight.name} />
<PlotFigure
<InsightPlot
barInterval={config.barInterval}
barReducer={config.barReducer}
events={events}
Expand All @@ -63,6 +63,7 @@ const InsightPage = async ({
showDots={config.showDots}
showLine={config.showLine}
showLinearRegression={config.showLinearRegression}
showLinearRegressionConfidence={config.showLinearRegressionConfidence}
subjectId={subjectId}
title={insight.name}
type={config.type}
Expand Down
137 changes: 73 additions & 64 deletions app/_components/plot-figure.tsx → app/_components/insight-plot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,14 @@ import { throttle } from 'lodash';
import { useRouter } from 'next/navigation';
import * as React from 'react';

const PlotFigure = ({
barInterval,
barReducer,
defaultHeight,
events,
includeEventsFrom,
includeEventsSince,
id,
inputId,
inputOptions,
isPublic,
lineCurveFunction,
marginBottom,
marginLeft,
marginRight,
marginTop,
setActiveId,
setSyncDate,
showBars,
showDots,
showLine,
showLinearRegression,
subjectId,
syncDate,
type,
}: {
interface InsightPlot {
barInterval: BarInterval;
barReducer: BarReducer;
defaultHeight?: number;
events: ListEventsData;
id?: string;
includeEventsFrom: string | null;
includeEventsSince: TimeSinceMilliseconds | null;
id?: string;
inputId: string;
inputOptions?: string[];
isPublic?: boolean;
Expand All @@ -64,11 +39,40 @@ const PlotFigure = ({
showDots: boolean;
showLine: boolean;
showLinearRegression: boolean;
showLinearRegressionConfidence?: boolean;
subjectId: string;
syncDate?: Date | null;
title: string;
type: ChartType;
}) => {
}

const InsightPlot = ({
barInterval,
barReducer,
defaultHeight,
events,
id,
includeEventsFrom,
includeEventsSince,
inputId,
inputOptions,
isPublic,
lineCurveFunction,
marginBottom,
marginLeft,
marginRight,
marginTop,
setActiveId,
setSyncDate,
showBars,
showDots,
showLine,
showLinearRegression,
showLinearRegressionConfidence,
subjectId,
syncDate,
type,
}: InsightPlot) => {
const containerRef = React.useRef<HTMLDivElement>(null);
const router = useRouter();
const { parentRef, width } = useParentSize();
Expand Down Expand Up @@ -149,7 +153,9 @@ const PlotFigure = ({
if (showLinearRegression) {
marks.push(
P.linearRegressionY(rows, {
ci: 0,
ci: showLinearRegressionConfidence ? undefined : 0,
fill: 'hsla(5, 85%, 50%, 10%)',
fillOpacity: 1,
stroke: 'hsla(5, 85%, 50%, 75%)',
x,
y,
Expand Down Expand Up @@ -258,43 +264,45 @@ const PlotFigure = ({
x: marks.length ? { type: 'time' } : undefined,
});

const onClick = () => {
try {
const datum = plot.querySelector('title')?.innerHTML ?? '';
if (!datum) return;
const { Id } = JSON.parse(datum);
if (!Id) return;
const shareOrSubjects = isPublic ? 'share' : 'subjects';
const href = `/${shareOrSubjects}/${subjectId}/events/${Id}`;
router.push(href, { scroll: false });
} catch {
// noop
}
};
if (showDots) {
const onClick = () => {
try {
const datum = plot.querySelector('title')?.innerHTML ?? '';
if (!datum) return;
const { Id } = JSON.parse(datum);
if (!Id) return;
const shareOrSubjects = isPublic ? 'share' : 'subjects';
const href = `/${shareOrSubjects}/${subjectId}/events/${Id}`;
router.push(href, { scroll: false });
} catch {
// noop
}
};

const onEnd = () => {
setActiveId?.(null);
setSyncDate?.(null);
};
const onEnd = () => {
setActiveId?.(null);
setSyncDate?.(null);
};

const onMove = throttle(() => {
try {
const datum = plot.querySelector('title')?.innerHTML ?? '';
if (!datum) return;
const { Time } = JSON.parse(datum);
setActiveId?.(id ?? null);
setSyncDate?.(Time ? new Date(Time) : null);
} catch {
// noop
}
}, 50);
const onMove = throttle(() => {
try {
const datum = plot.querySelector('title')?.innerHTML ?? '';
if (!datum) return;
const { Time } = JSON.parse(datum);
setActiveId?.(id ?? null);
setSyncDate?.(Time ? new Date(Time) : null);
} catch {
// noop
}
}, 50);

plot.addEventListener('click', onClick);
plot.addEventListener('mouseleave', onEnd);
plot.addEventListener('mousemove', onMove);
plot.addEventListener('touchcancel', onEnd);
plot.addEventListener('touchend', onEnd);
plot.addEventListener('touchmove', onMove);
plot.addEventListener('click', onClick);
plot.addEventListener('mouseleave', onEnd);
plot.addEventListener('mousemove', onMove);
plot.addEventListener('touchcancel', onEnd);
plot.addEventListener('touchend', onEnd);
plot.addEventListener('touchmove', onMove);
}

containerRef.current.append(plot);
return () => plot.remove();
Expand All @@ -310,6 +318,7 @@ const PlotFigure = ({
inputOptions,
isPublic,
lineCurveFunction,
showLinearRegressionConfidence,
marginBottom,
marginLeft,
marginRight,
Expand All @@ -334,4 +343,4 @@ const PlotFigure = ({
);
};

export default PlotFigure;
export default InsightPlot;
5 changes: 3 additions & 2 deletions app/_components/insight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Button from '@/_components/button';
import IconButton from '@/_components/icon-button';
import InsightMenu from '@/_components/insight-menu';
import PlotFigure from '@/_components/plot-figure';
import InsightPlot from '@/_components/insight-plot';
import { ListEventsData } from '@/_queries/list-events';
import { ListInsightsData } from '@/_queries/list-insights';
import { InsightConfigJson } from '@/_types/insight-config-json';
Expand Down Expand Up @@ -87,7 +87,7 @@ const Insight = ({
<InsightMenu insightId={insight.id} subjectId={subjectId} />
)}
</div>
<PlotFigure
<InsightPlot
barInterval={config.barInterval}
barReducer={config.barReducer}
defaultHeight={200}
Expand All @@ -109,6 +109,7 @@ const Insight = ({
showDots={config.showDots}
showLine={config.showLine}
showLinearRegression={config.showLinearRegression}
showLinearRegressionConfidence={config.showLinearRegressionConfidence}
subjectId={subjectId}
syncDate={insight.id === activeId ? null : syncDate}
title={insight.name}
Expand Down
1 change: 1 addition & 0 deletions app/_types/insight-config-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type InsightConfigJson = {
input: string;
inputOptions: string[];
lineCurveFunction: LineCurveFunction;
showLinearRegressionConfidence: boolean;
marginBottom: string;
marginLeft: string;
marginRight: string;
Expand Down

0 comments on commit 447f7cd

Please sign in to comment.