# Frontend Birth Chart Wheel Implementation Example

This example demonstrates how to use the new `/wheel-data` endpoint to create an interactive astrology chart wheel in the frontend.

## 🚀 Quick Start

### 1. Fetch Chart Wheel Data

```typescript
// API service for fetching chart wheel data
class AstrologyChartAPI {
  private baseURL = 'http://localhost:3000/api/v1/astrology-charts';
  private token = localStorage.getItem('authToken');

  async getChartWheelData(chartId: string) {
    const response = await fetch(`${this.baseURL}/${chartId}/wheel-data`, {
      headers: {
        Authorization: `Bearer ${this.token}`,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      throw new Error('Failed to fetch chart wheel data');
    }

    return response.json();
  }
}
```

### 2. React Component with D3.js

```jsx
import React, { useEffect, useRef, useState } from 'react';
import * as d3 from 'd3';

interface ChartWheelData {
  id: string;
  birthData: {
    date: string;
    time?: string;
    location: string;
    timezone?: string;
  };
  planetaryPositions: Array<{
    planet: string;
    sign: string;
    degree: number;
    house?: number;
    isRetrograde: boolean;
    x: number;
    y: number;
    angle: number;
    symbol: string;
    color: string;
  }>;
  houseCusps: Array<{
    houseNumber: number;
    sign: string;
    degree: number;
    x: number;
    y: number;
    angle: number;
    label: string;
  }>;
  aspects: Array<{
    fromPlanet: string;
    toPlanet: string;
    aspectType: string;
    orb: number;
    isExact: boolean;
    fromCoordinates: { x: number; y: number };
    toCoordinates: { x: number; y: number };
  }>;
  metadata: {
    sunSign: string;
    moonSign?: string;
    risingSign?: string;
    chartType: string;
  };
  wheelConfig: {
    centerX: number;
    centerY: number;
    outerRadius: number;
    innerRadius: number;
    planetRadius: number;
    houseRadius: number;
  };
}

const AstrologyChartWheel: React.FC<{ chartId: string }> = ({ chartId }) => {
  const svgRef = useRef<SVGSVGElement>(null);
  const [chartData, setChartData] = useState<ChartWheelData | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchChartData = async () => {
      try {
        setLoading(true);
        const api = new AstrologyChartAPI();
        const response = await api.getChartWheelData(chartId);
        setChartData(response.data);
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Failed to load chart');
      } finally {
        setLoading(false);
      }
    };

    fetchChartData();
  }, [chartId]);

  useEffect(() => {
    if (chartData && svgRef.current) {
      renderChartWheel();
    }
  }, [chartData]);

  const renderChartWheel = () => {
    if (!chartData || !svgRef.current) return;

    const svg = d3.select(svgRef.current);
    svg.selectAll('*').remove(); // Clear previous render

    const { wheelConfig } = chartData;
    const g = svg.append('g')
      .attr('transform', `translate(${wheelConfig.centerX}, ${wheelConfig.centerY})`);

    // Draw outer circle
    g.append('circle')
      .attr('r', wheelConfig.outerRadius)
      .style('fill', 'none')
      .style('stroke', '#333')
      .style('stroke-width', 2);

    // Draw inner circle
    g.append('circle')
      .attr('r', wheelConfig.innerRadius)
      .style('fill', 'none')
      .style('stroke', '#333')
      .style('stroke-width', 1);

    // Draw zodiac signs
    drawZodiacSigns(g, wheelConfig.outerRadius);

    // Draw houses
    drawHouses(g, wheelConfig.houseRadius);

    // Draw aspects
    drawAspects(g);

    // Draw planets
    drawPlanets(g);
  };

  const drawZodiacSigns = (g: any, radius: number) => {
    const signs = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
                   'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'];

    signs.forEach((sign, i) => {
      const angle = (i * 30) - 90; // Start from top
      const x = Math.cos(angle * Math.PI / 180) * (radius - 20);
      const y = Math.sin(angle * Math.PI / 180) * (radius - 20);

      g.append('text')
        .attr('x', x)
        .attr('y', y)
        .attr('text-anchor', 'middle')
        .attr('dominant-baseline', 'middle')
        .style('font-size', '12px')
        .style('font-weight', 'bold')
        .style('fill', '#333')
        .text(sign);
    });
  };

  const drawHouses = (g: any, radius: number) => {
    if (!chartData) return;

    chartData.houseCusps.forEach(house => {
      const x = house.x;
      const y = house.y;

      // Draw house cusp line
      g.append('line')
        .attr('x1', 0)
        .attr('y1', 0)
        .attr('x2', x)
        .attr('y2', y)
        .style('stroke', '#ccc')
        .style('stroke-width', 1);

      // Draw house number
      g.append('text')
        .attr('x', x * 0.7)
        .attr('y', y * 0.7)
        .attr('text-anchor', 'middle')
        .attr('dominant-baseline', 'middle')
        .style('font-size', '10px')
        .style('fill', '#666')
        .text(house.houseNumber);
    });
  };

  const drawAspects = (g: any) => {
    if (!chartData) return;

    chartData.aspects.forEach(aspect => {
      const fromX = aspect.fromCoordinates.x;
      const fromY = aspect.fromCoordinates.y;
      const toX = aspect.toCoordinates.x;
      const toY = aspect.toCoordinates.y;

      const aspectColor = getAspectColor(aspect.aspectType);

      g.append('line')
        .attr('x1', fromX)
        .attr('y1', fromY)
        .attr('x2', toX)
        .attr('y2', toY)
        .style('stroke', aspectColor)
        .style('stroke-width', aspect.isExact ? 2 : 1)
        .style('stroke-dasharray', aspect.aspectType === 'OPPOSITION' ? '5,5' : 'none')
        .style('opacity', 0.7);
    });
  };

  const drawPlanets = (g: any) => {
    if (!chartData) return;

    chartData.planetaryPositions.forEach(planet => {
      const planetGroup = g.append('g')
        .attr('transform', `translate(${planet.x}, ${planet.y})`);

      // Planet circle
      planetGroup.append('circle')
        .attr('r', 8)
        .style('fill', planet.color)
        .style('stroke', '#333')
        .style('stroke-width', 1);

      // Planet symbol
      planetGroup.append('text')
        .attr('y', 3)
        .attr('text-anchor', 'middle')
        .style('font-size', '8px')
        .style('font-weight', 'bold')
        .style('fill', '#fff')
        .text(planet.symbol);

      // Retrograde indicator
      if (planet.isRetrograde) {
        planetGroup.append('text')
          .attr('x', 12)
          .attr('y', -8)
          .attr('text-anchor', 'middle')
          .style('font-size', '6px')
          .style('fill', '#ff0000')
          .text('R');
      }

      // Tooltip
      planetGroup.append('title')
        .text(`${planet.planet} in ${planet.sign} ${planet.degree.toFixed(1)}°`);
    });
  };

  const getAspectColor = (aspectType: string): string => {
    const colors: Record<string, string> = {
      'CONJUNCTION': '#ff0000',
      'SEXTILE': '#00ff00',
      'SQUARE': '#ff0000',
      'TRINE': '#0000ff',
      'OPPOSITION': '#ff0000'
    };
    return colors[aspectType] || '#666';
  };

  if (loading) {
    return (
      <div className="flex items-center justify-center h-96">
        <div className="text-lg">Loading chart...</div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="flex items-center justify-center h-96">
        <div className="text-red-500">Error: {error}</div>
      </div>
    );
  }

  if (!chartData) {
    return (
      <div className="flex items-center justify-center h-96">
        <div className="text-gray-500">No chart data available</div>
      </div>
    );
  }

  return (
    <div className="chart-container">
      <div className="chart-header mb-4">
        <h2 className="text-2xl font-bold">
          {chartData.metadata.sunSign} Sun, {chartData.metadata.moonSign} Moon, {chartData.metadata.risingSign} Rising
        </h2>
        <p className="text-gray-600">
          Born {new Date(chartData.birthData.date).toLocaleDateString()} in {chartData.birthData.location}
        </p>
      </div>

      <div className="chart-wrapper">
        <svg
          ref={svgRef}
          width={600}
          height={600}
          className="border border-gray-300 rounded-lg"
        />
      </div>

      <div className="chart-legend mt-4 grid grid-cols-2 gap-4">
        <div>
          <h3 className="font-semibold mb-2">Planetary Positions</h3>
          {chartData.planetaryPositions.map(planet => (
            <div key={planet.planet} className="flex items-center mb-1">
              <span
                className="w-4 h-4 rounded-full mr-2"
                style={{ backgroundColor: planet.color }}
              />
              <span className="text-sm">
                {planet.planet} in {planet.sign} {planet.degree.toFixed(1)}°
                {planet.isRetrograde && <span className="text-red-500 ml-1">(R)</span>}
              </span>
            </div>
          ))}
        </div>

        <div>
          <h3 className="font-semibold mb-2">Major Aspects</h3>
          {chartData.aspects.slice(0, 5).map((aspect, index) => (
            <div key={index} className="text-sm mb-1">
              {aspect.fromPlanet} {aspect.aspectType} {aspect.toPlanet} ({aspect.orb.toFixed(1)}°)
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

export default AstrologyChartWheel;
```

### 3. Usage in Your App

```jsx
import React from 'react';
import AstrologyChartWheel from './components/AstrologyChartWheel';

const App: React.FC = () => {
  const chartId = 'your-chart-id-here';

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-6">My Astrology Chart</h1>
      <AstrologyChartWheel chartId={chartId} />
    </div>
  );
};

export default App;
```

## 🎨 Styling with Tailwind CSS

```css
/* Add to your CSS file */
.chart-container {
  @apply bg-white rounded-lg shadow-lg p-6;
}

.chart-wrapper {
  @apply flex justify-center;
}

.chart-legend {
  @apply bg-gray-50 rounded-lg p-4;
}

.planet-legend-item {
  @apply flex items-center space-x-2;
}

.planet-symbol {
  @apply text-lg font-bold;
}
```

## 🔧 Advanced Features

### 1. Interactive Tooltips

```jsx
const addTooltips = (g: any, chartData: ChartWheelData) => {
  const tooltip = d3.select('body').append('div')
    .attr('class', 'tooltip')
    .style('opacity', 0)
    .style('position', 'absolute')
    .style('background', 'rgba(0, 0, 0, 0.8)')
    .style('color', 'white')
    .style('padding', '8px')
    .style('border-radius', '4px')
    .style('font-size', '12px')
    .style('pointer-events', 'none');

  chartData.planetaryPositions.forEach(planet => {
    const planetGroup = g.select(`g[transform*="${planet.x},${planet.y}"]`);

    planetGroup
      .on('mouseover', (event: any) => {
        tooltip.transition().duration(200).style('opacity', 0.9);
        tooltip.html(`
          <strong>${planet.planet}</strong><br/>
          Sign: ${planet.sign}<br/>
          Degree: ${planet.degree.toFixed(1)}°<br/>
          House: ${planet.house || 'Unknown'}<br/>
          ${planet.isRetrograde ? 'Retrograde' : 'Direct'}
        `)
        .style('left', (event.pageX + 10) + 'px')
        .style('top', (event.pageY - 28) + 'px');
      })
      .on('mouseout', () => {
        tooltip.transition().duration(500).style('opacity', 0);
      });
  });
};
```

### 2. Zoom and Pan

```jsx
const addZoomPan = (svg: any) => {
  const zoom = d3.zoom()
    .scaleExtent([0.5, 3])
    .on('zoom', (event: any) => {
      svg.select('g').attr('transform', event.transform);
    });

  svg.call(zoom);
};
```

### 3. Chart Export

```jsx
const exportChart = () => {
  const svg = document.querySelector('svg');
  const svgData = new XMLSerializer().serializeToString(svg);
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  const img = new Image();

  img.onload = () => {
    canvas.width = img.width;
    canvas.height = img.height;
    ctx?.drawImage(img, 0, 0);

    const link = document.createElement('a');
    link.download = 'astrology-chart.png';
    link.href = canvas.toDataURL();
    link.click();
  };

  img.src = 'data:image/svg+xml;base64,' + btoa(svgData);
};
```

## 📱 Mobile Responsive

```jsx
const [dimensions, setDimensions] = useState({ width: 600, height: 600 });

useEffect(() => {
  const updateDimensions = () => {
    const isMobile = window.innerWidth < 768;
    setDimensions({
      width: isMobile ? 350 : 600,
      height: isMobile ? 350 : 600,
    });
  };

  updateDimensions();
  window.addEventListener('resize', updateDimensions);
  return () => window.removeEventListener('resize', updateDimensions);
}, []);
```

## 🚀 Performance Optimization

```jsx
// Memoize expensive calculations
const memoizedChartData = useMemo(() => {
  if (!chartData) return null;

  return {
    ...chartData,
    planetaryPositions: chartData.planetaryPositions.map((planet) => ({
      ...planet,
      // Pre-calculate expensive operations
      displayText: `${planet.planet} in ${planet.sign} ${planet.degree.toFixed(1)}°`,
    })),
  };
}, [chartData]);

// Use React.memo for component optimization
export default React.memo(AstrologyChartWheel);
```

This example provides a complete foundation for implementing an interactive astrology chart wheel using your backend API. The component is fully responsive, includes tooltips, and can be easily extended with additional features like zoom, pan, and chart export.
