Skip to content

Commit 3b6718c

Browse files
started chart refactor, started heatmap/likert chart for gym, added simple bars
1 parent b3a433e commit 3b6718c

File tree

19 files changed

+701
-203
lines changed

19 files changed

+701
-203
lines changed

docs/client/app/gym/charts/_components/BarController.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import SequentialBarController from "./SequentialBarController";
55
import { useState } from "react";
66
import { Button } from "@/components/ui/button";
77
import { ButtonGroup } from "@/components/ui/button-group";
8-
import { colorIssues, ColorIssues, min, max } from "./utils";
8+
import { colorIssues } from "./utils";
9+
import { type ColorIssues } from "./types";
910

1011
type ViewOptions = "divergent_bar" | "sequential_bar";
1112

@@ -16,13 +17,13 @@ export default function BarController() {
1617

1718
const increaseCount = () => {
1819
setCount((v) => {
19-
return min(v + 1, 9);
20+
return Math.min(v + 1, 9);
2021
});
2122
};
2223

2324
const decreaseCount = () => {
2425
setCount((v) => {
25-
return max(v - 1, 3);
26+
return Math.max(v - 1, 3);
2627
});
2728
};
2829
return (

docs/client/app/gym/charts/_components/ChartController.tsx

Whitespace-only changes.
Lines changed: 73 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -1,208 +1,97 @@
1-
import React from "react";
2-
import { Bar, LinePath } from "@visx/shape";
1+
import React, { useMemo } from "react";
2+
import { Bar } from "@visx/shape";
33
import { Group } from "@visx/group";
44
import { scaleBand, scaleLinear } from "@visx/scale";
5-
import { charts } from "../_data/charts/sequential/linen-pine";
6-
import { diverging } from "../_data/charts/diverging";
7-
import { sequential } from "../_data/charts/sequential";
8-
import { ColorIssues, min, max, simulatorMap } from "./utils";
95

10-
// --- DUMMY DATA ---
11-
// const data = [
12-
// { category: "A", value: 25 },
13-
// { category: "B", value: 45 },
14-
// { category: "C", value: 85 },
15-
// { category: "D", value: 65 },
16-
// { category: "E", value: 95 },
17-
// { category: "F", value: 95 },
18-
// { category: "G", value: 95 },
19-
// { category: "H", value: 95 },
20-
// { category: "I", value: 95 },
21-
// ];
6+
import { BaseChartProps } from "./types";
227

23-
const randomAmount = () => {
24-
return min(95, max(5, Math.floor(Math.random() * 100)));
25-
};
26-
27-
const getData = (count: number) => {
28-
return [
29-
{ category: "A", value: randomAmount() },
30-
{ category: "B", value: randomAmount() },
31-
{ category: "C", value: randomAmount() },
32-
{ category: "D", value: randomAmount() },
33-
{ category: "E", value: randomAmount() },
34-
{ category: "F", value: randomAmount() },
35-
{ category: "G", value: randomAmount() },
36-
{ category: "H", value: randomAmount() },
37-
{ category: "I", value: randomAmount() },
38-
{ category: "J", value: randomAmount() },
39-
{ category: "K", value: randomAmount() },
40-
{ category: "L", value: randomAmount() },
41-
].slice(0, count);
42-
};
43-
44-
const steps = 9;
45-
const palette = charts[steps]; // Grab the 5-step generated palette
46-
47-
const getDivergingPalette = (chartIdx: number, mode: ColorIssues) => {
48-
const values = Object.values(diverging);
49-
const basePalette = values[chartIdx]?.[steps];
8+
// --- SHARED COMPONENTS ---
509

51-
if (!basePalette) return [];
52-
if (mode === "default") return basePalette;
53-
54-
return basePalette.map(simulatorMap[mode]);
55-
};
56-
57-
// --- 1. BAR CHART (Sequential Stress Test) ---
58-
export const DivergingBars = (args: {
59-
paletteIdx: number;
60-
simulationMode:
61-
| "protanopia"
62-
| "protanomaly"
63-
| "deuteranopia"
64-
| "deuteranomaly"
65-
| "tritanopia"
66-
| "tritanomaly"
67-
| "default";
68-
count?: number;
10+
const BaseBarChart = ({
11+
data,
12+
palette,
13+
width = 400,
14+
height = 300,
15+
}: {
16+
data: { category: string; value: number }[];
17+
palette: string[];
6918
width?: number;
7019
height?: number;
7120
}) => {
72-
let width = 400;
73-
if (args.width) {
74-
width = args.width;
75-
}
76-
let height = 300;
77-
if (args.height) {
78-
height = args.height;
79-
}
80-
81-
const data = getData(args.count ?? 9);
82-
83-
const xScale = scaleBand({
84-
range: [0, width],
85-
domain: data.map((d) => d.category),
86-
padding: 0.2,
87-
});
88-
const yScale = scaleLinear({
89-
range: [height, 0],
90-
domain: [0, 100],
91-
});
21+
const xMax = width;
22+
const yMax = height;
23+
24+
const xScale = useMemo(
25+
() =>
26+
scaleBand({
27+
range: [0, xMax],
28+
domain: data.map((d) => d.category),
29+
padding: 0.2,
30+
}),
31+
[xMax, data],
32+
);
9233

93-
const palette = getDivergingPalette(args.paletteIdx, args.simulationMode);
94-
console.log({ palette });
34+
const yScale = useMemo(
35+
() =>
36+
scaleLinear({
37+
range: [yMax, 0],
38+
domain: [0, 100],
39+
}),
40+
[yMax],
41+
);
9542

9643
return (
9744
<svg width={width} height={height}>
9845
<Group>
99-
{data.map((d, i) => (
100-
<Bar
101-
key={d.category}
102-
x={xScale(d.category)}
103-
y={yScale(d.value)}
104-
width={xScale.bandwidth()}
105-
height={height - yScale(d.value)}
106-
fill={palette![i]} // Testing step-to-step contrast
107-
/>
108-
))}
46+
{data.map((d, i) => {
47+
const barHeight = yMax - yScale(d.value);
48+
// Safety fallback to grey if palette runs out
49+
const barColor = palette[i] || "#cccccc";
50+
51+
return (
52+
<Bar
53+
key={d.category}
54+
x={xScale(d.category)}
55+
y={yMax - barHeight}
56+
width={xScale.bandwidth()}
57+
height={barHeight}
58+
fill={barColor}
59+
/>
60+
);
61+
})}
10962
</Group>
11063
</svg>
11164
);
11265
};
11366

114-
const getSequentialPalette = (chartIdx: number, mode: ColorIssues) => {
115-
const values = Object.values(sequential);
116-
const basePalette = values[chartIdx]?.[steps];
117-
118-
if (!basePalette) return [];
119-
if (mode === "default") return basePalette;
120-
121-
const simulator = simulatorMap[mode];
67+
// --- EXPORTED CHART WRAPPERS ---
68+
69+
export const DivergingBars = ({
70+
count = 9,
71+
paletteIdx,
72+
simulationMode,
73+
...dims
74+
}: BaseChartProps) => {
75+
const data = useMemo(() => getData(count), [count]);
76+
const palette = useMemo(
77+
() => getSimulatedPalette(diverging, paletteIdx, count, simulationMode),
78+
[paletteIdx, count, simulationMode],
79+
);
12280

123-
return basePalette.map(simulator);
81+
return <BaseBarChart data={data} palette={palette} {...dims} />;
12482
};
12583

126-
export const SequentialBars = (args: {
127-
paletteIdx: number;
128-
simulationMode:
129-
| "protanopia"
130-
| "protanomaly"
131-
| "deuteranopia"
132-
| "deuteranomaly"
133-
| "tritanopia"
134-
| "tritanomaly"
135-
| "default";
136-
count?: number;
137-
width?: number;
138-
height?: number;
139-
}) => {
140-
let width = 400;
141-
if (args.width) {
142-
width = args.width;
143-
}
144-
let height = 300;
145-
if (args.height) {
146-
height = args.height;
147-
}
148-
149-
const data = getData(args?.count ?? 9);
150-
151-
const xScale = scaleBand({
152-
range: [0, width],
153-
domain: data.map((d) => d.category),
154-
padding: 0.2,
155-
});
156-
const yScale = scaleLinear({
157-
range: [height, 0],
158-
domain: [0, 100],
159-
});
160-
161-
const palette = getSequentialPalette(args.paletteIdx, args.simulationMode);
162-
163-
return (
164-
<svg width={width} height={height}>
165-
<Group>
166-
{data.map((d, i) => (
167-
<Bar
168-
key={d.category}
169-
x={xScale(d.category)}
170-
y={yScale(d.value)}
171-
width={xScale.bandwidth()}
172-
height={height - yScale(d.value)}
173-
fill={palette![i]} // Testing step-to-step contrast
174-
/>
175-
))}
176-
</Group>
177-
</svg>
84+
export const SequentialBars = ({
85+
count = 9,
86+
paletteIdx,
87+
simulationMode,
88+
...dims
89+
}: BaseChartProps) => {
90+
const data = useMemo(() => getData(count), [count]);
91+
const palette = useMemo(
92+
() => getSimulatedPalette(sequential, paletteIdx, count, simulationMode),
93+
[paletteIdx, count, simulationMode],
17894
);
179-
};
180-
181-
// --- 2. MULTI-LINE CHART (Distinguishability Test) ---
182-
export const QualitativeLines = ({ width = 400, height = 300 }) => {
183-
// Imagine these are 3 different "series"
184-
const series = [
185-
[10, 40, 30, 70, 50],
186-
[20, 60, 45, 90, 80],
187-
[5, 15, 25, 35, 45],
188-
];
189-
190-
const xScale = scaleLinear({ range: [0, width], domain: [0, 4] });
191-
const yScale = scaleLinear({ range: [height, 0], domain: [0, 100] });
19295

193-
return (
194-
<svg width={width} height={height}>
195-
{series.map((points, i) => (
196-
<LinePath
197-
key={i}
198-
data={points}
199-
x={(_, index) => xScale(index)}
200-
y={(d) => yScale(d)}
201-
stroke={palette[i * 2]} // Skip steps to test high-contrast gaps
202-
strokeWidth={3}
203-
curve={undefined}
204-
/>
205-
))}
206-
</svg>
207-
);
96+
return <BaseBarChart data={data} palette={palette} {...dims} />;
20897
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use client";
2+
import { createContext, useContext, useState } from "react";
3+
import { type ColorIssues } from "../types";
4+
import { colorIssues } from "../utils";
5+
import { type State } from "./types";
6+
import { getData, getSimulatedPalette } from "../utils";
7+
8+
type Palette = string[];
9+
10+
export interface BarValues {
11+
palette: State<Palette> | null;
12+
count: State<number> | null;
13+
tools: {
14+
palette: {
15+
next: () => void;
16+
prev: () => void;
17+
};
18+
count: {
19+
inc: () => void;
20+
dec: () => void;
21+
};
22+
};
23+
}
24+
25+
export const BarContext = createContext<BarValues>({
26+
palette: null,
27+
count: null,
28+
tools: {
29+
palette: {
30+
next: () => {},
31+
prev: () => {},
32+
},
33+
count: {
34+
inc: () => {},
35+
dec: () => {},
36+
},
37+
},
38+
});
39+
40+
export const useBarContext = () => {
41+
const context = useContext(BarContext);
42+
if (!context) {
43+
throw new Error("useBarContext must be used within a BarContext Provider");
44+
}
45+
};
46+
47+
export function BarProvider(args: { children: React.ReactNode }) {
48+
const [currentPalette, set_currentPalette] = useState<Palette>();
49+
const [currentSimMode, set_currentSimMode] = useState<ColorIssues>("default");
50+
51+
const state = {
52+
chart: {
53+
get: currentBar,
54+
set: set_currentBar,
55+
},
56+
simMode: {
57+
get: currentSimMode,
58+
set: set_currentSimMode,
59+
},
60+
simModes: colorIssues,
61+
};
62+
63+
return (
64+
<BarContext.Provider value={state}>{args.children}</BarContext.Provider>
65+
);
66+
}

0 commit comments

Comments
 (0)