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
138 changes: 87 additions & 51 deletions front_end/src/components/charts/continuous_area_chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type Props = {
withTodayLine?: boolean;
globalScaling?: Scaling;
outlineUser?: boolean;
centerOOBResolution?: boolean;
};

const ContinuousAreaChart: FC<Props> = ({
Expand All @@ -113,6 +114,7 @@ const ContinuousAreaChart: FC<Props> = ({
withTodayLine = true,
globalScaling,
outlineUser = false,
centerOOBResolution = false,
}) => {
const locale = useLocale();
const { ref: chartContainerRef, width: containerWidth } =
Expand Down Expand Up @@ -825,6 +827,47 @@ const ContinuousAreaChart: FC<Props> = ({
/>
))
)}

{/* Today's date dot for date questions */}
{question.type === QuestionType.Date && withTodayLine && (
<VictoryScatter
data={[
{
x: unscaleNominalLocation(
Math.floor(Date.now() / 1000),
question.scaling
),
y: yDomain[0], // Bottom of the chart
symbol: "circle",
size: 3,
},
]}
style={{
data: {
fill: getThemeColor(METAC_COLORS.blue["700"]),
stroke: "none",
},
}}
/>
)}

{question.type === QuestionType.Date &&
todayLabelPosition &&
withTodayLine && (
<VictoryPortal>
<VictoryLabel
x={todayLabelPosition.x}
y={height - BOTTOM_PADDING - 12} // Position above the dot
text="Today"
style={{
fill: getThemeColor(METAC_COLORS.blue["700"]),
fontSize: 12,
}}
textAnchor="middle"
/>
</VictoryPortal>
)}

{/* Resolution point */}
{resX != null && resPlacement === "in" && (
<VictoryScatter
Expand All @@ -849,8 +892,11 @@ const ContinuousAreaChart: FC<Props> = ({
{resX != null &&
resPlacement === "in" &&
withResolutionChip &&
(question.type === QuestionType.Discrete ||
question.type === QuestionType.Numeric) && (
[
QuestionType.Numeric,
QuestionType.Discrete,
QuestionType.Date,
].includes(question.type) && (
<VictoryScatter
data={[
{
Expand All @@ -875,6 +921,42 @@ const ContinuousAreaChart: FC<Props> = ({
/>
)}

{/* Resolution chip for out of bounds resolution */}
{resX != null &&
resPlacement !== "in" &&
withResolutionChip &&
[
QuestionType.Numeric,
QuestionType.Discrete,
QuestionType.Date,
].includes(question.type) && (
<VictoryScatter
data={[
{
x:
resPlacement === "left"
? Math.min(...xDomain)
: Math.max(...xDomain),
y: centerOOBResolution ? Math.max(...yDomain) / 2 : 0,
placement: resPlacement,
},
]}
dataComponent={
<VictoryPortal>
<ChartValueBox
rightPadding={0}
chartWidth={chartWidth}
isCursorActive={false}
isDistributionChip
colorOverride={METAC_COLORS.purple["800"]}
resolution={formattedResolution}
textAlignToSide={centerOOBResolution}
/>
</VictoryPortal>
}
/>
)}

{resX != null && resPlacement && resPlacement !== "in" && (
<VictoryPortal>
<VictoryScatter
Expand All @@ -884,62 +966,16 @@ const ContinuousAreaChart: FC<Props> = ({
resPlacement === "left"
? Math.min(...xDomain)
: Math.max(...xDomain),
y: yDomain[1] - (yDomain[1] - yDomain[0]) * 0.04,
placement: resPlacement === "left" ? "above" : "below",
y: centerOOBResolution ? Math.max(...yDomain) / 2 : 0,
placement: resPlacement,
primary: METAC_COLORS.purple["800"],
secondary: METAC_COLORS.purple["500"],
},
]}
dataComponent={
<ResolutionDiamond
hoverable={false}
axisPadPx={3}
rotateDeg={resPlacement === "left" ? 90 : -90}
refProps={{}}
/>
}
dataComponent={<ResolutionDiamond hoverable={false} />}
/>
</VictoryPortal>
)}
{/* Today's date dot for date questions */}
{question.type === QuestionType.Date && withTodayLine && (
<VictoryScatter
data={[
{
x: unscaleNominalLocation(
Math.floor(Date.now() / 1000),
question.scaling
),
y: yDomain[0], // Bottom of the chart
symbol: "circle",
size: 3,
},
]}
style={{
data: {
fill: getThemeColor(METAC_COLORS.blue["700"]),
stroke: "none",
},
}}
/>
)}

{question.type === QuestionType.Date &&
todayLabelPosition &&
withTodayLine && (
<VictoryPortal>
<VictoryLabel
x={todayLabelPosition.x}
y={height - BOTTOM_PADDING - 12} // Position above the dot
text="Today"
style={{
fill: getThemeColor(METAC_COLORS.blue["700"]),
fontSize: 12,
}}
textAnchor="middle"
/>
</VictoryPortal>
)}

{/* Manually render cursor component when cursor is on edge */}
{!isNil(cursorEdge) && (
Expand Down
75 changes: 54 additions & 21 deletions front_end/src/components/charts/fan_chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import ChartFanTooltip from "@/components/charts/primitives/chart_fan_tooltip";
import FanPoint from "@/components/charts/primitives/fan_point";
import PredictionWithRange from "@/components/charts/primitives/prediction_with_range";
import ResolutionDiamond from "@/components/charts/primitives/resolution_diamond";
import ForecastAvailabilityChartOverflow from "@/components/post_card/chart_overflow";
import { darkTheme, lightTheme } from "@/constants/chart_theme";
import { METAC_COLORS } from "@/constants/colors";
Expand Down Expand Up @@ -457,27 +458,48 @@ const FanChart: FC<Props> = ({
/>
)}

{resolutionPoints.map((point) => (
<VictoryScatter
key={`res-${point.x}`}
data={[{ ...point, symbol: "diamond" }]}
style={{
data: {
fill: v.resolutionPoint.fill({ getThemeColor }),
stroke: () => palette.resolutionStroke,
strokeWidth: 2,
strokeOpacity: 1,
},
}}
dataComponent={
<FanPoint
activePoint={null}
pointSize={v.resolutionPoint.size}
strokeWidth={v.resolutionPoint.strokeWidth}
/>
}
/>
))}
{resolutionPoints.map((point) => {
if (
point.placement &&
["below", "above"].includes(point.placement)
) {
return (
<VictoryPortal key={`res-portal-${point.x}`}>
<VictoryScatter
key={`res-${point.x}`}
data={[
{
...point,
y: point.placement === "below" ? 0 : 1,
},
]}
dataComponent={<ResolutionDiamond hoverable={false} />}
/>
</VictoryPortal>
);
}
return (
<VictoryScatter
key={`res-${point.x}`}
data={[{ ...point, symbol: "diamond" }]}
style={{
data: {
fill: v.resolutionPoint.fill({ getThemeColor }),
stroke: () => palette.resolutionStroke,
strokeWidth: 2,
strokeOpacity: 1,
},
}}
dataComponent={
<FanPoint
activePoint={null}
pointSize={v.resolutionPoint.size}
strokeWidth={v.resolutionPoint.strokeWidth}
/>
}
/>
);
})}
{emptyPoints.map((point) => (
<VictoryScatter
key={`empty-${point.x}`}
Expand Down Expand Up @@ -514,6 +536,7 @@ type FanGraphPoint = {
y: number;
resolved?: boolean;
unsuccessfullyResolved?: boolean;
placement?: "in" | "below" | "above";
};

function buildChartData({
Expand Down Expand Up @@ -593,11 +616,21 @@ function buildChartData({
? getResolutionPosition({ question: option.question, scaling })
: NaN;

const isAboveUpperBound =
option.question?.resolution === "above_upper_bound" || yVal > 1;
const isBelowLowerBound =
option.question?.resolution === "below_lower_bound" || yVal < 0;

resolutionPoints.push({
x: option.name,
y: yVal,
unsuccessfullyResolved: false,
resolved: true,
placement: isAboveUpperBound
? "above"
: isBelowLowerBound
? "below"
: "in",
});
}

Expand Down
4 changes: 2 additions & 2 deletions front_end/src/components/charts/fan_chart_variants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ export const fanVariants: Record<FanChartVariant, VariantConfig> = {
communityPoint: getThemeColor(METAC_COLORS.olive["800"]),
}),
resolutionPoint: {
size: 8,
strokeWidth: 2,
size: 10,
strokeWidth: 2.5,
fill: () => "none",
},
},
Expand Down
46 changes: 42 additions & 4 deletions front_end/src/components/charts/group_chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import ForecastAvailabilityChartOverflow from "../post_card/chart_overflow";
import ChartContainer from "./primitives/chart_container";
import ChartCursorLabel from "./primitives/chart_cursor_label";
import GroupResolutionPoint from "./primitives/group_resolution_point";
import ResolutionDiamond from "./primitives/resolution_diamond";
import XTickLabel from "./primitives/x_tick_label";

type Props = {
Expand Down Expand Up @@ -515,6 +516,31 @@ const GroupChart: FC<Props> = ({
? METAC_COLORS["mc-option-text"][1]
: color;

if (
resolutionPoint.placement &&
["below", "above"].includes(resolutionPoint.placement)
) {
return (
<VictoryPortal key={`group-resolution-portal-${index}`}>
<VictoryScatter
key={`group-resolution-${index}`}
data={[
{
x: resolutionPoint?.x,
y: resolutionPoint?.placement === "below" ? 0 : 1,
x1: resolutionPoint?.x1,
y1: resolutionPoint?.y1,
text: resolutionPoint?.text,
placement: resolutionPoint?.placement,
primary: color,
},
]}
dataComponent={<ResolutionDiamond hoverable={false} />}
/>
</VictoryPortal>
);
}

return (
<VictoryScatter
key={`group-resolution-${index}`}
Expand Down Expand Up @@ -593,6 +619,7 @@ export type ChoiceGraph = {
text?: string;
x1?: number;
y1?: number;
placement?: "in" | "below" | "above";
};
choice: string;
color: ThemeColor;
Expand Down Expand Up @@ -826,18 +853,26 @@ function buildChartData({
text,
x1: lastLineItem?.x,
y1: lastLineItem?.y ?? undefined,
placement:
resolution === "below_lower_bound"
? "below"
: resolution === "above_upper_bound"
? "above"
: "in",
};
}

if (isFinite(Number(resolution))) {
const yPos = scaling
? unscaleNominalLocation(Number(resolution), scaling)
: Number(resolution) ?? 0;
// continuous group case
item.resolutionPoint = {
x: resolveTime,
y: scaling
? unscaleNominalLocation(Number(resolution), scaling)
: Number(resolution) ?? 0,
y: yPos,
x1: lastLineItem?.x,
y1: lastLineItem?.y ?? undefined,
placement: yPos < 0 ? "below" : yPos > 1 ? "above" : "in",
};
} else if (
typeof resolution === "string" &&
Expand All @@ -851,10 +886,13 @@ function buildChartData({
resolveTime,
scaling,
});

if (dateResolution) {
const yPos = dateResolution.y ?? 0;
item.resolutionPoint = {
x: dateResolution.x,
y: dateResolution.y ?? 0,
y: yPos,
placement: yPos < 0 ? "below" : yPos > 1 ? "above" : "in",
x1: lastLineItem?.x,
y1: lastLineItem?.y ?? undefined,
};
Expand Down
Loading