|
1 | | -import React from "react"; |
2 | | -import { Bar, LinePath } from "@visx/shape"; |
| 1 | +import React, { useMemo } from "react"; |
| 2 | +import { Bar } from "@visx/shape"; |
3 | 3 | import { Group } from "@visx/group"; |
4 | 4 | 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"; |
9 | 5 |
|
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"; |
22 | 7 |
|
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 --- |
50 | 9 |
|
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[]; |
69 | 18 | width?: number; |
70 | 19 | height?: number; |
71 | 20 | }) => { |
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 | + ); |
92 | 33 |
|
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 | + ); |
95 | 42 |
|
96 | 43 | return ( |
97 | 44 | <svg width={width} height={height}> |
98 | 45 | <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 | + })} |
109 | 62 | </Group> |
110 | 63 | </svg> |
111 | 64 | ); |
112 | 65 | }; |
113 | 66 |
|
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 | + ); |
122 | 80 |
|
123 | | - return basePalette.map(simulator); |
| 81 | + return <BaseBarChart data={data} palette={palette} {...dims} />; |
124 | 82 | }; |
125 | 83 |
|
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], |
178 | 94 | ); |
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] }); |
192 | 95 |
|
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} />; |
208 | 97 | }; |
0 commit comments