Skip to content
Merged
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
4 changes: 4 additions & 0 deletions front_end/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,10 @@
"parentBackgroundInfo": "Parent Question Background Info",
"childBackgroundInfo": "Child Question Background Info",
"histogram": "Histogram",
"frequency": "Frequency",
"brierScoreForPlayer":"Brier scores for player predictions",
"scoreHistogram": "Score Histogram",
"scoreScatterPlot": "Score Scatter Plot",
"expand": "Expand",
"collapse": "Collapse",
"resolutionCriteria": "Resolution Criteria",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import { format } from "date-fns";
import { useTranslations } from "next-intl";
import { FC } from "react";

import CalibrationChart from "@/app/(main)/charts/calibration_chart";
import UserHistogram from "@/app/(main)/charts/user_histogram";
import { UserProfile } from "@/types/users";

const TrackRecord: FC<{ profile: UserProfile }> = ({ profile }) => {
Expand All @@ -15,6 +15,16 @@ const TrackRecord: FC<{ profile: UserProfile }> = ({ profile }) => {
return (
<div className="flex flex-col gap-4 md:gap-6">
<div className="flex flex-col rounded bg-white p-6 dark:bg-blue-900">
<h3 className="my-0 py-0 text-gray-700 dark:text-gray-300">
{t("scoreHistogram")}
</h3>
{profile.score_histogram && (
<UserHistogram
rawHistogramData={profile.score_histogram}
color="gray"
/>
)}

<h3 className="my-0 py-0 text-gray-700 dark:text-gray-300">
Calibration Curve
</h3>
Expand Down
1 change: 0 additions & 1 deletion front_end/src/app/(main)/charts/calibration_chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const CalibrationChart: React.FC<{ data: any; showIntervals?: boolean }> = ({
const { theme, getThemeColor } = useAppTheme();
const chartTheme = theme === "dark" ? darkTheme : lightTheme;
const actualTheme = merge({}, chartTheme);
console.log(actualTheme.axis?.style?.axis?.stroke);

return (
<div className="mb-5 size-full">
Expand Down
155 changes: 155 additions & 0 deletions front_end/src/app/(main)/charts/user_histogram.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"use client";

import { range } from "lodash";
import { useTranslations } from "next-intl";
import React from "react";
import {
VictoryAxis,
VictoryChart,
VictoryContainer,
VictoryArea,
} from "victory";

import { darkTheme, lightTheme } from "@/constants/chart_theme";
import useAppTheme from "@/hooks/use_app_theme";

type HistogramProps = {
rawHistogramData: {
bin_start: number;
bin_end: number;
pct_scores: number;
}[];
color: string;
};

const UserHistogram: React.FC<HistogramProps> = ({
rawHistogramData,
color,
}) => {
const histogramData = mapHistogramData(rawHistogramData);
const t = useTranslations();
const { theme } = useAppTheme();
const chartTheme = theme === "dark" ? darkTheme : lightTheme;

const { ticks: ticksY, tickFormat: ticksYFormat } = generateTicksY(180);
return (
<div className="mb-5 size-full">
<VictoryChart
theme={chartTheme}
domain={{
x: [
rawHistogramData[0].bin_start ?? 1,
rawHistogramData[rawHistogramData.length - 1].bin_end ?? 0,
],
y: [0, 1],
}}
containerComponent={<VictoryContainer responsive={true} />}
padding={{ top: 20, bottom: 65, left: 40, right: 20 }}
height={180}
>
<VictoryAxis
dependentAxis
domain={{
x: [
rawHistogramData[0].bin_start ?? 1,
rawHistogramData[rawHistogramData.length - 1].bin_end ?? 0,
],
y: [0, 1],
}}
offsetX={40}
tickValues={ticksY}
tickFormat={ticksYFormat}
style={{
tickLabels: {
fontSize: 5,
},
axisLabel: {
fontSize: 6.5,
},
axis: { stroke: chartTheme.axis?.style?.axis?.stroke },
}}
label={t("frequency")}
/>
<VictoryAxis
tickValues={range(
rawHistogramData[0].bin_start,
rawHistogramData[rawHistogramData.length - 1].bin_end + 70,
70
)}
style={{
tickLabels: {
fontSize: 5,
},
axisLabel: {
fontSize: 6.5,
},
axis: { stroke: chartTheme.axis?.style?.axis?.stroke },
grid: { stroke: "none" },
}}
label={t("brierScoreForPlayer")}
/>
<VictoryArea
data={histogramData}
style={{
data: {
strokeWidth: 1.5,
fill: "light" + color,
stroke: "dark" + color,
},
}}
interpolation="stepAfter"
/>
</VictoryChart>
</div>
);
};

const mapHistogramData = (
userHistogram: {
bin_start: number;
bin_end: number;
pct_scores: number;
}[]
) => {
const mappedArray = [] as { x: number; y: number }[];
userHistogram.forEach((data, index) => {
mappedArray.push(
...[
{ x: data.bin_start, y: Math.max(data.pct_scores, 0) },
{ x: data.bin_end - 1, y: Math.max(data.pct_scores, 0) },
]
);
});
return mappedArray;
};

function generateTicksY(height: number) {
const desiredMajorTicks = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
const minorTicksPerMajor = 9;
const desiredMajorTickDistance = 20;
let majorTicks = desiredMajorTicks;
const maxMajorTicks = Math.floor(height / desiredMajorTickDistance);

if (maxMajorTicks < desiredMajorTicks.length) {
const step = 1 / (maxMajorTicks - 1);
majorTicks = Array.from({ length: maxMajorTicks }, (_, i) => i * step);
}
const ticks = [];
for (let i = 0; i < majorTicks.length - 1; i++) {
ticks.push(majorTicks[i]);
const step = (majorTicks[i + 1] - majorTicks[i]) / (minorTicksPerMajor + 1);
for (let j = 1; j <= minorTicksPerMajor; j++) {
ticks.push(majorTicks[i] + step * j);
}
}
ticks.push(majorTicks[majorTicks.length - 1]);
const tickFormat = (value: number): string => {
if (!majorTicks.includes(value)) {
return "";
}
return value.toString();
};
return { ticks, tickFormat };
}

export default UserHistogram;
7 changes: 6 additions & 1 deletion front_end/src/types/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ export type User = {

export type UserProfile = User & {
calibration_curve?: any;
score_histogram?: any;
score_histogram?: {
bin_start: number;
bin_end: number;
pct_scores: number;
}[];
score_scatter_plot?: { score: number; score_timestamp: number }[];
nr_forecasts?: number;
nr_comments?: number;
avg_score?: number;
Expand Down