Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion front_end/src/components/charts/continuous_area_chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type Props = {
forceTickCount?: number; // is used on feed page
withResolutionChip?: boolean;
withTodayLine?: boolean;
outlineUser?: boolean;
};

const ContinuousAreaChart: FC<Props> = ({
Expand All @@ -106,6 +107,7 @@ const ContinuousAreaChart: FC<Props> = ({
forceTickCount,
withResolutionChip = true,
withTodayLine = true,
outlineUser = false,
}) => {
const locale = useLocale();
const { ref: chartContainerRef, width: containerWidth } =
Expand Down Expand Up @@ -529,7 +531,12 @@ const ContinuousAreaChart: FC<Props> = ({
return undefined;
}
})(),
opacity: chart.type === "user_previous" ? 0.1 : 0.3,
opacity:
outlineUser && chart.type === "user"
? 0
: chart.type === "user_previous"
? 0.1
: 0.3,
},
}}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { isNil } from "lodash";
import { FC } from "react";

import ResolutionIcon from "@/components/icons/resolution";
import Tooltip from "@/components/ui/tooltip";
import { QuestionStatus, Resolution } from "@/types/post";
import cn from "@/utils/core/cn";
import { isUnsuccessfullyResolved } from "@/utils/questions/resolution";
Expand All @@ -13,6 +16,7 @@ type Props = {
resolution: Resolution | null;
type: QuestionStatus.OPEN | QuestionStatus.CLOSED | QuestionStatus.RESOLVED;
unit?: string;
withdrawnLabel?: string;
};

const AccordionResolutionCell: FC<Props> = ({
Expand All @@ -21,6 +25,7 @@ const AccordionResolutionCell: FC<Props> = ({
userMedian,
resolution,
type,
withdrawnLabel,
}) => {
const isResolved = !isNil(resolution);
if (isResolved) {
Expand Down Expand Up @@ -54,9 +59,31 @@ const AccordionResolutionCell: FC<Props> = ({
{median}
</p>
{!!userMedian && (
<p className="m-0 text-sm leading-4 text-orange-700 dark:text-orange-700-dark">
{userMedian}
</p>
<div className="m-0 flex items-center gap-1 leading-4">
{withdrawnLabel ? (
<Tooltip
tooltipContent={withdrawnLabel}
showDelayMs={120}
placement="right-start"
tooltipClassName="z-[999] sm:ml-0 ml-[68px] -mt-4 sm:mt-0 rounded-sm px-1.5 py-0.5 text-[10px] leading-tight border-0 bg-salmon-800 dark:text-gray-0-dark text-gray-0 shadow-none dark:bg-salmon-800-dark"
>
<div className="inline-flex items-center gap-1">
<span className="text-sm font-normal text-orange-700 dark:text-orange-700-dark">
{userMedian}
</span>
<FontAwesomeIcon
className="text-salmon-800 dark:text-salmon-800-dark"
size="xs"
icon={faTriangleExclamation}
/>
</div>
</Tooltip>
) : (
<p className="m-0 text-sm text-orange-700 dark:text-orange-700-dark">
{userMedian}
</p>
)}
</div>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export type ContinuousGroupOption = {
resolution: Resolution | null;
menu?: ReactNode;
forecastExpiration?: ForecastExpirationValue;
wasWithdrawn?: boolean;
withdrawnEndTimeSec?: number | null;
};

type Props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ import {
getQuantileNumericForecastDataset,
getSliderNumericForecastDataset,
} from "@/utils/forecasts/dataset";
import { formatRelativeDate } from "@/utils/formatters/date";
import { getPredictionDisplayValue } from "@/utils/formatters/prediction";
import { formatResolution } from "@/utils/formatters/resolution";
import { computeQuartilesFromCDF } from "@/utils/math";

import { AccordionOpenButton } from "./accordion_open_button";
import { AccordionResolutionCell } from "./accordion_resolution_cell";
Expand Down Expand Up @@ -94,7 +96,19 @@ const AccordionItem: FC<PropsWithChildren<AccordionItemProps>> = memo(
actual_resolve_time: option.question.actual_resolve_time ?? null,
}
);
const userMedian = showUserPrediction
const endSec = option.withdrawnEndTimeSec;
const wasWithdrawn = endSec != null && endSec * 1000 < Date.now();
const withdrawnMedian =
wasWithdrawn && question.my_forecasts?.latest?.forecast_values
? computeQuartilesFromCDF(question.my_forecasts.latest.forecast_values)
.median
: undefined;

const withdrawnLabel = wasWithdrawn
? `Withdrawn ${formatRelativeDate(locale, new Date(endSec * 1000), { short: true })}`
: undefined;

let userMedian = showUserPrediction
? forecastInputMode === ContinuousForecastInputType.Quantile
? getPredictionDisplayValue(
option.userQuantileForecast?.find((q) => q.quantile === Quantile.q2)
Expand All @@ -113,6 +127,15 @@ const AccordionItem: FC<PropsWithChildren<AccordionItemProps>> = memo(
})
: undefined;

if (wasWithdrawn && !isDirty && withdrawnMedian != null) {
userMedian = getPredictionDisplayValue(withdrawnMedian, {
questionType: option.question.type,
scaling: option.question.scaling,
unit,
actual_resolve_time: option.question.actual_resolve_time ?? null,
});
}

const handleClick = () => {
setIsModalOpen((prev) => !prev);
};
Expand Down Expand Up @@ -171,6 +194,9 @@ const AccordionItem: FC<PropsWithChildren<AccordionItemProps>> = memo(
: undefined
}
type={type}
withdrawnLabel={
wasWithdrawn && !isDirty ? withdrawnLabel : undefined
}
/>
<div className="hidden h-full shrink-0 grow-0 items-center justify-center sm:block sm:w-[325px]">
<ContinuousAreaChart
Expand All @@ -182,6 +208,7 @@ const AccordionItem: FC<PropsWithChildren<AccordionItemProps>> = memo(
question={question}
withResolutionChip={false}
withTodayLine={false}
outlineUser={wasWithdrawn && !isDirty}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { formatValueUnit } from "@/utils/questions/units";
type Props = {
question: QuestionWithNumericForecasts;
overlayPreviousForecast?: boolean;
previousCdf?: number[];
dataset: {
cdf: number[];
pmf: number[];
Expand All @@ -38,25 +39,44 @@ type Props = {
width?: number;
showCP?: boolean;
chartTheme?: VictoryThemeDefinition;
outlineUser?: boolean;
};

const arraysAlmostEqual = (
a: ReadonlyArray<number> | null | undefined,
b: ReadonlyArray<number> | null | undefined,
eps = 1e-9
): boolean => {
if (!a || !b) return false;
if (a.length !== b.length) return false;

for (let i = 0; i < a.length; i++) {
const ai = a[i];
const bi = b[i];
if (ai === undefined || bi === undefined) return false;
if (Math.abs(ai - bi) > eps) return false;
}
return true;
};

const ContinuousPredictionChart: FC<Props> = ({
question,
overlayPreviousForecast,
previousCdf,
dataset,
graphType,
readOnly = false,
height = 300,
width = undefined,
showCP = true,
chartTheme,
outlineUser = false,
}) => {
const t = useTranslations();

const [hoverState, setHoverState] = useState<ContinuousAreaHoverState | null>(
null
);

const discreteValueOptions = getDiscreteValueOptions(question);

const cursorDisplayData = useMemo(() => {
Expand Down Expand Up @@ -107,11 +127,6 @@ const ContinuousPredictionChart: FC<Props> = ({
[question.aggregations, defaultAggMethod]
);

const myLatest = useMemo(
() => question.my_forecasts?.latest ?? null,
[question.my_forecasts]
);

const data: ContinuousAreaGraphInput = useMemo(() => {
const charts: ContinuousAreaGraphInput = [];

Expand All @@ -126,15 +141,20 @@ const ContinuousPredictionChart: FC<Props> = ({
});
}

if (overlayPreviousForecast && myLatest) {
const sameAsPrev = previousCdf
? arraysAlmostEqual(dataset.cdf, previousCdf)
: false;
const shouldShowPrev =
!!overlayPreviousForecast && !!previousCdf?.length && !sameAsPrev;
if (shouldShowPrev && previousCdf) {
charts.push({
pmf: cdfToPmf(myLatest.forecast_values),
cdf: myLatest.forecast_values,
pmf: cdfToPmf(previousCdf),
cdf: previousCdf,
type: "user_previous",
});
}

if (!readOnly || !!myLatest) {
if (!readOnly || !!previousCdf) {
charts.push({
pmf: dataset.pmf,
cdf: dataset.cdf,
Expand All @@ -149,7 +169,7 @@ const ContinuousPredictionChart: FC<Props> = ({
latestAggLatest,
question.status,
overlayPreviousForecast,
myLatest,
previousCdf,
readOnly,
dataset,
]);
Expand Down Expand Up @@ -184,6 +204,7 @@ const ContinuousPredictionChart: FC<Props> = ({
onCursorChange={handleCursorChange}
extraTheme={chartTheme}
alignChartTabs={true}
outlineUser={outlineUser}
/>
<div className="my-2 flex min-h-4 justify-center gap-2 text-xs text-gray-600 dark:text-gray-600-dark">
{cursorDisplayData && (
Expand Down
19 changes: 19 additions & 0 deletions front_end/src/components/forecast_maker/continuous_input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Props = {
};
userCdf: number[] | undefined;
userPreviousCdf: number[] | undefined;
overlayPreviousCdf?: number[] | undefined;
communityCdf: number[] | undefined;
sliderComponents: DistributionSliderComponent[];
onSliderChange: (components: DistributionSliderComponent[]) => void;
Expand All @@ -54,6 +55,10 @@ type Props = {
predictionMessage?: ReactNode;
menu?: ReactNode;
copyMenu?: ReactNode;
userPreviousLabel?: string;
userPreviousRowClassName?: string;
hideCurrentUserRow?: boolean;
outlineUser?: boolean;
};

const ContinuousInput: FC<Props> = ({
Expand All @@ -78,6 +83,11 @@ const ContinuousInput: FC<Props> = ({
predictionMessage,
menu,
copyMenu,
userPreviousLabel,
userPreviousRowClassName,
hideCurrentUserRow,
overlayPreviousCdf,
outlineUser = false,
}) => {
const { user } = useAuth();
const { hideCP } = useHideCP();
Expand Down Expand Up @@ -120,6 +130,10 @@ const ContinuousInput: FC<Props> = ({

const discrete = question.type === QuestionType.Discrete;

const derivedHideCurrentUserRow =
hideCurrentUserRow ??
(!isDirty && (!hasUserForecast || !userCdf || userCdf.length === 0));

return (
<ContinuousInputContainer
forecastInputMode={forecastInputMode}
Expand All @@ -142,9 +156,11 @@ const ContinuousInput: FC<Props> = ({
: tableGraphType
}
overlayPreviousForecast={overlayPreviousForecast}
previousCdf={overlayPreviousCdf}
question={question}
readOnly={disabled}
showCP={!user || !hideCP || !!question.resolution}
outlineUser={outlineUser}
/>

{forecastInputMode === ContinuousForecastInputType.Slider && (
Expand Down Expand Up @@ -187,6 +203,9 @@ const ContinuousInput: FC<Props> = ({
disableQuantileInput={disabled}
hasUserForecast={hasUserForecast}
forecastInputMode={forecastInputMode}
userPreviousLabel={userPreviousLabel}
userPreviousRowClassName={userPreviousRowClassName}
hideCurrentUserRow={derivedHideCurrentUserRow}
/>

{forecastInputMode === ContinuousForecastInputType.Quantile && (
Expand Down
Loading