-
-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Summary
Add an <Arc> React component to wrap Two.js's Arc class from the extras module. This will enable developers to create open arc shapes (partial circles/ellipses) for use in pie charts, progress indicators, and circular UI elements.
Motivation
Two.js provides an Arc class in extras/jsm/arc.js that creates open arc shapes (as opposed to ArcSegment which creates closed pie-slice shapes). This is useful for:
- Progress indicators (loading circles)
- Pie charts and donut charts
- Circular gauges and meters
- Partial circle decorations
- Arc-based animations
Currently, react-two.js includes ArcSegment but not Arc, leaving a gap for developers who need open arc shapes.
Arc vs ArcSegment
Arc (from extras):
- Open arc shape (no filled center)
- Creates a curved line from startAngle to endAngle
- Like drawing part of a circle's outline
- Useful for: progress indicators, gauges, open curves
ArcSegment (core, already in react-two.js):
- Closed pie-slice shape
- Creates a filled wedge with center point
- Like a slice of pizza
- Useful for: pie charts, radar charts, filled sections
// Two.js Arc example
import { Arc } from 'two.js/extras/jsm/arc.js';
const arc = new Arc(0, 0, 100, 100, 0, Math.PI);
// Creates an open semicircle arcProposed API
Component Signature
interface ArcProps {
// Position
x?: number;
y?: number;
// Dimensions
width?: number; // Horizontal diameter
height?: number; // Vertical diameter
// Arc angles (in radians)
startAngle?: number; // Starting angle (default: 0)
endAngle?: number; // Ending angle (default: 2π)
// Path properties
resolution?: number; // Number of vertices (default: 4)
curved?: boolean; // Smooth curve (default: true)
// Styling (inherited from Path)
stroke?: string;
linewidth?: number;
fill?: string; // Usually 'transparent' for arcs
cap?: 'butt' | 'round' | 'square';
join?: 'miter' | 'round' | 'bevel';
opacity?: number;
visible?: boolean;
// Transform
rotation?: number;
scale?: number;
}
export type RefArc = Arc; // From two.js/extras/jsm/arc
export const Arc: React.ForwardRefExoticComponent<
ArcProps & React.RefAttributes<RefArc>
>;Usage Examples
Basic Arc
import { Canvas, Arc } from 'react-two.js';
function BasicArc() {
return (
<Canvas width={400} height={400}>
<Arc
x={200}
y={200}
width={100}
height={100}
startAngle={0}
endAngle={Math.PI}
stroke="blue"
linewidth={3}
fill="transparent"
/>
</Canvas>
);
}Progress Indicator
function ProgressIndicator({ progress }: { progress: number }) {
// progress: 0 to 1
const startAngle = -Math.PI / 2; // Start at top
const endAngle = startAngle + (Math.PI * 2 * progress);
return (
<>
{/* Background arc */}
<Arc
x={100}
y={100}
width={80}
height={80}
startAngle={0}
endAngle={Math.PI * 2}
stroke="lightgray"
linewidth={8}
/>
{/* Progress arc */}
<Arc
x={100}
y={100}
width={80}
height={80}
startAngle={startAngle}
endAngle={endAngle}
stroke="blue"
linewidth={8}
cap="round"
/>
</>
);
}Animated Loading Spinner
function LoadingSpinner() {
const arcRef = useRef<RefArc>(null);
useFrame((elapsed) => {
if (arcRef.current) {
arcRef.current.rotation = elapsed * 2;
}
});
return (
<Arc
ref={arcRef}
x={200}
y={200}
width={60}
height={60}
startAngle={0}
endAngle={Math.PI * 1.5}
stroke="purple"
linewidth={4}
cap="round"
/>
);
}Gauge/Meter
function Gauge({ value, max }: { value: number; max: number }) {
const startAngle = Math.PI * 0.75; // Start at bottom-left
const endAngle = startAngle + (Math.PI * 1.5 * (value / max));
return (
<>
{/* Gauge background */}
<Arc
x={150}
y={150}
width={120}
height={120}
startAngle={Math.PI * 0.75}
endAngle={Math.PI * 2.25}
stroke="#eee"
linewidth={10}
/>
{/* Gauge value */}
<Arc
x={150}
y={150}
width={120}
height={120}
startAngle={startAngle}
endAngle={endAngle}
stroke="green"
linewidth={10}
cap="round"
/>
{/* Center text */}
<Text
value={`${value}/${max}`}
x={150}
y={150}
size={20}
alignment="center"
/>
</>
);
}Elliptical Arc
function EllipticalArc() {
return (
<Arc
x={200}
y={150}
width={150} // Wide
height={80} // Narrow
startAngle={0}
endAngle={Math.PI}
stroke="red"
linewidth={2}
/>
);
}Multiple Arcs (Donut Chart Alternative)
function ArcGallery() {
return (
<>
{[0, 1, 2, 3].map((i) => (
<Arc
key={i}
x={200}
y={200}
width={100}
height={100}
startAngle={(Math.PI / 2) * i}
endAngle={(Math.PI / 2) * (i + 0.8)}
stroke={`hsl(${i * 90}, 70%, 50%)`}
linewidth={20}
cap="round"
/>
))}
</>
);
}Implementation Phases
Phase 1: Core Component Structure
Files: lib/Arc.tsx (new file)
- Create
Arc.tsxwith React component boilerplate - Import Arc class from
two.js/extras/jsm/arc.js - Define
ArcPropsTypeScript interface - Define
RefArctype - Implement component with
forwardRef - Set up state management for Arc instance
- Integrate with
useTwo()context
Deliverable: Basic Arc component skeleton with TypeScript types
Phase 2: Arc Instance Management
Files: lib/Arc.tsx
- Implement
useLayoutEffectfor Arc creation:- Create Arc instance with initial props
- Store in component state
- Handle cleanup on unmount
- Implement parent integration:
- Add Arc to parent group/scene
- Remove on unmount
- Follow existing component patterns (see Circle.tsx)
- Handle Arc-specific initialization:
- Set initial width, height
- Set startAngle, endAngle
- Set resolution
- Set position (x, y)
Deliverable: Arc instances created and added to scene
Phase 3: Props Handling & Updates
Files: lib/Arc.tsx
- Implement
useEffectfor property updates:- Update position (x, y via translation)
- Update dimensions (width, height)
- Update angles (startAngle, endAngle)
- Update resolution
- Update path properties (stroke, linewidth, etc.)
- Handle prop changes reactively
- Apply defaults for undefined props
- Ensure Arc updates trigger re-render in Two.js
- Follow existing patterns from other shape components
Deliverable: Reactive prop updates on Arc component
Phase 4: Ref Forwarding
Files: lib/Arc.tsx
- Implement
useImperativeHandlefor ref forwarding - Expose Arc instance via ref
- Test ref access for animations
- Ensure ref updates when instance changes
- Document ref API and properties available
Deliverable: Working ref forwarding for imperative access
Phase 5: Integration & Export
Files: lib/main.ts
- Export
Arccomponent fromlib/main.ts - Export
RefArctype fromlib/main.ts - Verify tree-shaking works correctly
- Update package exports if needed
- Ensure Arc class is properly imported from Two.js extras
Deliverable: Arc component available in public API
Phase 6: Testing
Files: lib/__tests__/Arc.test.tsx (new file)
- Test component rendering
- Test Arc creation with props
- Test position updates (x, y)
- Test dimension updates (width, height)
- Test angle updates (startAngle, endAngle)
- Test resolution changes
- Test styling props (stroke, linewidth, etc.)
- Test ref forwarding
- Test cleanup on unmount
- Test with parent groups
- Mock Two.js Arc class for isolated testing
Deliverable: Comprehensive test coverage
Phase 7: Documentation & Examples
Files: README.md, src/App.tsx, documentation site
- Add
Arccomponent to README shape list - Document all props with descriptions
- Create interactive examples:
- Basic arc shape
- Progress indicator
- Loading spinner animation
- Gauge/meter
- Elliptical arcs
- Add comparison with ArcSegment
- Document use cases and patterns
- Include performance considerations
- Add troubleshooting section
- TypeScript usage examples
Deliverable: Complete documentation with examples
Technical Considerations
Arc Class Implementation
The Arc class (from Two.js extras):
class Arc extends Path {
constructor(x, y, width, height, startAngle, endAngle, resolution) {
// Creates vertices based on angles
// Curved by default
// Open path (not closed)
}
// Properties: width, height, startAngle, endAngle
// Inherits: stroke, linewidth, cap, join, etc. from Path
}Key characteristics:
- Extends Two.Path (not a primitive)
- Creates vertices based on angle range
- Automatically curved for smooth arcs
- Open path (no automatic closure)
- Resolution controls vertex count (higher = smoother)
Design Decisions
- Component approach: Matches existing shape component patterns
- Extras integration: First component from Two.js extras module
- Angle units: Use radians (Two.js convention)
- Default curved: Arcs are typically curved (can be overridden)
- Stroke-focused: Arcs typically use stroke, not fill
- Ref forwarding: Consistent with other shape components
- TypeScript: Full type safety for all props
Arc vs ArcSegment
| Feature | Arc | ArcSegment |
|---|---|---|
| Shape | Open arc line | Closed pie slice |
| Fill | Typically transparent | Can be filled |
| Use Case | Progress bars, gauges | Pie charts, radar |
| Closure | Open path | Closed path |
| Module | extras/jsm/arc | Core shapes |
| Available | ❌ Missing | ✅ Exists |
Performance Considerations
- Arc vertices calculated based on resolution
- Higher resolution = smoother but more vertices
- Update performance similar to Path component
- Angle changes recalculate vertices
- Consider memoizing angle calculations for animations
Importing from Extras
This will be the first component importing from Two.js extras:
import { Arc } from 'two.js/extras/jsm/arc.js';Considerations:
- Verify extras are included in Two.js package
- Check bundle size impact
- Ensure tree-shaking works with extras
- Test across different module systems
Alternative Approaches Considered
1. Enhance ArcSegment with closed prop:
<ArcSegment closed={false} />Pros: No new component
Cons: Different underlying class, confusing API, different properties
Decision: Separate component is clearer
2. Generic CurvedPath component:
<CurvedPath vertices={...} closed={false} />Pros: More flexible, covers many use cases
Cons: Too low-level, loses Arc-specific conveniences
Decision: Dedicated Arc component is more user-friendly
3. Path with arc helper:
<Path vertices={generateArcVertices(0, Math.PI)} />Pros: Uses existing component
Cons: Manual vertex generation, loses Arc class benefits
Decision: Native Arc component is simpler
Resources
Success Criteria
-
<Arc>component successfully creates arc shapes - All props work correctly (position, dimensions, angles, styling)
- Ref forwarding provides access to Arc instance
- Component integrates with existing Canvas/Group structure
- Dynamic prop updates work reactively
- Animations work smoothly via refs and useFrame
- Full TypeScript support with proper types
- Comprehensive tests with good coverage
- Documentation includes examples and API reference
- Performance equivalent to manual Arc usage
- Clear distinction from ArcSegment documented
- Works with all renderers (SVG, Canvas, WebGL)
- No breaking changes to existing API
Labels: enhancement, feature request, component, shapes
Milestone: v0.3.0