Back to catalog

coregraphics-helper

Generate and optimize CoreGraphics/AppKit drawing code for complex paths, shapes, and visualizations

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • Updated: 2025-11-22

Download the latest version (v1.0.0) from this page.

SKILL.md

---
description: Generate and optimize CoreGraphics/AppKit drawing code for complex paths, shapes, and visualizations
---

# CoreGraphics Helper

Generate and optimize CoreGraphics/AppKit drawing code for complex paths, shapes, and visualizations.

## Capabilities

1. **Path Generation**
   - Generate NSBezierPath/CGPath from descriptions
   - Create complex geometric shapes
   - Build parametric curves and arcs
   - Generate rose diagrams and polar plots

2. **Drawing Code Creation**
   - Generate draw() method implementations
   - Create custom shape drawing functions
   - Build reusable graphics utilities
   - Generate transformation matrices

3. **Path Manipulation**
   - Transform paths (scale, rotate, translate)
   - Combine paths (union, intersection, subtraction)
   - Simplify complex paths
   - Calculate path bounds and metrics

4. **Optimization**
   - Reduce path complexity
   - Optimize drawing performance
   - Cache expensive calculations
   - Use appropriate drawing primitives

5. **Mathematical Helpers**
   - Polar-to-cartesian conversions
   - Angle normalization and restrictions
   - Bezier curve calculations
   - Trigonometric utilities

## Workflow

When invoked, this skill will:

1. **Understand**: Parse shape/drawing requirements
2. **Calculate**: Compute necessary geometry
3. **Generate**: Create drawing code
4. **Optimize**: Improve performance
5. **Document**: Add clear comments

## Usage Instructions

When the user invokes this skill:

1. Ask what to create:
   - Specific shape (circle, arc, polygon, etc.)
   - Complex visualization (rose diagram, histogram)
   - Path transformation
   - Drawing optimization

2. Gather parameters:
   - Dimensions and coordinates
   - Angles and rotations
   - Colors and line widths
   - Data values to visualize

3. Generate code with:
   - Clear variable names
   - Step-by-step comments
   - Reusable functions
   - Performance considerations

## Project-Specific Context

Your project specializes in rose diagrams and paleontological visualizations:
- Circular plots with radial data
- Petal diagrams showing directional data
- Histogram overlays
- Vector graphics with precise angles

## Common Patterns

### 1. Rose Diagram Petal

```swift
func createPetalPath(
    center: CGPoint,
    startAngle: CGFloat,
    endAngle: CGFloat,
    innerRadius: CGFloat,
    outerRadius: CGFloat
) -> NSBezierPath {
    let path = NSBezierPath()

    // Convert angles to radians
    let startRad = startAngle * .pi / 180.0
    let endRad = endAngle * .pi / 180.0

    // Start at center
    path.move(to: center)

    // Line to inner arc start
    let innerStart = CGPoint(
        x: center.x + innerRadius * cos(startRad),
        y: center.y + innerRadius * sin(startRad)
    )
    path.line(to: innerStart)

    // Outer arc
    let outerStart = CGPoint(
        x: center.x + outerRadius * cos(startRad),
        y: center.y + outerRadius * sin(startRad)
    )
    let outerEnd = CGPoint(
        x: center.x + outerRadius * cos(endRad),
        y: center.y + outerRadius * sin(endRad)
    )

    path.line(to: outerStart)
    path.appendArc(
        withCenter: center,
        radius: outerRadius,
        startAngle: startAngle,
        endAngle: endAngle,
        clockwise: false
    )

    // Line back to center
    path.line(to: center)
    path.close()

    return path
}
```

### 2. Circular Grid/Circles

```swift
func createConcentricCircles(
    center: CGPoint,
    radii: [CGFloat]
) -> [NSBezierPath] {
    radii.map { radius in
        NSBezierPath(
            ovalIn: CGRect(
                x: center.x - radius,
                y: center.y - radius,
                width: radius * 2,
                height: radius * 2
            )
        )
    }
}

// Usage
let circles = createConcentricCircles(
    center: CGPoint(x: 200, y: 200),
    radii: [50, 100, 150, 200]
)

circles.forEach { circle in
    NSColor.gray.setStroke()
    circle.lineWidth = 0.5
    circle.stroke()
}
```

### 3. Radial Lines (Spokes)

```swift
func createRadialLines(
    center: CGPoint,
    radius: CGFloat,
    angleCount: Int,
    startAngle: CGFloat = 0
) -> NSBezierPath {
    let path = NSBezierPath()
    let angleIncrement = 360.0 / CGFloat(angleCount)

    for i in 0..<angleCount {
        let angle = (startAngle + CGFloat(i) * angleIncrement) * .pi / 180.0
        let endPoint = CGPoint(
            x: center.x + radius * cos(angle),
            y: center.y + radius * sin(angle)
        )

        path.move(to: center)
        path.line(to: endPoint)
    }

    return path
}

// Draw compass rose with 8 directions
let spokes = createRadialLines(
    center: CGPoint(x: 200, y: 200),
    radius: 180,
    angleCount: 8,
    startAngle: 0
)
```

### 4. Kite Diagram (Mirrored Histogram)

```swift
func createKitePath(
    center: CGPoint,
    angle: CGFloat,
    leftValue: CGFloat,
    rightValue: CGFloat,
    maxRadius: CGFloat
) -> NSBezierPath {
    let path = NSBezierPath()
    let rad = angle * .pi / 180.0

    // Perpendicular vector for left/right offset
    let perpX = -sin(rad)
    let perpY = cos(rad)

    // Four corners of kite segment
    let centerFront = CGPoint(
        x: center.x + maxRadius * cos(rad),
        y: center.y + maxRadius * sin(rad)
    )

    let leftPoint = CGPoint(
        x: center.x + leftValue * perpX,
        y: center.y + leftValue * perpY
    )

    let rightPoint = CGPoint(
        x: center.x - rightValue * perpX,
        y: center.y - rightValue * perpY
    )

    let leftFront = CGPoint(
        x: centerFront.x + leftValue * perpX,
        y: centerFront.y + leftValue * perpY
    )

    let rightFront = CGPoint(
        x: centerFront.x - rightValue * perpX,
        y: centerFront.y - rightValue * perpY
    )

    // Build path
    path.move(to: leftPoint)
    path.line(to: leftFront)
    path.line(to: rightFront)
    path.line(to: rightPoint)
    path.close()

    return path
}
```

### 5. Arrow Heads

```swift
func createArrowHead(
    at point: CGPoint,
    angle: CGFloat,
    length: CGFloat,
    width: CGFloat
) -> NSBezierPath {
    let path = NSBezierPath()
    let rad = angle * .pi / 180.0

    // Arrow points backward along angle direction
    let backX = point.x - length * cos(rad)
    let backY = point.y - length * sin(rad)

    // Perpendicular for width
    let perpX = -sin(rad)
    let perpY = cos(rad)

    let left = CGPoint(
        x: backX + width/2 * perpX,
        y: backY + width/2 * perpY
    )

    let right = CGPoint(
        x: backX - width/2 * perpX,
        y: backY - width/2 * perpY
    )

    path.move(to: point)
    path.line(to: left)
    path.line(to: right)
    path.close()

    return path
}
```

## Mathematical Utilities

### Angle Utilities

```swift
extension CGFloat {
    /// Normalize angle to 0-360 range
    func normalizedAngle() -> CGFloat {
        var angle = self
        while angle < 0 {
            angle += 360
        }
        while angle >= 360 {
            angle -= 360
        }
        return angle
    }

    /// Convert degrees to radians
    var radians: CGFloat {
        self * .pi / 180.0
    }

    /// Convert radians to degrees
    var degrees: CGFloat {
        self * 180.0 / .pi
    }
}

extension Float {
    var radians: Float {
        self * .pi / 180.0
    }

    var degrees: Float {
        self * 180.0 / .pi
    }

    func normalizedAngle() -> Float {
        var angle = self
        while angle < 0 {
            angle += 360
        }
        while angle >= 360 {
            angle -= 360
        }
        return angle
    }
}
```

### Polar Coordinates

```swift
struct PolarPoint {
    let angle: CGFloat  // degrees
    let radius: CGFloat

    var cartesian: CGPoint {
        CGPoint(
            x: radius * cos(angle.radians),
            y: radius * sin(angle.radians)
        )
    }

    func cartesian(center: CGPoint) -> CGPoint {
        CGPoint(
            x: center.x + radius * cos(angle.radians),
            y: center.y + radius * sin(angle.radians)
        )
    }
}

extension CGPoint {
    func polar(from center: CGPoint) -> PolarPoint {
        let dx = x - center.x
        let dy = y - center.y
        let radius = sqrt(dx * dx + dy * dy)
        let angle = atan2(dy, dx).degrees
        return PolarPoint(angle: angle, radius: radius)
    }
}
```

### Point Interpolation

```swift
extension CGPoint {
    /// Linear interpolation between two points
    func lerp(to: CGPoint, t: CGFloat) -> CGPoint {
        CGPoint(
            x: x + (to.x - x) * t,
            y: y + (to.y - y) * t
        )
    }

    /// Distance to another point
    func distance(to: CGPoint) -> CGFloat {
        let dx = to.x - x
        let dy = to.y - y
        return sqrt(dx * dx + dy * dy)
    }

    /// Rotate around a center point
    func rotated(
        around center: CGPoint,
        by angle: CGFloat  // degrees
    ) -> CGPoint {
        let rad = angle.radians
        let dx = x - center.x
        let dy = y - center.y

        let rotatedX = dx * cos(rad) - dy * sin(rad)
        let rotatedY = dx * sin(rad) + dy * cos(rad)

        return CGPoint(
            x: center.x + rotatedX,
            y: center.y + rotatedY
        )
    }
}
```

## Drawing Optimization

### 1. Cache Paths

```swift
class GraphicLayer {
    private var cachedPath: NSBezierPath?
    private var lastGeometry: GeometryState?

    func drawingPath() -> NSBezierPath {
        let currentGeometry = currentGeometryState()

        if cachedPath == nil || lastGeometry != currentGeometry {
            cachedPath = calculatePath()
            lastGeometry = currentGeometry
        }

        return cachedPath!
    }
}
```

### 2. Reduce Path Complexity

```swift
extension NSBezierPath {
    /// Simplify path by reducing points within tolerance
    func simplified(tolerance: CGFloat = 1.0) -> NSBezierPath {
        // Douglas-Peucker algorithm or similar
        // Remove points that don't significantly affect shape
        let simplified = NSBezierPath()
        // Implementation...
        return simplified
    }
}
```

### 3. Batch Drawing

```swift
func drawMultiplePetals(
    petals: [PetalData],
    in context: CGContext
) {
    context.saveGState()

    // Set common properties once
    context.setLineWidth(1.0)
    context.setStrokeColor(NSColor.black.cgColor)

    // Draw all petals
    for petal in petals {
        let path = petal.bezierPath.cgPath
        context.addPath(path)
        context.setFillColor(petal.color.cgColor)
        context.drawPath(using: .fillStroke)
    }

    context.restoreGState()
}
```

## Complex Shape Patterns

### Rose Diagram with Data

```swift
func createRoseDiagram(
    center: CGPoint,
    radius: CGFloat,
    data: [DirectionalData],
    binCount: Int = 16
) -> NSBezierPath {
    let path = NSBezierPath()
    let angleSize = 360.0 / CGFloat(binCount)

    // Bin the data
    let bins = binData(data, binCount: binCount)

    // Find max for scaling
    let maxValue = bins.max() ?? 1.0

    for (index, value) in bins.enumerated() {
        let startAngle = CGFloat(index) * angleSize
        let endAngle = startAngle + angleSize

        let petalRadius = radius * (value / maxValue)

        let petal = createPetalPath(
            center: center,
            startAngle: startAngle,
            endAngle: endAngle,
            innerRadius: 0,
            outerRadius: petalRadius
        )

        path.append(petal)
    }

    return path
}
```

### Histogram Overlay

```swift
func createHistogramBars(
    center: CGPoint,
    innerRadius: CGFloat,
    outerRadius: CGFloat,
    values: [CGFloat],
    startAngle: CGFloat = 0
) -> [NSBezierPath] {
    let binWidth = 360.0 / CGFloat(values.count)
    let maxValue = values.max() ?? 1.0

    return values.enumerated().map { index, value in
        let angle = startAngle + CGFloat(index) * binWidth
        let barHeight = (outerRadius - innerRadius) * (value / maxValue)
        let barOuterRadius = innerRadius + barHeight

        return createPetalPath(
            center: center,
            startAngle: angle,
            endAngle: angle + binWidth,
            innerRadius: innerRadius,
            outerRadius: barOuterRadius
        )
    }
}
```

## Performance Tips

1. **Use CGPath for Complex Rendering**
   - NSBezierPath is easier to construct
   - CGPath renders faster
   - Convert when needed: `bezierPath.cgPath`

2. **Minimize State Changes**
   - Batch similar drawing operations
   - Set colors/widths once for multiple shapes
   - Use CGContext state saving

3. **Pre-calculate Geometry**
   - Compute paths once, cache results
   - Invalidate cache only when needed
   - Use lazy properties for expensive calculations

4. **Appropriate Precision**
   - Don't over-subdivide curves
   - Use flatness parameter for quality/speed balance
   - Round coordinates when pixel-perfect isn't needed

## Code Templates

### Generic Shape Drawer

```swift
protocol ShapeDrawer {
    func createPath() -> NSBezierPath
    var strokeColor: NSColor { get }
    var fillColor: NSColor? { get }
    var lineWidth: CGFloat { get }

    func draw()
}

extension ShapeDrawer {
    func draw() {
        let path = createPath()
        strokeColor.setStroke()
        fillColor?.setFill()
        path.lineWidth = lineWidth

        if let _ = fillColor {
            path.fill()
        }
        path.stroke()
    }
}
```

### Parametric Path Generator

```swift
func createParametricPath(
    from: CGFloat,
    to: CGFloat,
    steps: Int,
    transform: (CGFloat) -> CGPoint
) -> NSBezierPath {
    let path = NSBezierPath()
    let stepSize = (to - from) / CGFloat(steps)

    for i in 0...steps {
        let t = from + CGFloat(i) * stepSize
        let point = transform(t)

        if i == 0 {
            path.move(to: point)
        } else {
            path.line(to: point)
        }
    }

    return path
}

// Example: spiral
let spiral = createParametricPath(
    from: 0,
    to: 4 * .pi,
    steps: 100
) { t in
    let radius = t * 10
    return CGPoint(
        x: 200 + radius * cos(t),
        y: 200 + radius * sin(t)
    )
}
```

## Configuration

Store drawing preferences in `.coregraphics-helper.json`:
```json
{
  "defaultCenter": {"x": 200, "y": 200},
  "defaultRadius": 180,
  "defaultLineWidth": 1.0,
  "anglesInDegrees": true,
  "optimizePaths": true,
  "cacheDrawingPaths": true
}
```