diff --git a/front_end/messages/en.json b/front_end/messages/en.json
index 8330cf2b25..2de8873d0c 100644
--- a/front_end/messages/en.json
+++ b/front_end/messages/en.json
@@ -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",
diff --git a/front_end/src/app/(main)/accounts/profile/components/track_record.tsx b/front_end/src/app/(main)/accounts/profile/components/track_record.tsx
index 221eec1e64..f4e463bf70 100644
--- a/front_end/src/app/(main)/accounts/profile/components/track_record.tsx
+++ b/front_end/src/app/(main)/accounts/profile/components/track_record.tsx
@@ -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 }) => {
@@ -15,6 +15,16 @@ const TrackRecord: FC<{ profile: UserProfile }> = ({ profile }) => {
return (
+
+ {t("scoreHistogram")}
+
+ {profile.score_histogram && (
+
+ )}
+
Calibration Curve
diff --git a/front_end/src/app/(main)/charts/calibration_chart.tsx b/front_end/src/app/(main)/charts/calibration_chart.tsx
index 7d6096a638..fff596fb85 100644
--- a/front_end/src/app/(main)/charts/calibration_chart.tsx
+++ b/front_end/src/app/(main)/charts/calibration_chart.tsx
@@ -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 (
diff --git a/front_end/src/app/(main)/charts/user_histogram.tsx b/front_end/src/app/(main)/charts/user_histogram.tsx
new file mode 100644
index 0000000000..efd94c4677
--- /dev/null
+++ b/front_end/src/app/(main)/charts/user_histogram.tsx
@@ -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
= ({
+ 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 (
+
+ }
+ padding={{ top: 20, bottom: 65, left: 40, right: 20 }}
+ height={180}
+ >
+
+
+
+
+
+ );
+};
+
+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;
diff --git a/front_end/src/types/users.ts b/front_end/src/types/users.ts
index ab176bd781..a651332822 100644
--- a/front_end/src/types/users.ts
+++ b/front_end/src/types/users.ts
@@ -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;