rg-life-wheel-chart 0.1.2
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/LICENSE +21 -0
- package/README.MD +97 -0
- package/dist/index.cjs.js +154 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.esm.js +152 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eudes Roger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.MD
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<!-- README.md -->
|
|
2
|
+
|
|
3
|
+
# rg-life-wheel-chart
|
|
4
|
+
|
|
5
|
+
A dynamic, zero-dependency Life Wheel Chart component for React and Next.js.
|
|
6
|
+
Built with pure SVG — no canvas, no third-party chart libraries.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Supports **1 to 50** life areas dynamically
|
|
11
|
+
- Auto-generated chromatic color palette — override per area as needed
|
|
12
|
+
- Fully **responsive** via SVG viewBox
|
|
13
|
+
- Clean, typed API with full TypeScript support
|
|
14
|
+
- Compatible with React 17+ and Next.js (App Router & Pages Router)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install rg-life-wheel-chart
|
|
20
|
+
# or
|
|
21
|
+
yarn add rg-life-wheel-chart
|
|
22
|
+
# or
|
|
23
|
+
pnpm add rg-life-wheel-chart
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Basic Usage
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { LifeWheel } from "rg-life-wheel-chart";
|
|
30
|
+
|
|
31
|
+
const areas = [
|
|
32
|
+
{ label: "Health", value: 8 },
|
|
33
|
+
{ label: "Career", value: 6 },
|
|
34
|
+
{ label: "Love", value: 7 },
|
|
35
|
+
{ label: "Family", value: 5 },
|
|
36
|
+
{ label: "Spirituality", value: 4 },
|
|
37
|
+
{ label: "Money", value: 6 },
|
|
38
|
+
{ label: "Fun", value: 9 },
|
|
39
|
+
{ label: "Friends", value: 7 },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export default function App() {
|
|
43
|
+
return <LifeWheel areas={areas} />;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Custom Colors
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
const areas = [
|
|
51
|
+
{ label: "Health", value: 8, color: "#f43f5e" },
|
|
52
|
+
{ label: "Career", value: 6, color: "#6366f1" },
|
|
53
|
+
{ label: "Love", value: 7 }, // auto-colored
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
<LifeWheel areas={areas} />;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Props
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Default | Description |
|
|
62
|
+
| --------------- | ----------------------- | ----------- | ----------------------------------- |
|
|
63
|
+
| `areas` | `LifeArea[]` | required | Array of life areas (1–50) |
|
|
64
|
+
| `gridLevels` | `number` | `10` | Number of concentric rings |
|
|
65
|
+
| `gridColor` | `string` | `"#d1d5db"` | Grid line color |
|
|
66
|
+
| `gridOpacity` | `number` | `0.6` | Grid line opacity |
|
|
67
|
+
| `strokeColor` | `string` | `"white"` | Segment border color |
|
|
68
|
+
| `strokeWidth` | `number` | `1.5` | Segment border width |
|
|
69
|
+
| `labelFontSize` | `number` | `9` | Label font size (SVG units) |
|
|
70
|
+
| `labelColor` | `string` | `"#374151"` | Label text color |
|
|
71
|
+
| `labelPadding` | `number` | `12` | Space between wheel edge and labels |
|
|
72
|
+
| `className` | `string` | — | CSS class for the wrapper div |
|
|
73
|
+
| `style` | `CSSProperties` | — | Inline styles for the wrapper div |
|
|
74
|
+
| `onAreaClick` | `(area, index) => void` | — | Click handler for segments |
|
|
75
|
+
|
|
76
|
+
## LifeArea Interface
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface LifeArea {
|
|
80
|
+
label: string; // display name
|
|
81
|
+
value: number; // score from 0 to 10
|
|
82
|
+
color?: string; // any valid CSS color (hex, hsl, rgb...)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Next.js (App Router)
|
|
87
|
+
|
|
88
|
+
If using the App Router, add `"use client"` to the file that imports the component since it uses React hooks internally.
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
"use client";
|
|
92
|
+
import { LifeWheel } from "rg-life-wheel-chart";
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generates a visually distinct chromatic palette in HSL.
|
|
8
|
+
* Distributes hues evenly across the color wheel.
|
|
9
|
+
*/
|
|
10
|
+
function generateColorPalette(count) {
|
|
11
|
+
return Array.from({ length: count }, (_, i) => {
|
|
12
|
+
const hue = Math.round((i / count) * 360);
|
|
13
|
+
const saturation = 65 + (i % 3) * 5; // slight variation: 65, 70, 75
|
|
14
|
+
const lightness = 55 + (i % 2) * 5; // slight variation: 55, 60
|
|
15
|
+
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Merges user-provided colors with auto-generated ones.
|
|
20
|
+
* Areas without a color receive one from the palette.
|
|
21
|
+
*/
|
|
22
|
+
function resolveColors(areas, palette) {
|
|
23
|
+
return areas.map((area, i) => { var _a, _b; return (_b = (_a = area.color) !== null && _a !== void 0 ? _a : palette[i]) !== null && _b !== void 0 ? _b : palette[i % palette.length]; });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Converts polar coordinates to Cartesian. Angle in radians. */
|
|
27
|
+
function polarToCartesian(cx, cy, radius, angleRad) {
|
|
28
|
+
return {
|
|
29
|
+
x: cx + radius * Math.cos(angleRad),
|
|
30
|
+
y: cy + radius * Math.sin(angleRad),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns the SVG path string for a filled pie segment.
|
|
35
|
+
* Handles the edge case where a full circle (value=10, single area) is drawn correctly.
|
|
36
|
+
*/
|
|
37
|
+
function buildSegmentPath(cx, cy, radius, startAngle, endAngle) {
|
|
38
|
+
if (radius <= 0)
|
|
39
|
+
return "";
|
|
40
|
+
const angleDelta = endAngle - startAngle;
|
|
41
|
+
const isFullCircle = Math.abs(angleDelta - Math.PI * 2) < 1e-6;
|
|
42
|
+
if (isFullCircle) {
|
|
43
|
+
// SVG arcs cannot span exactly 360°, so we split into two halves
|
|
44
|
+
const mid = startAngle + Math.PI;
|
|
45
|
+
const p1 = polarToCartesian(cx, cy, radius, startAngle);
|
|
46
|
+
const p2 = polarToCartesian(cx, cy, radius, mid);
|
|
47
|
+
return [
|
|
48
|
+
`M ${cx} ${cy}`,
|
|
49
|
+
`L ${p1.x} ${p1.y}`,
|
|
50
|
+
`A ${radius} ${radius} 0 0 1 ${p2.x} ${p2.y}`,
|
|
51
|
+
`A ${radius} ${radius} 0 0 1 ${p1.x} ${p1.y}`,
|
|
52
|
+
"Z",
|
|
53
|
+
].join(" ");
|
|
54
|
+
}
|
|
55
|
+
const start = polarToCartesian(cx, cy, radius, startAngle);
|
|
56
|
+
const end = polarToCartesian(cx, cy, radius, endAngle);
|
|
57
|
+
const largeArcFlag = angleDelta > Math.PI ? 1 : 0;
|
|
58
|
+
return [
|
|
59
|
+
`M ${cx} ${cy}`,
|
|
60
|
+
`L ${start.x} ${start.y}`,
|
|
61
|
+
`A ${radius} ${radius} 0 ${largeArcFlag} 1 ${end.x} ${end.y}`,
|
|
62
|
+
"Z",
|
|
63
|
+
].join(" ");
|
|
64
|
+
}
|
|
65
|
+
/** Derives SVG text-anchor and dominant-baseline from an angle. */
|
|
66
|
+
function getLabelAlignment(angleRad) {
|
|
67
|
+
const cos = Math.cos(angleRad);
|
|
68
|
+
const sin = Math.sin(angleRad);
|
|
69
|
+
const textAnchor = cos > 0.3 ? "start" : cos < -0.3 ? "end" : "middle";
|
|
70
|
+
const dominantBaseline = sin > 0.3 ? "hanging" : sin < -0.3 ? "auto" : "middle";
|
|
71
|
+
return { textAnchor, dominantBaseline };
|
|
72
|
+
}
|
|
73
|
+
/** Computes start angle, end angle, and mid angle (in radians) for segment i of n. */
|
|
74
|
+
function getSegmentAngles(i, n) {
|
|
75
|
+
const slice = (2 * Math.PI) / n;
|
|
76
|
+
const offset = -Math.PI / 2; // start from top
|
|
77
|
+
const startAngle = i * slice + offset;
|
|
78
|
+
const endAngle = (i + 1) * slice + offset;
|
|
79
|
+
const midAngle = startAngle + slice / 2;
|
|
80
|
+
return { startAngle, endAngle, midAngle };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function WheelGrid({ cx, cy, maxRadius, levels, segmentCount, color, opacity, }) {
|
|
84
|
+
const radii = Array.from({ length: levels }, (_, i) => ((i + 1) / levels) * maxRadius);
|
|
85
|
+
const radialLines = Array.from({ length: segmentCount }, (_, i) => {
|
|
86
|
+
const angle = (i / segmentCount) * 2 * Math.PI - Math.PI / 2;
|
|
87
|
+
const end = polarToCartesian(cx, cy, maxRadius, angle);
|
|
88
|
+
return { x2: end.x, y2: end.y };
|
|
89
|
+
});
|
|
90
|
+
return (jsxRuntime.jsxs("g", { stroke: color, strokeOpacity: opacity, fill: "none", children: [radii.map((r) => (jsxRuntime.jsx("circle", { cx: cx, cy: cy, r: r, strokeWidth: 0.8 }, r))), radialLines.map((line, i) => (jsxRuntime.jsx("line", { x1: cx, y1: cy, x2: line.x2, y2: line.y2, strokeWidth: 0.8 }, i)))] }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function WheelSegment({ cx, cy, maxRadius, value, startAngle, endAngle, color, strokeColor, strokeWidth, onClick, }) {
|
|
94
|
+
const clampedValue = Math.min(10, Math.max(0, value));
|
|
95
|
+
const radius = (clampedValue / 10) * maxRadius;
|
|
96
|
+
if (radius <= 0)
|
|
97
|
+
return null;
|
|
98
|
+
const d = buildSegmentPath(cx, cy, radius, startAngle, endAngle);
|
|
99
|
+
return (jsxRuntime.jsx("path", { d: d, fill: color, stroke: strokeColor, strokeWidth: strokeWidth, strokeLinejoin: "round", onClick: onClick, style: {
|
|
100
|
+
cursor: onClick ? "pointer" : "default",
|
|
101
|
+
transition: "opacity 0.2s",
|
|
102
|
+
mixBlendMode: "multiply",
|
|
103
|
+
}, onMouseEnter: (e) => {
|
|
104
|
+
e.currentTarget.style.opacity = "0.85";
|
|
105
|
+
}, onMouseLeave: (e) => {
|
|
106
|
+
e.currentTarget.style.opacity = "1";
|
|
107
|
+
} }));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function WheelLabel({ cx, cy, labelRadius, midAngle, label, fontSize, color, }) {
|
|
111
|
+
const position = polarToCartesian(cx, cy, labelRadius, midAngle);
|
|
112
|
+
const { textAnchor, dominantBaseline } = getLabelAlignment(midAngle);
|
|
113
|
+
const alignedTextAnchor = textAnchor;
|
|
114
|
+
const alignedDominantBaseline = dominantBaseline;
|
|
115
|
+
const words = label.split(" ");
|
|
116
|
+
const lines = words.length > 2
|
|
117
|
+
? [
|
|
118
|
+
words.slice(0, Math.ceil(words.length / 2)).join(" "),
|
|
119
|
+
words.slice(Math.ceil(words.length / 2)).join(" "),
|
|
120
|
+
]
|
|
121
|
+
: [label];
|
|
122
|
+
const lineHeight = fontSize * 1.2;
|
|
123
|
+
return (jsxRuntime.jsx("text", { x: position.x, y: position.y, textAnchor: alignedTextAnchor, dominantBaseline: alignedDominantBaseline, fontSize: fontSize, fill: color, fontFamily: "system-ui, -apple-system, sans-serif", children: lines.map((line, i) => (jsxRuntime.jsx("tspan", { x: position.x, dy: i === 0 ? 0 : lineHeight, children: line }, i))) }));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const VIEW_BOX_SIZE = 300;
|
|
127
|
+
const CENTER = VIEW_BOX_SIZE / 2;
|
|
128
|
+
const MAX_RADIUS_RATIO = 0.38; // fraction of viewBox half-size used for the wheel
|
|
129
|
+
const LABEL_PADDING_DEFAULT = 12;
|
|
130
|
+
function LifeWheel({ areas, gridLevels = 10, gridColor = "#d1d5db", gridOpacity = 0.6, strokeColor = "white", strokeWidth = 1.5, labelFontSize = 9, labelColor = "#374151", labelPadding = LABEL_PADDING_DEFAULT, className, style, onAreaClick, }) {
|
|
131
|
+
const safeAreas = react.useMemo(() => areas.slice(0, 50), [areas]);
|
|
132
|
+
const count = safeAreas.length;
|
|
133
|
+
const palette = react.useMemo(() => generateColorPalette(count), [count]);
|
|
134
|
+
const resolvedColors = react.useMemo(() => resolveColors(safeAreas, palette), [safeAreas, palette]);
|
|
135
|
+
const maxRadius = CENTER * MAX_RADIUS_RATIO * 2;
|
|
136
|
+
const labelRadius = maxRadius + labelPadding;
|
|
137
|
+
if (count === 0)
|
|
138
|
+
return null;
|
|
139
|
+
return (jsxRuntime.jsx("div", { className: className, style: Object.assign({ width: "100%", maxWidth: "600px", aspectRatio: "1 / 1" }, style), children: jsxRuntime.jsxs("svg", { viewBox: `0 0 ${VIEW_BOX_SIZE} ${VIEW_BOX_SIZE}`, xmlns: "http://www.w3.org/2000/svg", style: {
|
|
140
|
+
width: "100%",
|
|
141
|
+
height: "100%",
|
|
142
|
+
display: "block",
|
|
143
|
+
overflow: "visible",
|
|
144
|
+
}, "aria-label": "Life Wheel Chart", role: "img", children: [jsxRuntime.jsx("circle", { cx: CENTER, cy: CENTER, r: maxRadius, fill: "white" }), jsxRuntime.jsx(WheelGrid, { cx: CENTER, cy: CENTER, maxRadius: maxRadius, levels: gridLevels, segmentCount: count, color: gridColor, opacity: gridOpacity }), safeAreas.map((area, i) => {
|
|
145
|
+
const { startAngle, endAngle } = getSegmentAngles(i, count);
|
|
146
|
+
return (jsxRuntime.jsx(WheelSegment, { cx: CENTER, cy: CENTER, maxRadius: maxRadius, value: area.value, startAngle: startAngle, endAngle: endAngle, color: resolvedColors[i], strokeColor: strokeColor, strokeWidth: strokeWidth, onClick: onAreaClick ? () => onAreaClick(area, i) : undefined }, `segment-${i}`));
|
|
147
|
+
}), jsxRuntime.jsx("circle", { cx: CENTER, cy: CENTER, r: maxRadius, fill: "none", stroke: gridColor, strokeOpacity: gridOpacity, strokeWidth: 0.8 }), safeAreas.map((area, i) => {
|
|
148
|
+
const { midAngle } = getSegmentAngles(i, count);
|
|
149
|
+
return (jsxRuntime.jsx(WheelLabel, { cx: CENTER, cy: CENTER, labelRadius: labelRadius, midAngle: midAngle, label: area.label, fontSize: labelFontSize, color: labelColor }, `label-${i}`));
|
|
150
|
+
})] }) }));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
exports.LifeWheel = LifeWheel;
|
|
154
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/utils/colorPalette.ts","../src/utils/geometry.ts","../src/components/WheelGrid.tsx","../src/components/WheelSegment.tsx","../src/components/WheelLabel.tsx","../src/components/LifeWheel.tsx"],"sourcesContent":["/**\n * Generates a visually distinct chromatic palette in HSL.\n * Distributes hues evenly across the color wheel.\n */\nexport function generateColorPalette(count: number): string[] {\n return Array.from({ length: count }, (_, i) => {\n const hue = Math.round((i / count) * 360);\n const saturation = 65 + (i % 3) * 5; // slight variation: 65, 70, 75\n const lightness = 55 + (i % 2) * 5; // slight variation: 55, 60\n return `hsl(${hue}, ${saturation}%, ${lightness}%)`;\n });\n}\n\n/**\n * Merges user-provided colors with auto-generated ones.\n * Areas without a color receive one from the palette.\n */\nexport function resolveColors(\n areas: Array<{ color?: string }>,\n palette: string[],\n): string[] {\n return areas.map(\n (area, i) => area.color ?? palette[i] ?? palette[i % palette.length],\n );\n}\n","export interface Point {\n x: number;\n y: number;\n}\n\n/** Converts polar coordinates to Cartesian. Angle in radians. */\nexport function polarToCartesian(\n cx: number,\n cy: number,\n radius: number,\n angleRad: number,\n): Point {\n return {\n x: cx + radius * Math.cos(angleRad),\n y: cy + radius * Math.sin(angleRad),\n };\n}\n\n/**\n * Returns the SVG path string for a filled pie segment.\n * Handles the edge case where a full circle (value=10, single area) is drawn correctly.\n */\nexport function buildSegmentPath(\n cx: number,\n cy: number,\n radius: number,\n startAngle: number,\n endAngle: number,\n): string {\n if (radius <= 0) return \"\";\n\n const angleDelta = endAngle - startAngle;\n const isFullCircle = Math.abs(angleDelta - Math.PI * 2) < 1e-6;\n\n if (isFullCircle) {\n // SVG arcs cannot span exactly 360°, so we split into two halves\n const mid = startAngle + Math.PI;\n const p1 = polarToCartesian(cx, cy, radius, startAngle);\n const p2 = polarToCartesian(cx, cy, radius, mid);\n return [\n `M ${cx} ${cy}`,\n `L ${p1.x} ${p1.y}`,\n `A ${radius} ${radius} 0 0 1 ${p2.x} ${p2.y}`,\n `A ${radius} ${radius} 0 0 1 ${p1.x} ${p1.y}`,\n \"Z\",\n ].join(\" \");\n }\n\n const start = polarToCartesian(cx, cy, radius, startAngle);\n const end = polarToCartesian(cx, cy, radius, endAngle);\n const largeArcFlag = angleDelta > Math.PI ? 1 : 0;\n\n return [\n `M ${cx} ${cy}`,\n `L ${start.x} ${start.y}`,\n `A ${radius} ${radius} 0 ${largeArcFlag} 1 ${end.x} ${end.y}`,\n \"Z\",\n ].join(\" \");\n}\n\n/** Derives SVG text-anchor and dominant-baseline from an angle. */\nexport function getLabelAlignment(angleRad: number): {\n textAnchor: string;\n dominantBaseline: string;\n} {\n const cos = Math.cos(angleRad);\n const sin = Math.sin(angleRad);\n\n const textAnchor = cos > 0.3 ? \"start\" : cos < -0.3 ? \"end\" : \"middle\";\n\n const dominantBaseline =\n sin > 0.3 ? \"hanging\" : sin < -0.3 ? \"auto\" : \"middle\";\n\n return { textAnchor, dominantBaseline };\n}\n\n/** Computes start angle, end angle, and mid angle (in radians) for segment i of n. */\nexport function getSegmentAngles(i: number, n: number) {\n const slice = (2 * Math.PI) / n;\n const offset = -Math.PI / 2; // start from top\n const startAngle = i * slice + offset;\n const endAngle = (i + 1) * slice + offset;\n const midAngle = startAngle + slice / 2;\n return { startAngle, endAngle, midAngle };\n}\n","import { polarToCartesian } from \"../utils/geometry\";\n\ninterface WheelGridProps {\n cx: number;\n cy: number;\n maxRadius: number;\n levels: number;\n segmentCount: number;\n color: string;\n opacity: number;\n}\n\nexport function WheelGrid({\n cx,\n cy,\n maxRadius,\n levels,\n segmentCount,\n color,\n opacity,\n}: WheelGridProps) {\n const radii = Array.from(\n { length: levels },\n (_, i) => ((i + 1) / levels) * maxRadius,\n );\n\n const radialLines = Array.from({ length: segmentCount }, (_, i) => {\n const angle = (i / segmentCount) * 2 * Math.PI - Math.PI / 2;\n const end = polarToCartesian(cx, cy, maxRadius, angle);\n return { x2: end.x, y2: end.y };\n });\n\n return (\n <g stroke={color} strokeOpacity={opacity} fill=\"none\">\n {radii.map((r) => (\n <circle key={r} cx={cx} cy={cy} r={r} strokeWidth={0.8} />\n ))}\n\n {radialLines.map((line, i) => (\n <line\n key={i}\n x1={cx}\n y1={cy}\n x2={line.x2}\n y2={line.y2}\n strokeWidth={0.8}\n />\n ))}\n </g>\n );\n}\n","import { buildSegmentPath } from \"../utils/geometry\";\n\ninterface WheelSegmentProps {\n cx: number;\n cy: number;\n maxRadius: number;\n value: number;\n startAngle: number;\n endAngle: number;\n color: string;\n strokeColor: string;\n strokeWidth: number;\n onClick?: () => void;\n}\n\nexport function WheelSegment({\n cx,\n cy,\n maxRadius,\n value,\n startAngle,\n endAngle,\n color,\n strokeColor,\n strokeWidth,\n onClick,\n}: WheelSegmentProps) {\n const clampedValue = Math.min(10, Math.max(0, value));\n const radius = (clampedValue / 10) * maxRadius;\n\n if (radius <= 0) return null;\n\n const d = buildSegmentPath(cx, cy, radius, startAngle, endAngle);\n\n return (\n <path\n d={d}\n fill={color}\n stroke={strokeColor}\n strokeWidth={strokeWidth}\n strokeLinejoin=\"round\"\n onClick={onClick}\n style={{\n cursor: onClick ? \"pointer\" : \"default\",\n transition: \"opacity 0.2s\",\n mixBlendMode: \"multiply\",\n }}\n onMouseEnter={(e) => {\n (e.currentTarget as SVGPathElement).style.opacity = \"0.85\";\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as SVGPathElement).style.opacity = \"1\";\n }}\n />\n );\n}\n","import { polarToCartesian, getLabelAlignment } from \"../utils/geometry\";\n\ninterface WheelLabelProps {\n cx: number;\n cy: number;\n labelRadius: number;\n midAngle: number;\n label: string;\n fontSize: number;\n color: string;\n}\n\nexport function WheelLabel({\n cx,\n cy,\n labelRadius,\n midAngle,\n label,\n fontSize,\n color,\n}: WheelLabelProps) {\n const position = polarToCartesian(cx, cy, labelRadius, midAngle);\n const { textAnchor, dominantBaseline } = getLabelAlignment(midAngle);\n const alignedTextAnchor = textAnchor as\n | \"start\"\n | \"middle\"\n | \"end\"\n | \"inherit\";\n const alignedDominantBaseline = dominantBaseline as\n | \"middle\"\n | \"inherit\"\n | \"auto\"\n | \"use-script\"\n | \"no-change\"\n | \"reset-size\"\n | \"ideographic\"\n | \"alphabetic\"\n | \"hanging\"\n | \"mathematical\"\n | \"central\"\n | \"text-after-edge\"\n | \"text-before-edge\"\n | undefined;\n\n const words = label.split(\" \");\n const lines =\n words.length > 2\n ? [\n words.slice(0, Math.ceil(words.length / 2)).join(\" \"),\n words.slice(Math.ceil(words.length / 2)).join(\" \"),\n ]\n : [label];\n\n const lineHeight = fontSize * 1.2;\n\n return (\n <text\n x={position.x}\n y={position.y}\n textAnchor={alignedTextAnchor}\n dominantBaseline={alignedDominantBaseline}\n fontSize={fontSize}\n fill={color}\n fontFamily=\"system-ui, -apple-system, sans-serif\"\n >\n {lines.map((line, i) => (\n <tspan key={i} x={position.x} dy={i === 0 ? 0 : lineHeight}>\n {line}\n </tspan>\n ))}\n </text>\n );\n}\n","import { useMemo } from \"react\";\nimport type { LifeWheelProps } from \"../types\";\nimport { generateColorPalette, resolveColors } from \"../utils/colorPalette\";\nimport { getSegmentAngles } from \"../utils/geometry\";\nimport { WheelGrid } from \"./WheelGrid\";\nimport { WheelSegment } from \"./WheelSegment\";\nimport { WheelLabel } from \"./WheelLabel\";\n\nconst VIEW_BOX_SIZE = 300;\nconst CENTER = VIEW_BOX_SIZE / 2;\nconst MAX_RADIUS_RATIO = 0.38; // fraction of viewBox half-size used for the wheel\nconst LABEL_PADDING_DEFAULT = 12;\n\nexport function LifeWheel({\n areas,\n gridLevels = 10,\n gridColor = \"#d1d5db\",\n gridOpacity = 0.6,\n strokeColor = \"white\",\n strokeWidth = 1.5,\n labelFontSize = 9,\n labelColor = \"#374151\",\n labelPadding = LABEL_PADDING_DEFAULT,\n className,\n style,\n onAreaClick,\n}: LifeWheelProps) {\n const safeAreas = useMemo(() => areas.slice(0, 50), [areas]);\n const count = safeAreas.length;\n\n const palette = useMemo(() => generateColorPalette(count), [count]);\n const resolvedColors = useMemo(\n () => resolveColors(safeAreas, palette),\n [safeAreas, palette],\n );\n\n const maxRadius = CENTER * MAX_RADIUS_RATIO * 2;\n const labelRadius = maxRadius + labelPadding;\n\n if (count === 0) return null;\n\n return (\n <div\n className={className}\n style={{\n width: \"100%\",\n maxWidth: \"600px\",\n aspectRatio: \"1 / 1\",\n ...style,\n }}\n >\n <svg\n viewBox={`0 0 ${VIEW_BOX_SIZE} ${VIEW_BOX_SIZE}`}\n xmlns=\"http://www.w3.org/2000/svg\"\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"block\",\n overflow: \"visible\",\n }}\n aria-label=\"Life Wheel Chart\"\n role=\"img\"\n >\n <circle cx={CENTER} cy={CENTER} r={maxRadius} fill=\"white\" />\n\n <WheelGrid\n cx={CENTER}\n cy={CENTER}\n maxRadius={maxRadius}\n levels={gridLevels}\n segmentCount={count}\n color={gridColor}\n opacity={gridOpacity}\n />\n\n {safeAreas.map((area, i) => {\n const { startAngle, endAngle } = getSegmentAngles(i, count);\n return (\n <WheelSegment\n key={`segment-${i}`}\n cx={CENTER}\n cy={CENTER}\n maxRadius={maxRadius}\n value={area.value}\n startAngle={startAngle}\n endAngle={endAngle}\n color={resolvedColors[i]}\n strokeColor={strokeColor}\n strokeWidth={strokeWidth}\n onClick={onAreaClick ? () => onAreaClick(area, i) : undefined}\n />\n );\n })}\n\n <circle\n cx={CENTER}\n cy={CENTER}\n r={maxRadius}\n fill=\"none\"\n stroke={gridColor}\n strokeOpacity={gridOpacity}\n strokeWidth={0.8}\n />\n\n {safeAreas.map((area, i) => {\n const { midAngle } = getSegmentAngles(i, count);\n return (\n <WheelLabel\n key={`label-${i}`}\n cx={CENTER}\n cy={CENTER}\n labelRadius={labelRadius}\n midAngle={midAngle}\n label={area.label}\n fontSize={labelFontSize}\n color={labelColor}\n />\n );\n })}\n </svg>\n </div>\n );\n}\n"],"names":["_jsxs","_jsx","useMemo"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
interface LifeArea {
|
|
5
|
+
/** Display label for the area */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Score from 0 to 10 */
|
|
8
|
+
value: number;
|
|
9
|
+
/** Optional hex/hsl/rgb color. Auto-generated if omitted. */
|
|
10
|
+
color?: string;
|
|
11
|
+
}
|
|
12
|
+
interface LifeWheelProps {
|
|
13
|
+
/** Array of life areas (1–50) */
|
|
14
|
+
areas: LifeArea[];
|
|
15
|
+
/** Number of concentric grid rings (default: 10) */
|
|
16
|
+
gridLevels?: number;
|
|
17
|
+
/** Grid line color (default: "#d1d5db") */
|
|
18
|
+
gridColor?: string;
|
|
19
|
+
/** Grid line opacity (default: 0.6) */
|
|
20
|
+
gridOpacity?: number;
|
|
21
|
+
/** Segment stroke color (default: "white") */
|
|
22
|
+
strokeColor?: string;
|
|
23
|
+
/** Segment stroke width in SVG units (default: 1.5) */
|
|
24
|
+
strokeWidth?: number;
|
|
25
|
+
/** Font size for labels in SVG units (default: 10) */
|
|
26
|
+
labelFontSize?: number;
|
|
27
|
+
/** Label text color (default: "#374151") */
|
|
28
|
+
labelColor?: string;
|
|
29
|
+
/** Extra padding between wheel and label (default: 12) */
|
|
30
|
+
labelPadding?: number;
|
|
31
|
+
/** Optional className for the container div */
|
|
32
|
+
className?: string;
|
|
33
|
+
/** Optional inline styles for the container div */
|
|
34
|
+
style?: CSSProperties;
|
|
35
|
+
/** Callback when a segment is clicked */
|
|
36
|
+
onAreaClick?: (area: LifeArea, index: number) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare function LifeWheel({ areas, gridLevels, gridColor, gridOpacity, strokeColor, strokeWidth, labelFontSize, labelColor, labelPadding, className, style, onAreaClick, }: LifeWheelProps): react_jsx_runtime.JSX.Element | null;
|
|
40
|
+
|
|
41
|
+
export { LifeWheel };
|
|
42
|
+
export type { LifeArea, LifeWheelProps };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates a visually distinct chromatic palette in HSL.
|
|
6
|
+
* Distributes hues evenly across the color wheel.
|
|
7
|
+
*/
|
|
8
|
+
function generateColorPalette(count) {
|
|
9
|
+
return Array.from({ length: count }, (_, i) => {
|
|
10
|
+
const hue = Math.round((i / count) * 360);
|
|
11
|
+
const saturation = 65 + (i % 3) * 5; // slight variation: 65, 70, 75
|
|
12
|
+
const lightness = 55 + (i % 2) * 5; // slight variation: 55, 60
|
|
13
|
+
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Merges user-provided colors with auto-generated ones.
|
|
18
|
+
* Areas without a color receive one from the palette.
|
|
19
|
+
*/
|
|
20
|
+
function resolveColors(areas, palette) {
|
|
21
|
+
return areas.map((area, i) => { var _a, _b; return (_b = (_a = area.color) !== null && _a !== void 0 ? _a : palette[i]) !== null && _b !== void 0 ? _b : palette[i % palette.length]; });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Converts polar coordinates to Cartesian. Angle in radians. */
|
|
25
|
+
function polarToCartesian(cx, cy, radius, angleRad) {
|
|
26
|
+
return {
|
|
27
|
+
x: cx + radius * Math.cos(angleRad),
|
|
28
|
+
y: cy + radius * Math.sin(angleRad),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns the SVG path string for a filled pie segment.
|
|
33
|
+
* Handles the edge case where a full circle (value=10, single area) is drawn correctly.
|
|
34
|
+
*/
|
|
35
|
+
function buildSegmentPath(cx, cy, radius, startAngle, endAngle) {
|
|
36
|
+
if (radius <= 0)
|
|
37
|
+
return "";
|
|
38
|
+
const angleDelta = endAngle - startAngle;
|
|
39
|
+
const isFullCircle = Math.abs(angleDelta - Math.PI * 2) < 1e-6;
|
|
40
|
+
if (isFullCircle) {
|
|
41
|
+
// SVG arcs cannot span exactly 360°, so we split into two halves
|
|
42
|
+
const mid = startAngle + Math.PI;
|
|
43
|
+
const p1 = polarToCartesian(cx, cy, radius, startAngle);
|
|
44
|
+
const p2 = polarToCartesian(cx, cy, radius, mid);
|
|
45
|
+
return [
|
|
46
|
+
`M ${cx} ${cy}`,
|
|
47
|
+
`L ${p1.x} ${p1.y}`,
|
|
48
|
+
`A ${radius} ${radius} 0 0 1 ${p2.x} ${p2.y}`,
|
|
49
|
+
`A ${radius} ${radius} 0 0 1 ${p1.x} ${p1.y}`,
|
|
50
|
+
"Z",
|
|
51
|
+
].join(" ");
|
|
52
|
+
}
|
|
53
|
+
const start = polarToCartesian(cx, cy, radius, startAngle);
|
|
54
|
+
const end = polarToCartesian(cx, cy, radius, endAngle);
|
|
55
|
+
const largeArcFlag = angleDelta > Math.PI ? 1 : 0;
|
|
56
|
+
return [
|
|
57
|
+
`M ${cx} ${cy}`,
|
|
58
|
+
`L ${start.x} ${start.y}`,
|
|
59
|
+
`A ${radius} ${radius} 0 ${largeArcFlag} 1 ${end.x} ${end.y}`,
|
|
60
|
+
"Z",
|
|
61
|
+
].join(" ");
|
|
62
|
+
}
|
|
63
|
+
/** Derives SVG text-anchor and dominant-baseline from an angle. */
|
|
64
|
+
function getLabelAlignment(angleRad) {
|
|
65
|
+
const cos = Math.cos(angleRad);
|
|
66
|
+
const sin = Math.sin(angleRad);
|
|
67
|
+
const textAnchor = cos > 0.3 ? "start" : cos < -0.3 ? "end" : "middle";
|
|
68
|
+
const dominantBaseline = sin > 0.3 ? "hanging" : sin < -0.3 ? "auto" : "middle";
|
|
69
|
+
return { textAnchor, dominantBaseline };
|
|
70
|
+
}
|
|
71
|
+
/** Computes start angle, end angle, and mid angle (in radians) for segment i of n. */
|
|
72
|
+
function getSegmentAngles(i, n) {
|
|
73
|
+
const slice = (2 * Math.PI) / n;
|
|
74
|
+
const offset = -Math.PI / 2; // start from top
|
|
75
|
+
const startAngle = i * slice + offset;
|
|
76
|
+
const endAngle = (i + 1) * slice + offset;
|
|
77
|
+
const midAngle = startAngle + slice / 2;
|
|
78
|
+
return { startAngle, endAngle, midAngle };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function WheelGrid({ cx, cy, maxRadius, levels, segmentCount, color, opacity, }) {
|
|
82
|
+
const radii = Array.from({ length: levels }, (_, i) => ((i + 1) / levels) * maxRadius);
|
|
83
|
+
const radialLines = Array.from({ length: segmentCount }, (_, i) => {
|
|
84
|
+
const angle = (i / segmentCount) * 2 * Math.PI - Math.PI / 2;
|
|
85
|
+
const end = polarToCartesian(cx, cy, maxRadius, angle);
|
|
86
|
+
return { x2: end.x, y2: end.y };
|
|
87
|
+
});
|
|
88
|
+
return (jsxs("g", { stroke: color, strokeOpacity: opacity, fill: "none", children: [radii.map((r) => (jsx("circle", { cx: cx, cy: cy, r: r, strokeWidth: 0.8 }, r))), radialLines.map((line, i) => (jsx("line", { x1: cx, y1: cy, x2: line.x2, y2: line.y2, strokeWidth: 0.8 }, i)))] }));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function WheelSegment({ cx, cy, maxRadius, value, startAngle, endAngle, color, strokeColor, strokeWidth, onClick, }) {
|
|
92
|
+
const clampedValue = Math.min(10, Math.max(0, value));
|
|
93
|
+
const radius = (clampedValue / 10) * maxRadius;
|
|
94
|
+
if (radius <= 0)
|
|
95
|
+
return null;
|
|
96
|
+
const d = buildSegmentPath(cx, cy, radius, startAngle, endAngle);
|
|
97
|
+
return (jsx("path", { d: d, fill: color, stroke: strokeColor, strokeWidth: strokeWidth, strokeLinejoin: "round", onClick: onClick, style: {
|
|
98
|
+
cursor: onClick ? "pointer" : "default",
|
|
99
|
+
transition: "opacity 0.2s",
|
|
100
|
+
mixBlendMode: "multiply",
|
|
101
|
+
}, onMouseEnter: (e) => {
|
|
102
|
+
e.currentTarget.style.opacity = "0.85";
|
|
103
|
+
}, onMouseLeave: (e) => {
|
|
104
|
+
e.currentTarget.style.opacity = "1";
|
|
105
|
+
} }));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function WheelLabel({ cx, cy, labelRadius, midAngle, label, fontSize, color, }) {
|
|
109
|
+
const position = polarToCartesian(cx, cy, labelRadius, midAngle);
|
|
110
|
+
const { textAnchor, dominantBaseline } = getLabelAlignment(midAngle);
|
|
111
|
+
const alignedTextAnchor = textAnchor;
|
|
112
|
+
const alignedDominantBaseline = dominantBaseline;
|
|
113
|
+
const words = label.split(" ");
|
|
114
|
+
const lines = words.length > 2
|
|
115
|
+
? [
|
|
116
|
+
words.slice(0, Math.ceil(words.length / 2)).join(" "),
|
|
117
|
+
words.slice(Math.ceil(words.length / 2)).join(" "),
|
|
118
|
+
]
|
|
119
|
+
: [label];
|
|
120
|
+
const lineHeight = fontSize * 1.2;
|
|
121
|
+
return (jsx("text", { x: position.x, y: position.y, textAnchor: alignedTextAnchor, dominantBaseline: alignedDominantBaseline, fontSize: fontSize, fill: color, fontFamily: "system-ui, -apple-system, sans-serif", children: lines.map((line, i) => (jsx("tspan", { x: position.x, dy: i === 0 ? 0 : lineHeight, children: line }, i))) }));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const VIEW_BOX_SIZE = 300;
|
|
125
|
+
const CENTER = VIEW_BOX_SIZE / 2;
|
|
126
|
+
const MAX_RADIUS_RATIO = 0.38; // fraction of viewBox half-size used for the wheel
|
|
127
|
+
const LABEL_PADDING_DEFAULT = 12;
|
|
128
|
+
function LifeWheel({ areas, gridLevels = 10, gridColor = "#d1d5db", gridOpacity = 0.6, strokeColor = "white", strokeWidth = 1.5, labelFontSize = 9, labelColor = "#374151", labelPadding = LABEL_PADDING_DEFAULT, className, style, onAreaClick, }) {
|
|
129
|
+
const safeAreas = useMemo(() => areas.slice(0, 50), [areas]);
|
|
130
|
+
const count = safeAreas.length;
|
|
131
|
+
const palette = useMemo(() => generateColorPalette(count), [count]);
|
|
132
|
+
const resolvedColors = useMemo(() => resolveColors(safeAreas, palette), [safeAreas, palette]);
|
|
133
|
+
const maxRadius = CENTER * MAX_RADIUS_RATIO * 2;
|
|
134
|
+
const labelRadius = maxRadius + labelPadding;
|
|
135
|
+
if (count === 0)
|
|
136
|
+
return null;
|
|
137
|
+
return (jsx("div", { className: className, style: Object.assign({ width: "100%", maxWidth: "600px", aspectRatio: "1 / 1" }, style), children: jsxs("svg", { viewBox: `0 0 ${VIEW_BOX_SIZE} ${VIEW_BOX_SIZE}`, xmlns: "http://www.w3.org/2000/svg", style: {
|
|
138
|
+
width: "100%",
|
|
139
|
+
height: "100%",
|
|
140
|
+
display: "block",
|
|
141
|
+
overflow: "visible",
|
|
142
|
+
}, "aria-label": "Life Wheel Chart", role: "img", children: [jsx("circle", { cx: CENTER, cy: CENTER, r: maxRadius, fill: "white" }), jsx(WheelGrid, { cx: CENTER, cy: CENTER, maxRadius: maxRadius, levels: gridLevels, segmentCount: count, color: gridColor, opacity: gridOpacity }), safeAreas.map((area, i) => {
|
|
143
|
+
const { startAngle, endAngle } = getSegmentAngles(i, count);
|
|
144
|
+
return (jsx(WheelSegment, { cx: CENTER, cy: CENTER, maxRadius: maxRadius, value: area.value, startAngle: startAngle, endAngle: endAngle, color: resolvedColors[i], strokeColor: strokeColor, strokeWidth: strokeWidth, onClick: onAreaClick ? () => onAreaClick(area, i) : undefined }, `segment-${i}`));
|
|
145
|
+
}), jsx("circle", { cx: CENTER, cy: CENTER, r: maxRadius, fill: "none", stroke: gridColor, strokeOpacity: gridOpacity, strokeWidth: 0.8 }), safeAreas.map((area, i) => {
|
|
146
|
+
const { midAngle } = getSegmentAngles(i, count);
|
|
147
|
+
return (jsx(WheelLabel, { cx: CENTER, cy: CENTER, labelRadius: labelRadius, midAngle: midAngle, label: area.label, fontSize: labelFontSize, color: labelColor }, `label-${i}`));
|
|
148
|
+
})] }) }));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { LifeWheel };
|
|
152
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/utils/colorPalette.ts","../src/utils/geometry.ts","../src/components/WheelGrid.tsx","../src/components/WheelSegment.tsx","../src/components/WheelLabel.tsx","../src/components/LifeWheel.tsx"],"sourcesContent":["/**\n * Generates a visually distinct chromatic palette in HSL.\n * Distributes hues evenly across the color wheel.\n */\nexport function generateColorPalette(count: number): string[] {\n return Array.from({ length: count }, (_, i) => {\n const hue = Math.round((i / count) * 360);\n const saturation = 65 + (i % 3) * 5; // slight variation: 65, 70, 75\n const lightness = 55 + (i % 2) * 5; // slight variation: 55, 60\n return `hsl(${hue}, ${saturation}%, ${lightness}%)`;\n });\n}\n\n/**\n * Merges user-provided colors with auto-generated ones.\n * Areas without a color receive one from the palette.\n */\nexport function resolveColors(\n areas: Array<{ color?: string }>,\n palette: string[],\n): string[] {\n return areas.map(\n (area, i) => area.color ?? palette[i] ?? palette[i % palette.length],\n );\n}\n","export interface Point {\n x: number;\n y: number;\n}\n\n/** Converts polar coordinates to Cartesian. Angle in radians. */\nexport function polarToCartesian(\n cx: number,\n cy: number,\n radius: number,\n angleRad: number,\n): Point {\n return {\n x: cx + radius * Math.cos(angleRad),\n y: cy + radius * Math.sin(angleRad),\n };\n}\n\n/**\n * Returns the SVG path string for a filled pie segment.\n * Handles the edge case where a full circle (value=10, single area) is drawn correctly.\n */\nexport function buildSegmentPath(\n cx: number,\n cy: number,\n radius: number,\n startAngle: number,\n endAngle: number,\n): string {\n if (radius <= 0) return \"\";\n\n const angleDelta = endAngle - startAngle;\n const isFullCircle = Math.abs(angleDelta - Math.PI * 2) < 1e-6;\n\n if (isFullCircle) {\n // SVG arcs cannot span exactly 360°, so we split into two halves\n const mid = startAngle + Math.PI;\n const p1 = polarToCartesian(cx, cy, radius, startAngle);\n const p2 = polarToCartesian(cx, cy, radius, mid);\n return [\n `M ${cx} ${cy}`,\n `L ${p1.x} ${p1.y}`,\n `A ${radius} ${radius} 0 0 1 ${p2.x} ${p2.y}`,\n `A ${radius} ${radius} 0 0 1 ${p1.x} ${p1.y}`,\n \"Z\",\n ].join(\" \");\n }\n\n const start = polarToCartesian(cx, cy, radius, startAngle);\n const end = polarToCartesian(cx, cy, radius, endAngle);\n const largeArcFlag = angleDelta > Math.PI ? 1 : 0;\n\n return [\n `M ${cx} ${cy}`,\n `L ${start.x} ${start.y}`,\n `A ${radius} ${radius} 0 ${largeArcFlag} 1 ${end.x} ${end.y}`,\n \"Z\",\n ].join(\" \");\n}\n\n/** Derives SVG text-anchor and dominant-baseline from an angle. */\nexport function getLabelAlignment(angleRad: number): {\n textAnchor: string;\n dominantBaseline: string;\n} {\n const cos = Math.cos(angleRad);\n const sin = Math.sin(angleRad);\n\n const textAnchor = cos > 0.3 ? \"start\" : cos < -0.3 ? \"end\" : \"middle\";\n\n const dominantBaseline =\n sin > 0.3 ? \"hanging\" : sin < -0.3 ? \"auto\" : \"middle\";\n\n return { textAnchor, dominantBaseline };\n}\n\n/** Computes start angle, end angle, and mid angle (in radians) for segment i of n. */\nexport function getSegmentAngles(i: number, n: number) {\n const slice = (2 * Math.PI) / n;\n const offset = -Math.PI / 2; // start from top\n const startAngle = i * slice + offset;\n const endAngle = (i + 1) * slice + offset;\n const midAngle = startAngle + slice / 2;\n return { startAngle, endAngle, midAngle };\n}\n","import { polarToCartesian } from \"../utils/geometry\";\n\ninterface WheelGridProps {\n cx: number;\n cy: number;\n maxRadius: number;\n levels: number;\n segmentCount: number;\n color: string;\n opacity: number;\n}\n\nexport function WheelGrid({\n cx,\n cy,\n maxRadius,\n levels,\n segmentCount,\n color,\n opacity,\n}: WheelGridProps) {\n const radii = Array.from(\n { length: levels },\n (_, i) => ((i + 1) / levels) * maxRadius,\n );\n\n const radialLines = Array.from({ length: segmentCount }, (_, i) => {\n const angle = (i / segmentCount) * 2 * Math.PI - Math.PI / 2;\n const end = polarToCartesian(cx, cy, maxRadius, angle);\n return { x2: end.x, y2: end.y };\n });\n\n return (\n <g stroke={color} strokeOpacity={opacity} fill=\"none\">\n {radii.map((r) => (\n <circle key={r} cx={cx} cy={cy} r={r} strokeWidth={0.8} />\n ))}\n\n {radialLines.map((line, i) => (\n <line\n key={i}\n x1={cx}\n y1={cy}\n x2={line.x2}\n y2={line.y2}\n strokeWidth={0.8}\n />\n ))}\n </g>\n );\n}\n","import { buildSegmentPath } from \"../utils/geometry\";\n\ninterface WheelSegmentProps {\n cx: number;\n cy: number;\n maxRadius: number;\n value: number;\n startAngle: number;\n endAngle: number;\n color: string;\n strokeColor: string;\n strokeWidth: number;\n onClick?: () => void;\n}\n\nexport function WheelSegment({\n cx,\n cy,\n maxRadius,\n value,\n startAngle,\n endAngle,\n color,\n strokeColor,\n strokeWidth,\n onClick,\n}: WheelSegmentProps) {\n const clampedValue = Math.min(10, Math.max(0, value));\n const radius = (clampedValue / 10) * maxRadius;\n\n if (radius <= 0) return null;\n\n const d = buildSegmentPath(cx, cy, radius, startAngle, endAngle);\n\n return (\n <path\n d={d}\n fill={color}\n stroke={strokeColor}\n strokeWidth={strokeWidth}\n strokeLinejoin=\"round\"\n onClick={onClick}\n style={{\n cursor: onClick ? \"pointer\" : \"default\",\n transition: \"opacity 0.2s\",\n mixBlendMode: \"multiply\",\n }}\n onMouseEnter={(e) => {\n (e.currentTarget as SVGPathElement).style.opacity = \"0.85\";\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as SVGPathElement).style.opacity = \"1\";\n }}\n />\n );\n}\n","import { polarToCartesian, getLabelAlignment } from \"../utils/geometry\";\n\ninterface WheelLabelProps {\n cx: number;\n cy: number;\n labelRadius: number;\n midAngle: number;\n label: string;\n fontSize: number;\n color: string;\n}\n\nexport function WheelLabel({\n cx,\n cy,\n labelRadius,\n midAngle,\n label,\n fontSize,\n color,\n}: WheelLabelProps) {\n const position = polarToCartesian(cx, cy, labelRadius, midAngle);\n const { textAnchor, dominantBaseline } = getLabelAlignment(midAngle);\n const alignedTextAnchor = textAnchor as\n | \"start\"\n | \"middle\"\n | \"end\"\n | \"inherit\";\n const alignedDominantBaseline = dominantBaseline as\n | \"middle\"\n | \"inherit\"\n | \"auto\"\n | \"use-script\"\n | \"no-change\"\n | \"reset-size\"\n | \"ideographic\"\n | \"alphabetic\"\n | \"hanging\"\n | \"mathematical\"\n | \"central\"\n | \"text-after-edge\"\n | \"text-before-edge\"\n | undefined;\n\n const words = label.split(\" \");\n const lines =\n words.length > 2\n ? [\n words.slice(0, Math.ceil(words.length / 2)).join(\" \"),\n words.slice(Math.ceil(words.length / 2)).join(\" \"),\n ]\n : [label];\n\n const lineHeight = fontSize * 1.2;\n\n return (\n <text\n x={position.x}\n y={position.y}\n textAnchor={alignedTextAnchor}\n dominantBaseline={alignedDominantBaseline}\n fontSize={fontSize}\n fill={color}\n fontFamily=\"system-ui, -apple-system, sans-serif\"\n >\n {lines.map((line, i) => (\n <tspan key={i} x={position.x} dy={i === 0 ? 0 : lineHeight}>\n {line}\n </tspan>\n ))}\n </text>\n );\n}\n","import { useMemo } from \"react\";\nimport type { LifeWheelProps } from \"../types\";\nimport { generateColorPalette, resolveColors } from \"../utils/colorPalette\";\nimport { getSegmentAngles } from \"../utils/geometry\";\nimport { WheelGrid } from \"./WheelGrid\";\nimport { WheelSegment } from \"./WheelSegment\";\nimport { WheelLabel } from \"./WheelLabel\";\n\nconst VIEW_BOX_SIZE = 300;\nconst CENTER = VIEW_BOX_SIZE / 2;\nconst MAX_RADIUS_RATIO = 0.38; // fraction of viewBox half-size used for the wheel\nconst LABEL_PADDING_DEFAULT = 12;\n\nexport function LifeWheel({\n areas,\n gridLevels = 10,\n gridColor = \"#d1d5db\",\n gridOpacity = 0.6,\n strokeColor = \"white\",\n strokeWidth = 1.5,\n labelFontSize = 9,\n labelColor = \"#374151\",\n labelPadding = LABEL_PADDING_DEFAULT,\n className,\n style,\n onAreaClick,\n}: LifeWheelProps) {\n const safeAreas = useMemo(() => areas.slice(0, 50), [areas]);\n const count = safeAreas.length;\n\n const palette = useMemo(() => generateColorPalette(count), [count]);\n const resolvedColors = useMemo(\n () => resolveColors(safeAreas, palette),\n [safeAreas, palette],\n );\n\n const maxRadius = CENTER * MAX_RADIUS_RATIO * 2;\n const labelRadius = maxRadius + labelPadding;\n\n if (count === 0) return null;\n\n return (\n <div\n className={className}\n style={{\n width: \"100%\",\n maxWidth: \"600px\",\n aspectRatio: \"1 / 1\",\n ...style,\n }}\n >\n <svg\n viewBox={`0 0 ${VIEW_BOX_SIZE} ${VIEW_BOX_SIZE}`}\n xmlns=\"http://www.w3.org/2000/svg\"\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"block\",\n overflow: \"visible\",\n }}\n aria-label=\"Life Wheel Chart\"\n role=\"img\"\n >\n <circle cx={CENTER} cy={CENTER} r={maxRadius} fill=\"white\" />\n\n <WheelGrid\n cx={CENTER}\n cy={CENTER}\n maxRadius={maxRadius}\n levels={gridLevels}\n segmentCount={count}\n color={gridColor}\n opacity={gridOpacity}\n />\n\n {safeAreas.map((area, i) => {\n const { startAngle, endAngle } = getSegmentAngles(i, count);\n return (\n <WheelSegment\n key={`segment-${i}`}\n cx={CENTER}\n cy={CENTER}\n maxRadius={maxRadius}\n value={area.value}\n startAngle={startAngle}\n endAngle={endAngle}\n color={resolvedColors[i]}\n strokeColor={strokeColor}\n strokeWidth={strokeWidth}\n onClick={onAreaClick ? () => onAreaClick(area, i) : undefined}\n />\n );\n })}\n\n <circle\n cx={CENTER}\n cy={CENTER}\n r={maxRadius}\n fill=\"none\"\n stroke={gridColor}\n strokeOpacity={gridOpacity}\n strokeWidth={0.8}\n />\n\n {safeAreas.map((area, i) => {\n const { midAngle } = getSegmentAngles(i, count);\n return (\n <WheelLabel\n key={`label-${i}`}\n cx={CENTER}\n cy={CENTER}\n labelRadius={labelRadius}\n midAngle={midAngle}\n label={area.label}\n fontSize={labelFontSize}\n color={labelColor}\n />\n );\n })}\n </svg>\n </div>\n );\n}\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rg-life-wheel-chart",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "A dynamic, customizable Life Wheel Chart component for React and Next.js",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.esm.js",
|
|
11
|
+
"require": "./dist/index.cjs.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "rollup -c",
|
|
21
|
+
"dev": "rollup -c --watch",
|
|
22
|
+
"type-check": "tsc --noEmit",
|
|
23
|
+
"preview": "vite --config vite.config.preview.ts",
|
|
24
|
+
"release": "standard-version",
|
|
25
|
+
"prepublishOnly": "npm run type-check && npm run build",
|
|
26
|
+
"publish:public": "npm publish --access public",
|
|
27
|
+
"release:publish": "npm run release && npm run publish:public"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"react",
|
|
31
|
+
"nextjs",
|
|
32
|
+
"life-wheel",
|
|
33
|
+
"chart",
|
|
34
|
+
"wheel-of-life",
|
|
35
|
+
"svg"
|
|
36
|
+
],
|
|
37
|
+
"author": "Eudes Roger <eudesroger7@gmail.com>",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/eudesroger7/rg-life-wheel-chart.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://rg-life-wheel-chart.eudesroger.com",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/eudesroger7/rg-life-wheel-chart/issues"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=17.0.0",
|
|
49
|
+
"react-dom": ">=17.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
53
|
+
"@types/node": "^25.6.0",
|
|
54
|
+
"@types/react": "^18.3.28",
|
|
55
|
+
"@types/react-dom": "^18.3.0",
|
|
56
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
57
|
+
"react": "^18.3.1",
|
|
58
|
+
"react-dom": "^18.3.1",
|
|
59
|
+
"rollup": "^4.18.0",
|
|
60
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
61
|
+
"standard-version": "^9.5.0",
|
|
62
|
+
"tslib": "^2.6.3",
|
|
63
|
+
"typescript": "^5.5.0",
|
|
64
|
+
"vite": "^5.3.0"
|
|
65
|
+
}
|
|
66
|
+
}
|