react-muscle-stats 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/dist/components/MuscleRadar/index.cjs.js +84 -0
- package/dist/components/MuscleRadar/index.cjs.js.map +1 -0
- package/dist/components/MuscleRadar/index.d.ts +43 -0
- package/dist/components/MuscleRadar/index.d.ts.map +1 -0
- package/dist/components/MuscleRadar/index.es.js +83 -0
- package/dist/components/MuscleRadar/index.es.js.map +1 -0
- package/dist/components/MuscleRadar/normalization.cjs.js +100 -0
- package/dist/components/MuscleRadar/normalization.cjs.js.map +1 -0
- package/dist/components/MuscleRadar/normalization.d.ts +21 -0
- package/dist/components/MuscleRadar/normalization.d.ts.map +1 -0
- package/dist/components/MuscleRadar/normalization.es.js +99 -0
- package/dist/components/MuscleRadar/normalization.es.js.map +1 -0
- package/dist/components/MuscleVisualizer/index.cjs.js +49 -0
- package/dist/components/MuscleVisualizer/index.cjs.js.map +1 -0
- package/dist/components/MuscleVisualizer/index.d.ts +32 -0
- package/dist/components/MuscleVisualizer/index.d.ts.map +1 -0
- package/dist/components/MuscleVisualizer/index.es.js +48 -0
- package/dist/components/MuscleVisualizer/index.es.js.map +1 -0
- package/dist/components/MuscleVisualizer/svgs.cjs.js +2274 -0
- package/dist/components/MuscleVisualizer/svgs.cjs.js.map +1 -0
- package/dist/components/MuscleVisualizer/svgs.d.ts +2 -0
- package/dist/components/MuscleVisualizer/svgs.d.ts.map +1 -0
- package/dist/components/MuscleVisualizer/svgs.es.js +2274 -0
- package/dist/components/MuscleVisualizer/svgs.es.js.map +1 -0
- package/dist/components/OneRepMaxChart/index.cjs.js +132 -0
- package/dist/components/OneRepMaxChart/index.cjs.js.map +1 -0
- package/dist/components/OneRepMaxChart/index.d.ts +30 -0
- package/dist/components/OneRepMaxChart/index.d.ts.map +1 -0
- package/dist/components/OneRepMaxChart/index.es.js +127 -0
- package/dist/components/OneRepMaxChart/index.es.js.map +1 -0
- package/dist/components/VolumeChart/index.cjs.js +155 -0
- package/dist/components/VolumeChart/index.cjs.js.map +1 -0
- package/dist/components/VolumeChart/index.d.ts +30 -0
- package/dist/components/VolumeChart/index.d.ts.map +1 -0
- package/dist/components/VolumeChart/index.es.js +150 -0
- package/dist/components/VolumeChart/index.es.js.map +1 -0
- package/dist/index.cjs.js +16 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +8 -0
- package/dist/utils/math.cjs.js +16 -0
- package/dist/utils/math.cjs.js.map +1 -0
- package/dist/utils/math.d.ts +11 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/math.es.js +16 -0
- package/dist/utils/math.es.js.map +1 -0
- package/package.json +87 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_normalization = require("./normalization.cjs.js");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
let recharts = require("recharts");
|
|
6
|
+
/**
|
|
7
|
+
* A Recharts-powered radar / spider chart that normalizes raw body
|
|
8
|
+
* measurements into development scores (0–100) and plots them on a
|
|
9
|
+
* radial grid. Optionally overlays a second data set for comparison.
|
|
10
|
+
*/
|
|
11
|
+
function MuscleRadar({ gender, measurements, compareMeasurements, color = "#8884d8", compareColor = "#82ca9d", label = "Current", compareLabel = "Comparison", width = "100%", height = 400, style, className, showTooltip = true, showLegend = true, fillOpacity = .35 }) {
|
|
12
|
+
const data = (0, react.useMemo)(() => {
|
|
13
|
+
const standards = require_normalization.MUSCLE_STANDARDS[gender];
|
|
14
|
+
if (!standards) return [];
|
|
15
|
+
return Object.keys(standards).map((muscle) => {
|
|
16
|
+
const { min, max } = standards[muscle];
|
|
17
|
+
const rawValue = measurements[muscle] ?? min;
|
|
18
|
+
const point = {
|
|
19
|
+
muscle,
|
|
20
|
+
score: require_normalization.calculateDevelopmentScore(rawValue, min, max),
|
|
21
|
+
rawValue
|
|
22
|
+
};
|
|
23
|
+
if (compareMeasurements) {
|
|
24
|
+
const compareRaw = compareMeasurements[muscle] ?? min;
|
|
25
|
+
point.compareScore = require_normalization.calculateDevelopmentScore(compareRaw, min, max);
|
|
26
|
+
point.compareRawValue = compareRaw;
|
|
27
|
+
}
|
|
28
|
+
return point;
|
|
29
|
+
});
|
|
30
|
+
}, [
|
|
31
|
+
gender,
|
|
32
|
+
measurements,
|
|
33
|
+
compareMeasurements
|
|
34
|
+
]);
|
|
35
|
+
if (data.length === 0) return null;
|
|
36
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
37
|
+
className,
|
|
38
|
+
style: {
|
|
39
|
+
width,
|
|
40
|
+
...style
|
|
41
|
+
},
|
|
42
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.ResponsiveContainer, {
|
|
43
|
+
width: "100%",
|
|
44
|
+
height,
|
|
45
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(recharts.RadarChart, {
|
|
46
|
+
cx: "50%",
|
|
47
|
+
cy: "50%",
|
|
48
|
+
outerRadius: "80%",
|
|
49
|
+
data,
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.PolarGrid, {}),
|
|
52
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.PolarAngleAxis, {
|
|
53
|
+
dataKey: "muscle",
|
|
54
|
+
tick: { fontSize: 12 }
|
|
55
|
+
}),
|
|
56
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.PolarRadiusAxis, {
|
|
57
|
+
angle: 90,
|
|
58
|
+
domain: [0, 100],
|
|
59
|
+
tick: { fontSize: 10 }
|
|
60
|
+
}),
|
|
61
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Radar, {
|
|
62
|
+
name: label,
|
|
63
|
+
dataKey: "score",
|
|
64
|
+
stroke: color,
|
|
65
|
+
fill: color,
|
|
66
|
+
fillOpacity
|
|
67
|
+
}),
|
|
68
|
+
compareMeasurements && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Radar, {
|
|
69
|
+
name: compareLabel,
|
|
70
|
+
dataKey: "compareScore",
|
|
71
|
+
stroke: compareColor,
|
|
72
|
+
fill: compareColor,
|
|
73
|
+
fillOpacity
|
|
74
|
+
}),
|
|
75
|
+
showTooltip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Tooltip, {}),
|
|
76
|
+
showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(recharts.Legend, {})
|
|
77
|
+
]
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
exports.MuscleRadar = MuscleRadar;
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","names":[],"sources":["../../../src/components/MuscleRadar/index.tsx"],"sourcesContent":["import { useMemo, type CSSProperties } from 'react';\r\nimport {\r\n Radar,\r\n RadarChart,\r\n PolarGrid,\r\n PolarAngleAxis,\r\n PolarRadiusAxis,\r\n ResponsiveContainer,\r\n Tooltip,\r\n Legend,\r\n} from 'recharts';\r\nimport {\r\n MUSCLE_STANDARDS,\r\n calculateDevelopmentScore,\r\n type Gender,\r\n} from './normalization';\r\n\r\nexport interface BodyMeasurements {\r\n /** Measurement values in inches, keyed by muscle/body-part name. */\r\n [muscle: string]: number;\r\n}\r\n\r\nexport interface MuscleRadarProps {\r\n /** Gender – determines which min/max standards are used. */\r\n gender: Gender;\r\n /** Raw body measurements in inches. Keys should match `MUSCLE_STANDARDS` keys. */\r\n measurements: BodyMeasurements;\r\n /** Optional second data set to overlay (e.g. goals or previous measurements). */\r\n compareMeasurements?: BodyMeasurements;\r\n /** Fill colour for the primary radar area (default: `'#8884d8'`). */\r\n color?: string;\r\n /** Fill colour for the comparison radar area (default: `'#82ca9d'`). */\r\n compareColor?: string;\r\n /** Label for the primary data set shown in the legend (default: `'Current'`). */\r\n label?: string;\r\n /** Label for the comparison data set (default: `'Comparison'`). */\r\n compareLabel?: string;\r\n /** Width of the chart container (default: `'100%'`). */\r\n width?: string | number;\r\n /** Height of the chart container (default: `400`). */\r\n height?: number;\r\n /** Optional inline styles applied to the outer wrapper. */\r\n style?: CSSProperties;\r\n /** Optional CSS class name for the outer wrapper. */\r\n className?: string;\r\n /** Whether to show the tooltip on hover (default: `true`). */\r\n showTooltip?: boolean;\r\n /** Whether to show the legend (default: `true`). */\r\n showLegend?: boolean;\r\n /** Fill opacity for radar areas (default: `0.35`). */\r\n fillOpacity?: number;\r\n}\r\n\r\ninterface RadarDataPoint {\r\n muscle: string;\r\n score: number;\r\n rawValue: number;\r\n compareScore?: number;\r\n compareRawValue?: number;\r\n}\r\n\r\n/**\r\n * A Recharts-powered radar / spider chart that normalizes raw body\r\n * measurements into development scores (0–100) and plots them on a\r\n * radial grid. Optionally overlays a second data set for comparison.\r\n */\r\nexport function MuscleRadar({\r\n gender,\r\n measurements,\r\n compareMeasurements,\r\n color = '#8884d8',\r\n compareColor = '#82ca9d',\r\n label = 'Current',\r\n compareLabel = 'Comparison',\r\n width = '100%',\r\n height = 400,\r\n style,\r\n className,\r\n showTooltip = true,\r\n showLegend = true,\r\n fillOpacity = 0.35,\r\n}: MuscleRadarProps) {\r\n const data = useMemo<RadarDataPoint[]>(() => {\r\n const standards = MUSCLE_STANDARDS[gender];\r\n if (!standards) return [];\r\n\r\n return Object.keys(standards).map((muscle) => {\r\n const { min, max } = standards[muscle];\r\n const rawValue = measurements[muscle] ?? min;\r\n const score = calculateDevelopmentScore(rawValue, min, max);\r\n\r\n const point: RadarDataPoint = { muscle, score, rawValue };\r\n\r\n if (compareMeasurements) {\r\n const compareRaw = compareMeasurements[muscle] ?? min;\r\n point.compareScore = calculateDevelopmentScore(compareRaw, min, max);\r\n point.compareRawValue = compareRaw;\r\n }\r\n\r\n return point;\r\n });\r\n }, [gender, measurements, compareMeasurements]);\r\n\r\n if (data.length === 0) return null;\r\n\r\n return (\r\n <div className={className} style={{ width, ...style }}>\r\n <ResponsiveContainer width=\"100%\" height={height}>\r\n <RadarChart cx=\"50%\" cy=\"50%\" outerRadius=\"80%\" data={data}>\r\n <PolarGrid />\r\n <PolarAngleAxis\r\n dataKey=\"muscle\"\r\n tick={{ fontSize: 12 }}\r\n />\r\n <PolarRadiusAxis\r\n angle={90}\r\n domain={[0, 100]}\r\n tick={{ fontSize: 10 }}\r\n />\r\n\r\n <Radar\r\n name={label}\r\n dataKey=\"score\"\r\n stroke={color}\r\n fill={color}\r\n fillOpacity={fillOpacity}\r\n />\r\n\r\n {compareMeasurements && (\r\n <Radar\r\n name={compareLabel}\r\n dataKey=\"compareScore\"\r\n stroke={compareColor}\r\n fill={compareColor}\r\n fillOpacity={fillOpacity}\r\n />\r\n )}\r\n\r\n {showTooltip && <Tooltip />}\r\n {showLegend && <Legend />}\r\n </RadarChart>\r\n </ResponsiveContainer>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";;;;;;;;;;AAkEA,SAAgB,YAAY,EAC1B,QACA,cACA,qBACA,QAAQ,WACR,eAAe,WACf,QAAQ,WACR,eAAe,cACf,QAAQ,QACR,SAAS,KACT,OACA,WACA,cAAc,MACd,aAAa,MACb,cAAc,OACK;CACnB,MAAM,QAAA,GAAA,MAAA,eAAuC;EAC3C,MAAM,YAAY,sBAAA,iBAAiB;AACnC,MAAI,CAAC,UAAW,QAAO,EAAE;AAEzB,SAAO,OAAO,KAAK,UAAU,CAAC,KAAK,WAAW;GAC5C,MAAM,EAAE,KAAK,QAAQ,UAAU;GAC/B,MAAM,WAAW,aAAa,WAAW;GAGzC,MAAM,QAAwB;IAAE;IAAQ,OAF1B,sBAAA,0BAA0B,UAAU,KAAK,IAAI;IAEZ;IAAU;AAEzD,OAAI,qBAAqB;IACvB,MAAM,aAAa,oBAAoB,WAAW;AAClD,UAAM,eAAe,sBAAA,0BAA0B,YAAY,KAAK,IAAI;AACpE,UAAM,kBAAkB;;AAG1B,UAAO;IACP;IACD;EAAC;EAAQ;EAAc;EAAoB,CAAC;AAE/C,KAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EAAgB;EAAW,OAAO;GAAE;GAAO,GAAG;GAAO;YACnD,iBAAA,GAAA,kBAAA,KAAC,SAAA,qBAAD;GAAqB,OAAM;GAAe;aACxC,iBAAA,GAAA,kBAAA,MAAC,SAAA,YAAD;IAAY,IAAG;IAAM,IAAG;IAAM,aAAY;IAAY;cAAtD;KACE,iBAAA,GAAA,kBAAA,KAAC,SAAA,WAAD,EAAa,CAAA;KACb,iBAAA,GAAA,kBAAA,KAAC,SAAA,gBAAD;MACE,SAAQ;MACR,MAAM,EAAE,UAAU,IAAI;MACtB,CAAA;KACF,iBAAA,GAAA,kBAAA,KAAC,SAAA,iBAAD;MACE,OAAO;MACP,QAAQ,CAAC,GAAG,IAAI;MAChB,MAAM,EAAE,UAAU,IAAI;MACtB,CAAA;KAEF,iBAAA,GAAA,kBAAA,KAAC,SAAA,OAAD;MACE,MAAM;MACN,SAAQ;MACR,QAAQ;MACR,MAAM;MACO;MACb,CAAA;KAED,uBACC,iBAAA,GAAA,kBAAA,KAAC,SAAA,OAAD;MACE,MAAM;MACN,SAAQ;MACR,QAAQ;MACR,MAAM;MACO;MACb,CAAA;KAGH,eAAe,iBAAA,GAAA,kBAAA,KAAC,SAAA,SAAD,EAAW,CAAA;KAC1B,cAAc,iBAAA,GAAA,kBAAA,KAAC,SAAA,QAAD,EAAU,CAAA;KACd;;GACO,CAAA;EAClB,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
import { Gender } from './normalization';
|
|
3
|
+
export interface BodyMeasurements {
|
|
4
|
+
/** Measurement values in inches, keyed by muscle/body-part name. */
|
|
5
|
+
[muscle: string]: number;
|
|
6
|
+
}
|
|
7
|
+
export interface MuscleRadarProps {
|
|
8
|
+
/** Gender – determines which min/max standards are used. */
|
|
9
|
+
gender: Gender;
|
|
10
|
+
/** Raw body measurements in inches. Keys should match `MUSCLE_STANDARDS` keys. */
|
|
11
|
+
measurements: BodyMeasurements;
|
|
12
|
+
/** Optional second data set to overlay (e.g. goals or previous measurements). */
|
|
13
|
+
compareMeasurements?: BodyMeasurements;
|
|
14
|
+
/** Fill colour for the primary radar area (default: `'#8884d8'`). */
|
|
15
|
+
color?: string;
|
|
16
|
+
/** Fill colour for the comparison radar area (default: `'#82ca9d'`). */
|
|
17
|
+
compareColor?: string;
|
|
18
|
+
/** Label for the primary data set shown in the legend (default: `'Current'`). */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** Label for the comparison data set (default: `'Comparison'`). */
|
|
21
|
+
compareLabel?: string;
|
|
22
|
+
/** Width of the chart container (default: `'100%'`). */
|
|
23
|
+
width?: string | number;
|
|
24
|
+
/** Height of the chart container (default: `400`). */
|
|
25
|
+
height?: number;
|
|
26
|
+
/** Optional inline styles applied to the outer wrapper. */
|
|
27
|
+
style?: CSSProperties;
|
|
28
|
+
/** Optional CSS class name for the outer wrapper. */
|
|
29
|
+
className?: string;
|
|
30
|
+
/** Whether to show the tooltip on hover (default: `true`). */
|
|
31
|
+
showTooltip?: boolean;
|
|
32
|
+
/** Whether to show the legend (default: `true`). */
|
|
33
|
+
showLegend?: boolean;
|
|
34
|
+
/** Fill opacity for radar areas (default: `0.35`). */
|
|
35
|
+
fillOpacity?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A Recharts-powered radar / spider chart that normalizes raw body
|
|
39
|
+
* measurements into development scores (0–100) and plots them on a
|
|
40
|
+
* radial grid. Optionally overlays a second data set for comparison.
|
|
41
|
+
*/
|
|
42
|
+
export declare function MuscleRadar({ gender, measurements, compareMeasurements, color, compareColor, label, compareLabel, width, height, style, className, showTooltip, showLegend, fillOpacity, }: MuscleRadarProps): import("react/jsx-runtime").JSX.Element | null;
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/MuscleRadar/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAWpD,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,YAAY,EAAE,gBAAgB,CAAC;IAC/B,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,gBAAgB,CAAC;IACvC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAUD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,KAAiB,EACjB,YAAwB,EACxB,KAAiB,EACjB,YAA2B,EAC3B,KAAc,EACd,MAAY,EACZ,KAAK,EACL,SAAS,EACT,WAAkB,EAClB,UAAiB,EACjB,WAAkB,GACnB,EAAE,gBAAgB,kDA+DlB"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { MUSCLE_STANDARDS, calculateDevelopmentScore } from "./normalization.es.js";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { Legend, PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, ResponsiveContainer, Tooltip } from "recharts";
|
|
5
|
+
/**
|
|
6
|
+
* A Recharts-powered radar / spider chart that normalizes raw body
|
|
7
|
+
* measurements into development scores (0–100) and plots them on a
|
|
8
|
+
* radial grid. Optionally overlays a second data set for comparison.
|
|
9
|
+
*/
|
|
10
|
+
function MuscleRadar({ gender, measurements, compareMeasurements, color = "#8884d8", compareColor = "#82ca9d", label = "Current", compareLabel = "Comparison", width = "100%", height = 400, style, className, showTooltip = true, showLegend = true, fillOpacity = .35 }) {
|
|
11
|
+
const data = useMemo(() => {
|
|
12
|
+
const standards = MUSCLE_STANDARDS[gender];
|
|
13
|
+
if (!standards) return [];
|
|
14
|
+
return Object.keys(standards).map((muscle) => {
|
|
15
|
+
const { min, max } = standards[muscle];
|
|
16
|
+
const rawValue = measurements[muscle] ?? min;
|
|
17
|
+
const point = {
|
|
18
|
+
muscle,
|
|
19
|
+
score: calculateDevelopmentScore(rawValue, min, max),
|
|
20
|
+
rawValue
|
|
21
|
+
};
|
|
22
|
+
if (compareMeasurements) {
|
|
23
|
+
const compareRaw = compareMeasurements[muscle] ?? min;
|
|
24
|
+
point.compareScore = calculateDevelopmentScore(compareRaw, min, max);
|
|
25
|
+
point.compareRawValue = compareRaw;
|
|
26
|
+
}
|
|
27
|
+
return point;
|
|
28
|
+
});
|
|
29
|
+
}, [
|
|
30
|
+
gender,
|
|
31
|
+
measurements,
|
|
32
|
+
compareMeasurements
|
|
33
|
+
]);
|
|
34
|
+
if (data.length === 0) return null;
|
|
35
|
+
return /* @__PURE__ */ jsx("div", {
|
|
36
|
+
className,
|
|
37
|
+
style: {
|
|
38
|
+
width,
|
|
39
|
+
...style
|
|
40
|
+
},
|
|
41
|
+
children: /* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
42
|
+
width: "100%",
|
|
43
|
+
height,
|
|
44
|
+
children: /* @__PURE__ */ jsxs(RadarChart, {
|
|
45
|
+
cx: "50%",
|
|
46
|
+
cy: "50%",
|
|
47
|
+
outerRadius: "80%",
|
|
48
|
+
data,
|
|
49
|
+
children: [
|
|
50
|
+
/* @__PURE__ */ jsx(PolarGrid, {}),
|
|
51
|
+
/* @__PURE__ */ jsx(PolarAngleAxis, {
|
|
52
|
+
dataKey: "muscle",
|
|
53
|
+
tick: { fontSize: 12 }
|
|
54
|
+
}),
|
|
55
|
+
/* @__PURE__ */ jsx(PolarRadiusAxis, {
|
|
56
|
+
angle: 90,
|
|
57
|
+
domain: [0, 100],
|
|
58
|
+
tick: { fontSize: 10 }
|
|
59
|
+
}),
|
|
60
|
+
/* @__PURE__ */ jsx(Radar, {
|
|
61
|
+
name: label,
|
|
62
|
+
dataKey: "score",
|
|
63
|
+
stroke: color,
|
|
64
|
+
fill: color,
|
|
65
|
+
fillOpacity
|
|
66
|
+
}),
|
|
67
|
+
compareMeasurements && /* @__PURE__ */ jsx(Radar, {
|
|
68
|
+
name: compareLabel,
|
|
69
|
+
dataKey: "compareScore",
|
|
70
|
+
stroke: compareColor,
|
|
71
|
+
fill: compareColor,
|
|
72
|
+
fillOpacity
|
|
73
|
+
}),
|
|
74
|
+
showTooltip && /* @__PURE__ */ jsx(Tooltip, {}),
|
|
75
|
+
showLegend && /* @__PURE__ */ jsx(Legend, {})
|
|
76
|
+
]
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export { MuscleRadar };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=index.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.es.js","names":[],"sources":["../../../src/components/MuscleRadar/index.tsx"],"sourcesContent":["import { useMemo, type CSSProperties } from 'react';\r\nimport {\r\n Radar,\r\n RadarChart,\r\n PolarGrid,\r\n PolarAngleAxis,\r\n PolarRadiusAxis,\r\n ResponsiveContainer,\r\n Tooltip,\r\n Legend,\r\n} from 'recharts';\r\nimport {\r\n MUSCLE_STANDARDS,\r\n calculateDevelopmentScore,\r\n type Gender,\r\n} from './normalization';\r\n\r\nexport interface BodyMeasurements {\r\n /** Measurement values in inches, keyed by muscle/body-part name. */\r\n [muscle: string]: number;\r\n}\r\n\r\nexport interface MuscleRadarProps {\r\n /** Gender – determines which min/max standards are used. */\r\n gender: Gender;\r\n /** Raw body measurements in inches. Keys should match `MUSCLE_STANDARDS` keys. */\r\n measurements: BodyMeasurements;\r\n /** Optional second data set to overlay (e.g. goals or previous measurements). */\r\n compareMeasurements?: BodyMeasurements;\r\n /** Fill colour for the primary radar area (default: `'#8884d8'`). */\r\n color?: string;\r\n /** Fill colour for the comparison radar area (default: `'#82ca9d'`). */\r\n compareColor?: string;\r\n /** Label for the primary data set shown in the legend (default: `'Current'`). */\r\n label?: string;\r\n /** Label for the comparison data set (default: `'Comparison'`). */\r\n compareLabel?: string;\r\n /** Width of the chart container (default: `'100%'`). */\r\n width?: string | number;\r\n /** Height of the chart container (default: `400`). */\r\n height?: number;\r\n /** Optional inline styles applied to the outer wrapper. */\r\n style?: CSSProperties;\r\n /** Optional CSS class name for the outer wrapper. */\r\n className?: string;\r\n /** Whether to show the tooltip on hover (default: `true`). */\r\n showTooltip?: boolean;\r\n /** Whether to show the legend (default: `true`). */\r\n showLegend?: boolean;\r\n /** Fill opacity for radar areas (default: `0.35`). */\r\n fillOpacity?: number;\r\n}\r\n\r\ninterface RadarDataPoint {\r\n muscle: string;\r\n score: number;\r\n rawValue: number;\r\n compareScore?: number;\r\n compareRawValue?: number;\r\n}\r\n\r\n/**\r\n * A Recharts-powered radar / spider chart that normalizes raw body\r\n * measurements into development scores (0–100) and plots them on a\r\n * radial grid. Optionally overlays a second data set for comparison.\r\n */\r\nexport function MuscleRadar({\r\n gender,\r\n measurements,\r\n compareMeasurements,\r\n color = '#8884d8',\r\n compareColor = '#82ca9d',\r\n label = 'Current',\r\n compareLabel = 'Comparison',\r\n width = '100%',\r\n height = 400,\r\n style,\r\n className,\r\n showTooltip = true,\r\n showLegend = true,\r\n fillOpacity = 0.35,\r\n}: MuscleRadarProps) {\r\n const data = useMemo<RadarDataPoint[]>(() => {\r\n const standards = MUSCLE_STANDARDS[gender];\r\n if (!standards) return [];\r\n\r\n return Object.keys(standards).map((muscle) => {\r\n const { min, max } = standards[muscle];\r\n const rawValue = measurements[muscle] ?? min;\r\n const score = calculateDevelopmentScore(rawValue, min, max);\r\n\r\n const point: RadarDataPoint = { muscle, score, rawValue };\r\n\r\n if (compareMeasurements) {\r\n const compareRaw = compareMeasurements[muscle] ?? min;\r\n point.compareScore = calculateDevelopmentScore(compareRaw, min, max);\r\n point.compareRawValue = compareRaw;\r\n }\r\n\r\n return point;\r\n });\r\n }, [gender, measurements, compareMeasurements]);\r\n\r\n if (data.length === 0) return null;\r\n\r\n return (\r\n <div className={className} style={{ width, ...style }}>\r\n <ResponsiveContainer width=\"100%\" height={height}>\r\n <RadarChart cx=\"50%\" cy=\"50%\" outerRadius=\"80%\" data={data}>\r\n <PolarGrid />\r\n <PolarAngleAxis\r\n dataKey=\"muscle\"\r\n tick={{ fontSize: 12 }}\r\n />\r\n <PolarRadiusAxis\r\n angle={90}\r\n domain={[0, 100]}\r\n tick={{ fontSize: 10 }}\r\n />\r\n\r\n <Radar\r\n name={label}\r\n dataKey=\"score\"\r\n stroke={color}\r\n fill={color}\r\n fillOpacity={fillOpacity}\r\n />\r\n\r\n {compareMeasurements && (\r\n <Radar\r\n name={compareLabel}\r\n dataKey=\"compareScore\"\r\n stroke={compareColor}\r\n fill={compareColor}\r\n fillOpacity={fillOpacity}\r\n />\r\n )}\r\n\r\n {showTooltip && <Tooltip />}\r\n {showLegend && <Legend />}\r\n </RadarChart>\r\n </ResponsiveContainer>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";;;;;;;;;AAkEA,SAAgB,YAAY,EAC1B,QACA,cACA,qBACA,QAAQ,WACR,eAAe,WACf,QAAQ,WACR,eAAe,cACf,QAAQ,QACR,SAAS,KACT,OACA,WACA,cAAc,MACd,aAAa,MACb,cAAc,OACK;CACnB,MAAM,OAAO,cAAgC;EAC3C,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UAAW,QAAO,EAAE;AAEzB,SAAO,OAAO,KAAK,UAAU,CAAC,KAAK,WAAW;GAC5C,MAAM,EAAE,KAAK,QAAQ,UAAU;GAC/B,MAAM,WAAW,aAAa,WAAW;GAGzC,MAAM,QAAwB;IAAE;IAAQ,OAF1B,0BAA0B,UAAU,KAAK,IAAI;IAEZ;IAAU;AAEzD,OAAI,qBAAqB;IACvB,MAAM,aAAa,oBAAoB,WAAW;AAClD,UAAM,eAAe,0BAA0B,YAAY,KAAK,IAAI;AACpE,UAAM,kBAAkB;;AAG1B,UAAO;IACP;IACD;EAAC;EAAQ;EAAc;EAAoB,CAAC;AAE/C,KAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QACE,oBAAC,OAAD;EAAgB;EAAW,OAAO;GAAE;GAAO,GAAG;GAAO;YACnD,oBAAC,qBAAD;GAAqB,OAAM;GAAe;aACxC,qBAAC,YAAD;IAAY,IAAG;IAAM,IAAG;IAAM,aAAY;IAAY;cAAtD;KACE,oBAAC,WAAD,EAAa,CAAA;KACb,oBAAC,gBAAD;MACE,SAAQ;MACR,MAAM,EAAE,UAAU,IAAI;MACtB,CAAA;KACF,oBAAC,iBAAD;MACE,OAAO;MACP,QAAQ,CAAC,GAAG,IAAI;MAChB,MAAM,EAAE,UAAU,IAAI;MACtB,CAAA;KAEF,oBAAC,OAAD;MACE,MAAM;MACN,SAAQ;MACR,QAAQ;MACR,MAAM;MACO;MACb,CAAA;KAED,uBACC,oBAAC,OAAD;MACE,MAAM;MACN,SAAQ;MACR,QAAQ;MACR,MAAM;MACO;MACb,CAAA;KAGH,eAAe,oBAAC,SAAD,EAAW,CAAA;KAC1B,cAAc,oBAAC,QAAD,EAAU,CAAA;KACd;;GACO,CAAA;EAClB,CAAA"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physiological min/max limits in inches for body measurements.
|
|
3
|
+
* Used to normalize raw measurements into 0–100 development scores.
|
|
4
|
+
*/
|
|
5
|
+
const MUSCLE_STANDARDS = {
|
|
6
|
+
male: {
|
|
7
|
+
chest: {
|
|
8
|
+
min: 32,
|
|
9
|
+
max: 50
|
|
10
|
+
},
|
|
11
|
+
shoulders: {
|
|
12
|
+
min: 38,
|
|
13
|
+
max: 56
|
|
14
|
+
},
|
|
15
|
+
biceps: {
|
|
16
|
+
min: 10,
|
|
17
|
+
max: 20
|
|
18
|
+
},
|
|
19
|
+
forearm: {
|
|
20
|
+
min: 8,
|
|
21
|
+
max: 15
|
|
22
|
+
},
|
|
23
|
+
waist: {
|
|
24
|
+
min: 26,
|
|
25
|
+
max: 40
|
|
26
|
+
},
|
|
27
|
+
hips: {
|
|
28
|
+
min: 32,
|
|
29
|
+
max: 44
|
|
30
|
+
},
|
|
31
|
+
thigh: {
|
|
32
|
+
min: 18,
|
|
33
|
+
max: 30
|
|
34
|
+
},
|
|
35
|
+
calf: {
|
|
36
|
+
min: 12,
|
|
37
|
+
max: 20
|
|
38
|
+
},
|
|
39
|
+
neck: {
|
|
40
|
+
min: 13,
|
|
41
|
+
max: 20
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
female: {
|
|
45
|
+
chest: {
|
|
46
|
+
min: 28,
|
|
47
|
+
max: 44
|
|
48
|
+
},
|
|
49
|
+
shoulders: {
|
|
50
|
+
min: 34,
|
|
51
|
+
max: 48
|
|
52
|
+
},
|
|
53
|
+
biceps: {
|
|
54
|
+
min: 8,
|
|
55
|
+
max: 16
|
|
56
|
+
},
|
|
57
|
+
forearm: {
|
|
58
|
+
min: 6,
|
|
59
|
+
max: 12
|
|
60
|
+
},
|
|
61
|
+
waist: {
|
|
62
|
+
min: 22,
|
|
63
|
+
max: 36
|
|
64
|
+
},
|
|
65
|
+
hips: {
|
|
66
|
+
min: 30,
|
|
67
|
+
max: 46
|
|
68
|
+
},
|
|
69
|
+
thigh: {
|
|
70
|
+
min: 16,
|
|
71
|
+
max: 28
|
|
72
|
+
},
|
|
73
|
+
calf: {
|
|
74
|
+
min: 10,
|
|
75
|
+
max: 18
|
|
76
|
+
},
|
|
77
|
+
neck: {
|
|
78
|
+
min: 11,
|
|
79
|
+
max: 16
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Converts a raw inch measurement into a 0–100 development score.
|
|
85
|
+
* The score is clamped between 5 and 100.
|
|
86
|
+
*
|
|
87
|
+
* @param val - The measured value in inches
|
|
88
|
+
* @param min - The physiological minimum for the measurement
|
|
89
|
+
* @param max - The physiological maximum for the measurement
|
|
90
|
+
* @returns A score between 5 and 100
|
|
91
|
+
*/
|
|
92
|
+
function calculateDevelopmentScore(val, min, max) {
|
|
93
|
+
if (max === min) return 5;
|
|
94
|
+
const raw = (val - min) / (max - min) * 100;
|
|
95
|
+
return Math.min(100, Math.max(5, Math.round(raw)));
|
|
96
|
+
}
|
|
97
|
+
exports.MUSCLE_STANDARDS = MUSCLE_STANDARDS;
|
|
98
|
+
exports.calculateDevelopmentScore = calculateDevelopmentScore;
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=normalization.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalization.cjs.js","names":[],"sources":["../../../src/components/MuscleRadar/normalization.ts"],"sourcesContent":["export type Gender = 'male' | 'female';\r\n\r\nexport interface MuscleStandards {\r\n min: number;\r\n max: number;\r\n}\r\n\r\n/**\r\n * Physiological min/max limits in inches for body measurements.\r\n * Used to normalize raw measurements into 0–100 development scores.\r\n */\r\nexport const MUSCLE_STANDARDS: Record<Gender, Record<string, MuscleStandards>> = {\r\n male: {\r\n chest: { min: 32, max: 50 },\r\n shoulders: { min: 38, max: 56 },\r\n biceps: { min: 10, max: 20 },\r\n forearm: { min: 8, max: 15 },\r\n waist: { min: 26, max: 40 },\r\n hips: { min: 32, max: 44 },\r\n thigh: { min: 18, max: 30 },\r\n calf: { min: 12, max: 20 },\r\n neck: { min: 13, max: 20 },\r\n },\r\n female: {\r\n chest: { min: 28, max: 44 },\r\n shoulders: { min: 34, max: 48 },\r\n biceps: { min: 8, max: 16 },\r\n forearm: { min: 6, max: 12 },\r\n waist: { min: 22, max: 36 },\r\n hips: { min: 30, max: 46 },\r\n thigh: { min: 16, max: 28 },\r\n calf: { min: 10, max: 18 },\r\n neck: { min: 11, max: 16 },\r\n },\r\n};\r\n\r\n/**\r\n * Converts a raw inch measurement into a 0–100 development score.\r\n * The score is clamped between 5 and 100.\r\n *\r\n * @param val - The measured value in inches\r\n * @param min - The physiological minimum for the measurement\r\n * @param max - The physiological maximum for the measurement\r\n * @returns A score between 5 and 100\r\n */\r\nexport function calculateDevelopmentScore(val: number, min: number, max: number): number {\r\n if (max === min) return 5;\r\n const raw = ((val - min) / (max - min)) * 100;\r\n return Math.min(100, Math.max(5, Math.round(raw)));\r\n}\r\n"],"mappings":";;;;AAWA,MAAa,mBAAoE;CAC/E,MAAM;EACJ,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,WAAW;GAAE,KAAK;GAAI,KAAK;GAAI;EAC/B,QAAQ;GAAE,KAAK;GAAI,KAAK;GAAI;EAC5B,SAAS;GAAE,KAAK;GAAG,KAAK;GAAI;EAC5B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B;CACD,QAAQ;EACN,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,WAAW;GAAE,KAAK;GAAI,KAAK;GAAI;EAC/B,QAAQ;GAAE,KAAK;GAAG,KAAK;GAAI;EAC3B,SAAS;GAAE,KAAK;GAAG,KAAK;GAAI;EAC5B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B;CACF;;;;;;;;;;AAWD,SAAgB,0BAA0B,KAAa,KAAa,KAAqB;AACvF,KAAI,QAAQ,IAAK,QAAO;CACxB,MAAM,OAAQ,MAAM,QAAQ,MAAM,OAAQ;AAC1C,QAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Gender = 'male' | 'female';
|
|
2
|
+
export interface MuscleStandards {
|
|
3
|
+
min: number;
|
|
4
|
+
max: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Physiological min/max limits in inches for body measurements.
|
|
8
|
+
* Used to normalize raw measurements into 0–100 development scores.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MUSCLE_STANDARDS: Record<Gender, Record<string, MuscleStandards>>;
|
|
11
|
+
/**
|
|
12
|
+
* Converts a raw inch measurement into a 0–100 development score.
|
|
13
|
+
* The score is clamped between 5 and 100.
|
|
14
|
+
*
|
|
15
|
+
* @param val - The measured value in inches
|
|
16
|
+
* @param min - The physiological minimum for the measurement
|
|
17
|
+
* @param max - The physiological maximum for the measurement
|
|
18
|
+
* @returns A score between 5 and 100
|
|
19
|
+
*/
|
|
20
|
+
export declare function calculateDevelopmentScore(val: number, min: number, max: number): number;
|
|
21
|
+
//# sourceMappingURL=normalization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalization.d.ts","sourceRoot":"","sources":["../../../src/components/MuscleRadar/normalization.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAuB5E,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIvF"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physiological min/max limits in inches for body measurements.
|
|
3
|
+
* Used to normalize raw measurements into 0–100 development scores.
|
|
4
|
+
*/
|
|
5
|
+
const MUSCLE_STANDARDS = {
|
|
6
|
+
male: {
|
|
7
|
+
chest: {
|
|
8
|
+
min: 32,
|
|
9
|
+
max: 50
|
|
10
|
+
},
|
|
11
|
+
shoulders: {
|
|
12
|
+
min: 38,
|
|
13
|
+
max: 56
|
|
14
|
+
},
|
|
15
|
+
biceps: {
|
|
16
|
+
min: 10,
|
|
17
|
+
max: 20
|
|
18
|
+
},
|
|
19
|
+
forearm: {
|
|
20
|
+
min: 8,
|
|
21
|
+
max: 15
|
|
22
|
+
},
|
|
23
|
+
waist: {
|
|
24
|
+
min: 26,
|
|
25
|
+
max: 40
|
|
26
|
+
},
|
|
27
|
+
hips: {
|
|
28
|
+
min: 32,
|
|
29
|
+
max: 44
|
|
30
|
+
},
|
|
31
|
+
thigh: {
|
|
32
|
+
min: 18,
|
|
33
|
+
max: 30
|
|
34
|
+
},
|
|
35
|
+
calf: {
|
|
36
|
+
min: 12,
|
|
37
|
+
max: 20
|
|
38
|
+
},
|
|
39
|
+
neck: {
|
|
40
|
+
min: 13,
|
|
41
|
+
max: 20
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
female: {
|
|
45
|
+
chest: {
|
|
46
|
+
min: 28,
|
|
47
|
+
max: 44
|
|
48
|
+
},
|
|
49
|
+
shoulders: {
|
|
50
|
+
min: 34,
|
|
51
|
+
max: 48
|
|
52
|
+
},
|
|
53
|
+
biceps: {
|
|
54
|
+
min: 8,
|
|
55
|
+
max: 16
|
|
56
|
+
},
|
|
57
|
+
forearm: {
|
|
58
|
+
min: 6,
|
|
59
|
+
max: 12
|
|
60
|
+
},
|
|
61
|
+
waist: {
|
|
62
|
+
min: 22,
|
|
63
|
+
max: 36
|
|
64
|
+
},
|
|
65
|
+
hips: {
|
|
66
|
+
min: 30,
|
|
67
|
+
max: 46
|
|
68
|
+
},
|
|
69
|
+
thigh: {
|
|
70
|
+
min: 16,
|
|
71
|
+
max: 28
|
|
72
|
+
},
|
|
73
|
+
calf: {
|
|
74
|
+
min: 10,
|
|
75
|
+
max: 18
|
|
76
|
+
},
|
|
77
|
+
neck: {
|
|
78
|
+
min: 11,
|
|
79
|
+
max: 16
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Converts a raw inch measurement into a 0–100 development score.
|
|
85
|
+
* The score is clamped between 5 and 100.
|
|
86
|
+
*
|
|
87
|
+
* @param val - The measured value in inches
|
|
88
|
+
* @param min - The physiological minimum for the measurement
|
|
89
|
+
* @param max - The physiological maximum for the measurement
|
|
90
|
+
* @returns A score between 5 and 100
|
|
91
|
+
*/
|
|
92
|
+
function calculateDevelopmentScore(val, min, max) {
|
|
93
|
+
if (max === min) return 5;
|
|
94
|
+
const raw = (val - min) / (max - min) * 100;
|
|
95
|
+
return Math.min(100, Math.max(5, Math.round(raw)));
|
|
96
|
+
}
|
|
97
|
+
export { MUSCLE_STANDARDS, calculateDevelopmentScore };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=normalization.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalization.es.js","names":[],"sources":["../../../src/components/MuscleRadar/normalization.ts"],"sourcesContent":["export type Gender = 'male' | 'female';\r\n\r\nexport interface MuscleStandards {\r\n min: number;\r\n max: number;\r\n}\r\n\r\n/**\r\n * Physiological min/max limits in inches for body measurements.\r\n * Used to normalize raw measurements into 0–100 development scores.\r\n */\r\nexport const MUSCLE_STANDARDS: Record<Gender, Record<string, MuscleStandards>> = {\r\n male: {\r\n chest: { min: 32, max: 50 },\r\n shoulders: { min: 38, max: 56 },\r\n biceps: { min: 10, max: 20 },\r\n forearm: { min: 8, max: 15 },\r\n waist: { min: 26, max: 40 },\r\n hips: { min: 32, max: 44 },\r\n thigh: { min: 18, max: 30 },\r\n calf: { min: 12, max: 20 },\r\n neck: { min: 13, max: 20 },\r\n },\r\n female: {\r\n chest: { min: 28, max: 44 },\r\n shoulders: { min: 34, max: 48 },\r\n biceps: { min: 8, max: 16 },\r\n forearm: { min: 6, max: 12 },\r\n waist: { min: 22, max: 36 },\r\n hips: { min: 30, max: 46 },\r\n thigh: { min: 16, max: 28 },\r\n calf: { min: 10, max: 18 },\r\n neck: { min: 11, max: 16 },\r\n },\r\n};\r\n\r\n/**\r\n * Converts a raw inch measurement into a 0–100 development score.\r\n * The score is clamped between 5 and 100.\r\n *\r\n * @param val - The measured value in inches\r\n * @param min - The physiological minimum for the measurement\r\n * @param max - The physiological maximum for the measurement\r\n * @returns A score between 5 and 100\r\n */\r\nexport function calculateDevelopmentScore(val: number, min: number, max: number): number {\r\n if (max === min) return 5;\r\n const raw = ((val - min) / (max - min)) * 100;\r\n return Math.min(100, Math.max(5, Math.round(raw)));\r\n}\r\n"],"mappings":";;;;AAWA,MAAa,mBAAoE;CAC/E,MAAM;EACJ,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,WAAW;GAAE,KAAK;GAAI,KAAK;GAAI;EAC/B,QAAQ;GAAE,KAAK;GAAI,KAAK;GAAI;EAC5B,SAAS;GAAE,KAAK;GAAG,KAAK;GAAI;EAC5B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B;CACD,QAAQ;EACN,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,WAAW;GAAE,KAAK;GAAI,KAAK;GAAI;EAC/B,QAAQ;GAAE,KAAK;GAAG,KAAK;GAAI;EAC3B,SAAS;GAAE,KAAK;GAAG,KAAK;GAAI;EAC5B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,OAAO;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC1B,MAAM;GAAE,KAAK;GAAI,KAAK;GAAI;EAC3B;CACF;;;;;;;;;;AAWD,SAAgB,0BAA0B,KAAa,KAAa,KAAqB;AACvF,KAAI,QAAQ,IAAK,QAAO;CACxB,MAAM,OAAQ,MAAM,QAAQ,MAAM,OAAQ;AAC1C,QAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_svgs = require("./svgs.cjs.js");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
/**
|
|
6
|
+
* Renders an interactive SVG body diagram.
|
|
7
|
+
*
|
|
8
|
+
* Muscle groups inside the SVG carry `class="muscle"` and can be
|
|
9
|
+
* highlighted by passing their `id` values through the `activeMuscles`
|
|
10
|
+
* prop. The component uses `dangerouslySetInnerHTML` to inject the SVG
|
|
11
|
+
* markup and `useEffect` to imperatively toggle the `active` class on
|
|
12
|
+
* the matching `<g>` elements.
|
|
13
|
+
*/
|
|
14
|
+
function MuscleVisualizer({ gender, view = "front", activeMuscles = [], onMuscleClick, style, className, width = "100%", height = "auto" }) {
|
|
15
|
+
const containerRef = (0, react.useRef)(null);
|
|
16
|
+
const svgKey = `${gender} ${view}`;
|
|
17
|
+
const svgMarkup = require_svgs.MUSCLE_SVGS[svgKey] ?? "";
|
|
18
|
+
(0, react.useEffect)(() => {
|
|
19
|
+
const container = containerRef.current;
|
|
20
|
+
if (!container) return;
|
|
21
|
+
container.querySelectorAll("g.muscle").forEach((g) => {
|
|
22
|
+
if (activeMuscles.includes(g.id)) g.classList.add("active");
|
|
23
|
+
else g.classList.remove("active");
|
|
24
|
+
});
|
|
25
|
+
}, [activeMuscles, svgKey]);
|
|
26
|
+
(0, react.useEffect)(() => {
|
|
27
|
+
const container = containerRef.current;
|
|
28
|
+
if (!container || !onMuscleClick) return;
|
|
29
|
+
const handler = (e) => {
|
|
30
|
+
const target = e.target.closest("g.muscle");
|
|
31
|
+
if (target?.id) onMuscleClick(target.id);
|
|
32
|
+
};
|
|
33
|
+
container.addEventListener("click", handler);
|
|
34
|
+
return () => container.removeEventListener("click", handler);
|
|
35
|
+
}, [onMuscleClick, svgKey]);
|
|
36
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
37
|
+
ref: containerRef,
|
|
38
|
+
className,
|
|
39
|
+
style: {
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
42
|
+
...style
|
|
43
|
+
},
|
|
44
|
+
dangerouslySetInnerHTML: { __html: svgMarkup }
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
exports.MuscleVisualizer = MuscleVisualizer;
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","names":[],"sources":["../../../src/components/MuscleVisualizer/index.tsx"],"sourcesContent":["import { useEffect, useRef, type CSSProperties } from 'react';\r\nimport { MUSCLE_SVGS } from './svgs';\r\n\r\nexport type Gender = 'male' | 'female';\r\nexport type View = 'front' | 'back';\r\n\r\nexport interface MuscleVisualizerProps {\r\n /** Gender to display – determines the body silhouette. */\r\n gender: Gender;\r\n /** Which side of the body to show. */\r\n view?: View;\r\n /** Muscle IDs that should be highlighted (given the `.active` CSS class). */\r\n activeMuscles?: string[];\r\n /** Optional callback fired when a muscle group `<g>` element is clicked. */\r\n onMuscleClick?: (muscleId: string) => void;\r\n /** Optional inline styles applied to the wrapper `<div>`. */\r\n style?: CSSProperties;\r\n /** Optional CSS class name for the wrapper `<div>`. */\r\n className?: string;\r\n /** Width applied to the SVG wrapper (default: `'100%'`). */\r\n width?: string | number;\r\n /** Height applied to the SVG wrapper (default: `'auto'`). */\r\n height?: string | number;\r\n}\r\n\r\n/**\r\n * Renders an interactive SVG body diagram.\r\n *\r\n * Muscle groups inside the SVG carry `class=\"muscle\"` and can be\r\n * highlighted by passing their `id` values through the `activeMuscles`\r\n * prop. The component uses `dangerouslySetInnerHTML` to inject the SVG\r\n * markup and `useEffect` to imperatively toggle the `active` class on\r\n * the matching `<g>` elements.\r\n */\r\nexport function MuscleVisualizer({\r\n gender,\r\n view = 'front',\r\n activeMuscles = [],\r\n onMuscleClick,\r\n style,\r\n className,\r\n width = '100%',\r\n height = 'auto',\r\n}: MuscleVisualizerProps) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n const svgKey = `${gender} ${view}` as keyof typeof MUSCLE_SVGS;\r\n const svgMarkup = MUSCLE_SVGS[svgKey] ?? '';\r\n\r\n // Toggle the `active` class on `<g class=\"muscle\">` elements whose\r\n // `id` is listed in `activeMuscles`.\r\n useEffect(() => {\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n const muscleGroups = container.querySelectorAll<SVGGElement>('g.muscle');\r\n muscleGroups.forEach((g) => {\r\n if (activeMuscles.includes(g.id)) {\r\n g.classList.add('active');\r\n } else {\r\n g.classList.remove('active');\r\n }\r\n });\r\n }, [activeMuscles, svgKey]);\r\n\r\n // Attach click listeners to every `<g class=\"muscle\">` element.\r\n useEffect(() => {\r\n const container = containerRef.current;\r\n if (!container || !onMuscleClick) return;\r\n\r\n const handler = (e: Event) => {\r\n const target = (e.target as SVGElement).closest<SVGGElement>('g.muscle');\r\n if (target?.id) {\r\n onMuscleClick(target.id);\r\n }\r\n };\r\n\r\n container.addEventListener('click', handler);\r\n return () => container.removeEventListener('click', handler);\r\n }, [onMuscleClick, svgKey]);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ width, height, ...style }}\r\n dangerouslySetInnerHTML={{ __html: svgMarkup }}\r\n />\r\n );\r\n}\r\n"],"mappings":";;;;;;;;;;;;;AAkCA,SAAgB,iBAAiB,EAC/B,QACA,OAAO,SACP,gBAAgB,EAAE,EAClB,eACA,OACA,WACA,QAAQ,QACR,SAAS,UACe;CACxB,MAAM,gBAAA,GAAA,MAAA,QAAsC,KAAK;CAEjD,MAAM,SAAS,GAAG,OAAO,GAAG;CAC5B,MAAM,YAAY,aAAA,YAAY,WAAW;AAIzC,EAAA,GAAA,MAAA,iBAAgB;EACd,MAAM,YAAY,aAAa;AAC/B,MAAI,CAAC,UAAW;AAEK,YAAU,iBAA8B,WAAW,CAC3D,SAAS,MAAM;AAC1B,OAAI,cAAc,SAAS,EAAE,GAAG,CAC9B,GAAE,UAAU,IAAI,SAAS;OAEzB,GAAE,UAAU,OAAO,SAAS;IAE9B;IACD,CAAC,eAAe,OAAO,CAAC;AAG3B,EAAA,GAAA,MAAA,iBAAgB;EACd,MAAM,YAAY,aAAa;AAC/B,MAAI,CAAC,aAAa,CAAC,cAAe;EAElC,MAAM,WAAW,MAAa;GAC5B,MAAM,SAAU,EAAE,OAAsB,QAAqB,WAAW;AACxE,OAAI,QAAQ,GACV,eAAc,OAAO,GAAG;;AAI5B,YAAU,iBAAiB,SAAS,QAAQ;AAC5C,eAAa,UAAU,oBAAoB,SAAS,QAAQ;IAC3D,CAAC,eAAe,OAAO,CAAC;AAE3B,QACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EACE,KAAK;EACM;EACX,OAAO;GAAE;GAAO;GAAQ,GAAG;GAAO;EAClC,yBAAyB,EAAE,QAAQ,WAAW;EAC9C,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
export type Gender = 'male' | 'female';
|
|
3
|
+
export type View = 'front' | 'back';
|
|
4
|
+
export interface MuscleVisualizerProps {
|
|
5
|
+
/** Gender to display – determines the body silhouette. */
|
|
6
|
+
gender: Gender;
|
|
7
|
+
/** Which side of the body to show. */
|
|
8
|
+
view?: View;
|
|
9
|
+
/** Muscle IDs that should be highlighted (given the `.active` CSS class). */
|
|
10
|
+
activeMuscles?: string[];
|
|
11
|
+
/** Optional callback fired when a muscle group `<g>` element is clicked. */
|
|
12
|
+
onMuscleClick?: (muscleId: string) => void;
|
|
13
|
+
/** Optional inline styles applied to the wrapper `<div>`. */
|
|
14
|
+
style?: CSSProperties;
|
|
15
|
+
/** Optional CSS class name for the wrapper `<div>`. */
|
|
16
|
+
className?: string;
|
|
17
|
+
/** Width applied to the SVG wrapper (default: `'100%'`). */
|
|
18
|
+
width?: string | number;
|
|
19
|
+
/** Height applied to the SVG wrapper (default: `'auto'`). */
|
|
20
|
+
height?: string | number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Renders an interactive SVG body diagram.
|
|
24
|
+
*
|
|
25
|
+
* Muscle groups inside the SVG carry `class="muscle"` and can be
|
|
26
|
+
* highlighted by passing their `id` values through the `activeMuscles`
|
|
27
|
+
* prop. The component uses `dangerouslySetInnerHTML` to inject the SVG
|
|
28
|
+
* markup and `useEffect` to imperatively toggle the `active` class on
|
|
29
|
+
* the matching `<g>` elements.
|
|
30
|
+
*/
|
|
31
|
+
export declare function MuscleVisualizer({ gender, view, activeMuscles, onMuscleClick, style, className, width, height, }: MuscleVisualizerProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/MuscleVisualizer/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAG9D,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AACvC,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,6DAA6D;IAC7D,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,IAAc,EACd,aAAkB,EAClB,aAAa,EACb,KAAK,EACL,SAAS,EACT,KAAc,EACd,MAAe,GAChB,EAAE,qBAAqB,2CA8CvB"}
|