RoxyAPI

Menu

How to Build a Rashi Chart Visualizer (React Native + API Tutorial)

17 min read
By Kavya Iyer
Vedic AstrologyKundli ChartChart VisualizationReact NativeCanvas Drawing

Complete tutorial for rendering North, South, and East Indian chart layouts. Includes planetary positions, house placements, aspects, and interactive features.

How to Build a Rashi Chart Visualizer (React Native + API Tutorial)

Visualizing Vedic astrology charts is one of the most challenging aspects of building kundli applications. Unlike Western charts which use circular wheels, Vedic charts employ three distinct regional layouts - North Indian (diamond), South Indian (square grid), and East Indian (rectangular). Each style has unique conventions for displaying planetary positions and house placements.

This comprehensive tutorial shows you how to build a production-ready Rashi chart visualizer supporting all three Indian styles, complete with planetary glyphs, aspect lines, house numbers, and interactive features.

What We Will Build

Our chart visualizer includes:

  1. Three Chart Styles - North, South, and East Indian layouts
  2. Planetary Positions - Visual glyphs with accurate placements
  3. House Numbers - Clear house identification in each style
  4. Aspect Lines - Connections between aspecting planets
  5. Retrograde Indicators - Special markers for retrograde planets
  6. Lagna Highlighting - Visual emphasis on ascendant
  7. Interactive Features - Tap planets for details, zoom, pan
  8. Export Options - Save as image or PDF

Understanding Chart Styles

North Indian Chart (Diamond Layout)

  • Diamond-shaped with Lagna (ascendant) always at top
  • Houses rotate but shape stays fixed
  • Most popular in Northern India
  • Houses arranged in counter-clockwise pattern

South Indian Chart (Square Grid)

  • Square grid with fixed house positions
  • Houses 1-12 always in same position
  • Planets move between houses
  • Most popular in Southern India
  • Easiest to read for beginners

East Indian Chart (Rectangular Layout)

  • Rectangular format similar to North Indian
  • Lagna position varies by rashi
  • Houses in counter-clockwise spiral
  • Popular in Bengal and Eastern India

Step 1: Understanding the API Response

First, let's understand the actual birth chart API structure:

// API Response Structure from /birth-chart endpoint
interface BirthChartResponse {
  // Planets grouped by rashi
  aries: RashiData;
  taurus: RashiData;
  gemini: RashiData;
  cancer: RashiData;
  leo: RashiData;
  virgo: RashiData;
  libra: RashiData;
  scorpio: RashiData;
  sagittarius: RashiData;
  capricorn: RashiData;
  aquarius: RashiData;
  pisces: RashiData;
  
  // Planet-keyed metadata
  meta: {
    Sun: PlanetData;
    Moon: PlanetData;
    Mars: PlanetData;
    Mercury: PlanetData;
    Jupiter: PlanetData;
    Venus: PlanetData;
    Saturn: PlanetData;
    Rahu: PlanetData;
    Ketu: PlanetData;
    Lagna: PlanetData;
  };
  
  // House descriptions
  houses: Array<{
    number: number;
    name: string;
    description: string;
  }>;
}

interface RashiData {
  rashi: string;
  signs: PlanetData[];
}

interface PlanetData {
  graha: string;
  rashi: string;
  longitude: number;
  nakshatra: {
    anga: string;
    key: number;
    ratio: number;
    abhijit: boolean;
    left: number;
    name: string;
    pada: number;
  };
  isRetrograde: boolean;
}

Now let's create our chart rendering types:

// types/chart.ts
export interface ChartPlanet {
  name: string;
  rashi: string;
  longitude: number;
  nakshatra: string;
  pada: number;
  isRetrograde: boolean;
}

export interface ChartData {
  lagna: ChartPlanet;
  planets: ChartPlanet[];
  planetsByRashi: Record<string, ChartPlanet[]>;
}

export type ChartStyle = 'north' | 'south' | 'east';

export interface ChartConfig {
  style: ChartStyle;
  size: number;
  showPlanetNames: boolean;
  colors: {
    background: string;
    border: string;
    lagna: string;
    planet: string;
  };
}

Step 2: Transform API Data

Convert the API response to chart-friendly structure:

// services/chart-transformer.ts
export function transformBirthChart(apiResponse: BirthChartResponse): ChartData {
  // Extract Lagna
  const lagna: ChartPlanet = {
    name: 'Lagna',
    rashi: apiResponse.meta.Lagna.rashi,
    longitude: apiResponse.meta.Lagna.longitude,
    nakshatra: apiResponse.meta.Lagna.nakshatra.name,
    pada: apiResponse.meta.Lagna.nakshatra.pada,
    isRetrograde: false,
  };

  // Extract all planets from meta
  const planetNames = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'];
  const planets: ChartPlanet[] = planetNames.map(name => ({
    name,
    rashi: apiResponse.meta[name].rashi,
    longitude: apiResponse.meta[name].longitude,
    nakshatra: apiResponse.meta[name].nakshatra.name,
    pada: apiResponse.meta[name].nakshatra.pada,
    isRetrograde: apiResponse.meta[name].isRetrograde,
  }));

  // Group planets by rashi for rendering
  const planetsByRashi: Record<string, ChartPlanet[]> = {};
  planets.forEach(planet => {
    const rashiKey = planet.rashi.toLowerCase();
    if (!planetsByRashi[rashiKey]) {
      planetsByRashi[rashiKey] = [];
    }
    planetsByRashi[rashiKey].push(planet);
  });

  return {
    lagna,
    planets,
    planetsByRashi,
  };
}

Step 3: North Indian Chart Renderer

Build the diamond-style chart:

// components/charts/NorthIndianChart.tsx
import React from 'react';
import { View, StyleSheet } from 'react-native';
import Svg, { Path, Line, Text as SvgText, G } from 'react-native-svg';

interface Props {
  chartData: ChartData;
  config: ChartConfig;
}

export function NorthIndianChart({ chartData, config }: Props) {
  const { size } = config;
  const center = size / 2;
  const outerRadius = size * 0.45;
  const innerRadius = size * 0.15;

  // Rashi order starting from Lagna at top (counter-clockwise)
  const rashiOrder = [
    'aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo',
    'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces'
  ];

  // Find Lagna rashi index
  const lagnaRashiIndex = rashiOrder.indexOf(chartData.lagna.rashi.toLowerCase());

  // Rashi positions in diamond (starting from top, counter-clockwise)
  const rashiAngles = [
    90,   // Top
    120,  // Top-left
    150,  // Left-top
    180,  // Left
    210,  // Left-bottom
    240,  // Bottom-left
    270,  // Bottom
    300,  // Bottom-right
    330,  // Right-bottom
    0,    // Right
    30,   // Right-top
    60,   // Top-right
  ];

  // Adjust angles so Lagna is at top
  const adjustedRashis = rashiAngles.map((angle, index) => {
    const rashiIndex = (index + lagnaRashiIndex) % 12;
    const rashi = rashiOrder[rashiIndex];
    return { rashi, angle };
  });

  // Calculate diamond path
  const diamondPath = `
    M ${center},${center - outerRadius}
    L ${center + outerRadius},${center}
    L ${center},${center + outerRadius}
    L ${center - outerRadius},${center}
    Z
  `;

  // Inner square path
  const innerPath = `
    M ${center},${center - innerRadius}
    L ${center + innerRadius},${center}
    L ${center},${center + innerRadius}
    L ${center - innerRadius},${center}
    Z
  `;

  // Calculate position for rashi content
  function getRashiPosition(angle: number, distance: number) {
    const rad = (angle * Math.PI) / 180;
    return {
      x: center + Math.cos(rad) * distance,
      y: center - Math.sin(rad) * distance,
    };
  }

  return (
    <View style={[styles.container, { width: size, height: size }]}>
      <Svg width={size} height={size}>
        {/* Outer diamond */}
        <Path
          d={diamondPath}
          fill={config.colors.background}
          stroke={config.colors.border}
          strokeWidth={2}
        />

        {/* Inner square */}
        <Path
          d={innerPath}
          fill="none"
          stroke={config.colors.border}
          strokeWidth={1}
        />

        {/* Diagonal lines */}
        <Line
          x1={center - outerRadius}
          y1={center}
          x2={center + outerRadius}
          y2={center}
          stroke={config.colors.border}
          strokeWidth={1}
        />
        <Line
          x1={center}
          y1={center - outerRadius}
          x2={center}
          y2={center + outerRadius}
          stroke={config.colors.border}
          strokeWidth={1}
        />

        {/* Rashis and planets */}
        {adjustedRashis.map(({ rashi, angle }) => {
          const midRadius = (outerRadius + innerRadius) / 2;
          const pos = getRashiPosition(angle, midRadius);
          const planets = chartData.planetsByRashi[rashi] || [];
          const isLagnaRashi = rashi === chartData.lagna.rashi.toLowerCase();

          return (
            <G key={rashi}>
              {/* Rashi name */}
              <SvgText
                x={pos.x}
                y={pos.y - 25}
                fontSize={9}
                fill={isLagnaRashi ? config.colors.lagna : '#999'}
                fontWeight={isLagnaRashi ? 'bold' : 'normal'}
                textAnchor="middle"
              >
                {rashi.charAt(0).toUpperCase() + rashi.slice(1)}
                {isLagnaRashi && ' (ASC)'}
              </SvgText>

              {/* Planets in this rashi */}
              {planets.map((planet, index) => {
                const yOffset = -5 + (index * 16);
                
                return (
                  <G key={planet.name}>
                    <SvgText
                      x={pos.x}
                      y={pos.y + yOffset}
                      fontSize={13}
                      fill={config.colors.planet}
                      fontWeight="bold"
                      textAnchor="middle"
                    >
                      {getPlanetGlyph(planet.name)}
                      {planet.isRetrograde && '℞'}
                    </SvgText>
                  </G>
                );
              })}
            </G>
          );
        })}
      </Svg>
    </View>
  );
}

function getPlanetGlyph(planet: string): string {
  const glyphs: Record<string, string> = {
    Sun: '☉',
    Moon: '☽',
    Mars: '♂',
    Mercury: '☿',
    Jupiter: '♃',
    Venus: '♀',
    Saturn: '♄',
    Rahu: '☊',
    Ketu: '☋',
  };
  return glyphs[planet] || planet.substring(0, 2);
}

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Step 4: South Indian Chart Renderer

Build the square grid layout with fixed rashi positions:

// components/charts/SouthIndianChart.tsx
import React from 'react';
import { View, StyleSheet } from 'react-native';
import Svg, { Path, Text as SvgText, G } from 'react-native-svg';

interface Props {
  chartData: ChartData;
  config: ChartConfig;
}

export function SouthIndianChart({ chartData, config }: Props) {
  const { size } = config;
  const cellSize = size / 4;

  // Fixed rashi positions in South Indian chart (clockwise from top-left)
  const rashiOrder = [
    'pisces', 'aries', 'taurus', 'gemini',
    'aquarius', null, null, 'cancer',
    'capricorn', null, null, 'leo',
    'sagittarius', 'scorpio', 'libra', 'virgo',
  ];

  const rashiLayout = [
    // Row 1
    { rashi: 'pisces', row: 0, col: 0 },
    { rashi: 'aries', row: 0, col: 1 },
    { rashi: 'taurus', row: 0, col: 2 },
    { rashi: 'gemini', row: 0, col: 3 },
    // Row 2
    { rashi: 'aquarius', row: 1, col: 0 },
    null, // Center diagonal
    null, // Center diagonal
    { rashi: 'cancer', row: 1, col: 3 },
    // Row 3
    { rashi: 'capricorn', row: 2, col: 0 },
    null, // Center diagonal
    null, // Center diagonal
    { rashi: 'leo', row: 2, col: 3 },
    // Row 4
    { rashi: 'sagittarius', row: 3, col: 0 },
    { rashi: 'scorpio', row: 3, col: 1 },
    { rashi: 'libra', row: 3, col: 2 },
    { rashi: 'virgo', row: 3, col: 3 },
  ];

  function renderCell(rashiName: string | null, row: number, col: number) {
    if (rashiName === null) {
      // Center diagonal cells
      const isDiagonalTopLeft = row === 1 && col === 1;
      
      return (
        <G key={`${row}-${col}`}>
          <Path
            d={
              isDiagonalTopLeft
                ? `M ${col * cellSize},${row * cellSize} L ${(col + 1) * cellSize},${(row + 1) * cellSize}`
                : `M ${(col + 1) * cellSize},${row * cellSize} L ${col * cellSize},${(row + 1) * cellSize}`
            }
            stroke={config.colors.border}
            strokeWidth={1}
          />
        </G>
      );
    }

    const planets = chartData.planetsByRashi[rashiName] || [];
    const isLagnaRashi = rashiName === chartData.lagna.rashi.toLowerCase();

    const x = col * cellSize;
    const y = row * cellSize;

    return (
      <G key={rashiName}>
        {/* Cell border */}
        <Path
          d={`M ${x},${y} L ${x + cellSize},${y} L ${x + cellSize},${y + cellSize} L ${x},${y + cellSize} Z`}
          fill={isLagnaRashi ? config.colors.lagna + '20' : 'transparent'}
          stroke={config.colors.border}
          strokeWidth={isLagnaRashi ? 2 : 1}
        />

        {/* Rashi name */}
        <SvgText
          x={x + cellSize / 2}
          y={y + 14}
          fontSize={9}
          fill={isLagnaRashi ? config.colors.lagna : '#999'}
          fontWeight={isLagnaRashi ? 'bold' : 'normal'}
          textAnchor="middle"
        >
          {rashiName.charAt(0).toUpperCase() + rashiName.slice(1)}
          {isLagnaRashi && ' (ASC)'}
        </SvgText>

        {/* Planets */}
        {planets.map((planet, index) => {
          const planetY = y + 35 + (index * 18);

          return (
            <SvgText
              key={planet.name}
              x={x + cellSize / 2}
              y={planetY}
              fontSize={14}
              fill={config.colors.planet}
              fontWeight="bold"
              textAnchor="middle"
            >
              {getPlanetGlyph(planet.name)}
              {planet.isRetrograde && ' ℞'}
            </SvgText>
          );
        })}
      </G>
    );
  }

  return (
    <View style={[styles.container, { width: size, height: size }]}>
      <Svg width={size} height={size}>
        {/* Outer border */}
        <Path
          d={`M 0,0 L ${size},0 L ${size},${size} L 0,${size} Z`}
          fill={config.colors.background}
          stroke={config.colors.border}
          strokeWidth={2}
        />

        {/* Render all cells */}
        {rashiLayout.map((cell, index) => {
          if (!cell) {
            const row = Math.floor(index / 4);
            const col = index % 4;
            return renderCell(null, row, col);
          }
          return renderCell(cell.rashi, cell.row, cell.col);
        })}
      </Svg>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Step 5: East Indian Chart Renderer

Build the rectangular spiral layout:

// components/charts/EastIndianChart.tsx
import React from 'react';
import { View, StyleSheet } from 'react-native';
import Svg, { Path, Text as SvgText, G } from 'react-native-svg';

interface Props {
  chartData: ChartData;
  config: ChartConfig;
}

export function EastIndianChart({ chartData, config }: Props) {
  const { size } = config;
  const cols = 4;
  const rows = 3;
  const cellWidth = size / cols;
  const cellHeight = size / rows;

  // East Indian rashi layout (spiral pattern similar to North Indian)
  const rashiLayout = [
    // Top row (12, 1, 2, 3 from Lagna perspective)
    { rashi: 'pisces', row: 0, col: 0 },
    { rashi: 'aries', row: 0, col: 1 },
    { rashi: 'taurus', row: 0, col: 2 },
    { rashi: 'gemini', row: 0, col: 3 },
    // Middle row
    { rashi: 'aquarius', row: 1, col: 0 },
    { rashi: 'cancer', row: 1, col: 3 },
    // Bottom row
    { rashi: 'capricorn', row: 2, col: 0 },
    { rashi: 'sagittarius', row: 2, col: 1 },
    { rashi: 'scorpio', row: 2, col: 2 },
    { rashi: 'libra', row: 2, col: 3 },
    // Side rashis
    { rashi: 'leo', row: 1, col: 3 },
    { rashi: 'virgo', row: 2, col: 3 },
  ];

  function renderRashi(rashiName: string, row: number, col: number) {
    const x = col * cellWidth;
    const y = row * cellHeight;
    const w = cellWidth;
    const h = cellHeight;

    const planets = chartData.planetsByRashi[rashiName] || [];
    const isLagnaRashi = rashiName === chartData.lagna.rashi.toLowerCase();

    return (
      <G key={rashiName}>
        {/* Cell */}
        <Path
          d={`M ${x},${y} L ${x + w},${y} L ${x + w},${y + h} L ${x},${y + h} Z`}
          fill={isLagnaRashi ? config.colors.lagna + '20' : 'transparent'}
          stroke={config.colors.border}
          strokeWidth={isLagnaRashi ? 2 : 1}
        />

        {/* Rashi name */}
        <SvgText
          x={x + w / 2}
          y={y + 14}
          fontSize={9}
          fill={isLagnaRashi ? config.colors.lagna : '#999'}
          fontWeight={isLagnaRashi ? 'bold' : 'normal'}
          textAnchor="middle"
        >
          {rashiName.charAt(0).toUpperCase() + rashiName.slice(1)}
          {isLagnaRashi && ' (ASC)'}
        </SvgText>

        {/* Planets */}
        {planets.map((planet, index) => {
          const planetY = y + 35 + (index * 16);

          return (
            <SvgText
              key={planet.name}
              x={x + w / 2}
              y={planetY}
              fontSize={12}
              fill={config.colors.planet}
              fontWeight="bold"
              textAnchor="middle"
            >
              {getPlanetGlyph(planet.name)}
              {planet.isRetrograde && ' ℞'}
            </SvgText>
          );
        })}
      </G>
    );
  }

  return (
    <View style={[styles.container, { width: size, height: size }]}>
      <Svg width={size} height={size}>
        {/* Background */}
        <Path
          d={`M 0,0 L ${size},0 L ${size},${size} L 0,${size} Z`}
          fill={config.colors.background}
          stroke={config.colors.border}
          strokeWidth={2}
        />

        {/* Render rashis */}
        {rashiLayout.map((cell) => 
          renderRashi(cell.rashi, cell.row, cell.col)
        )}
      </Svg>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Step 6: Chart Controller Component

Unified interface for all chart styles:

// components/ChartViewer.tsx
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
import { NorthIndianChart } from './charts/NorthIndianChart';
import { SouthIndianChart } from './charts/SouthIndianChart';
import { EastIndianChart } from './charts/EastIndianChart';
import { transformBirthChart } from '../services/chart-transformer';

interface BirthData {
  date: string;
  time: string;
  latitude: number;
  longitude: number;
  timezone: number;
}

interface Props {
  birthData: BirthData;
}

export function ChartViewer({ birthData }: Props) {
  const [chartData, setChartData] = useState<ChartData | null>(null);
  const [style, setStyle] = useState<ChartStyle>('north');
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    loadChartData();
  }, [birthData]);

  async function loadChartData() {
    try {
      // Fetch birth chart from API
      const response = await fetch('https://roxyapi.com/api/v2/vedic-astrology/birth-chart', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-API-Key': 'YOUR_API_KEY',
        },
        body: JSON.stringify(birthData),
      });
      
      const birthChart = await response.json();
      
      // Transform to chart data structure
      const data = transformBirthChart(birthChart);
      setChartData(data);
    } catch (error) {
      console.error('Failed to load chart:', error);
    } finally {
      setLoading(false);
    }
  }

  const config: ChartConfig = {
    style,
    size: 400,
    showPlanetNames: true,
    colors: {
      background: '#FFFFFF',
      border: '#8B4513',
      lagna: '#CD853F',
      planet: '#4A4A4A',
    },
  };

  if (loading) return <Text>Loading chart...</Text>;
  if (!chartData) return <Text>Failed to load chart</Text>;

  return (
    <View style={styles.container}>
      {/* Style Selector */}
      <View style={styles.styleSelector}>
        <TouchableOpacity
          style={[styles.styleButton, style === 'north' && styles.activeStyle]}
          onPress={() => setStyle('north')}
        >
          <Text style={styles.styleText}>North Indian</Text>
        </TouchableOpacity>
        <TouchableOpacity
          style={[styles.styleButton, style === 'south' && styles.activeStyle]}
          onPress={() => setStyle('south')}
        >
          <Text style={styles.styleText}>South Indian</Text>
        </TouchableOpacity>
        <TouchableOpacity
          style={[styles.styleButton, style === 'east' && styles.activeStyle]}
          onPress={() => setStyle('east')}
        >
          <Text style={styles.styleText}>East Indian</Text>
        </TouchableOpacity>
      </View>

      {/* Chart Display */}
      <View style={styles.chartContainer}>
        {style === 'north' && <NorthIndianChart chartData={chartData} config={config} />}
        {style === 'south' && <SouthIndianChart chartData={chartData} config={config} />}
        {style === 'east' && <EastIndianChart chartData={chartData} config={config} />}
      </View>

      {/* Planet Details */}
      <ScrollView style={styles.planetList}>
        <Text style={styles.planetListTitle}>Planetary Positions</Text>
        {chartData.planets.map((planet) => (
          <View key={planet.name} style={styles.planetItem}>
            <Text style={styles.planetName}>
              {getPlanetGlyph(planet.name)} {planet.name}
              {planet.isRetrograde && ' ℞'}
            </Text>
            <Text style={styles.planetDetails}>
              {planet.rashi} • {planet.nakshatra} (Pada {planet.pada})
            </Text>
            <Text style={styles.planetLongitude}>
              {planet.longitude.toFixed(2)}°
            </Text>
          </View>
        ))}
      </ScrollView>
    </View>
  );
}

// Simplified - aspects calculation removed as not included in basic API response\n// For production apps, you would calculate aspects based on planetary longitudes\n// or use a dedicated aspects API endpoint

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FFF8F0',
  },
  styleSelector: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    padding: 16,
    backgroundColor: '#FFFFFF',
  },
  styleButton: {
    paddingHorizontal: 20,
    paddingVertical: 10,
    borderRadius: 8,
    backgroundColor: '#F3F4F6',
  },
  activeStyle: {
    backgroundColor: '#CD853F',
  },
  styleText: {
    fontSize: 14,
    fontWeight: '600',
    color: '#4A4A4A',
  },
  chartContainer: {
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#FFFFFF',
    margin: 16,
    borderRadius: 16,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    elevation: 3,
  },
  options: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    padding: 16,
  },
  optionButton: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 8,
    backgroundColor: '#F3F4F6',
  },
  optionText: {
    fontSize: 14,
    color: '#4A4A4A',
  },
  planetList: {
    backgroundColor: '#FFFFFF',
    margin: 16,
    borderRadius: 16,
    padding: 20,
  },
  planetListTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#8B4513',
    marginBottom: 16,
  },
  planetItem: {
    marginBottom: 12,
    paddingBottom: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#F3F4F6',
  },
  planetName: {
    fontSize: 16,
    fontWeight: '600',
    color: '#4A4A4A',
    marginBottom: 4,
  },
  planetDetails: {
    fontSize: 13,
    color: '#6B7280',
  },
  planetLongitude: {
    fontSize: 11,
    color: '#9CA3AF',
    marginTop: 2,
  },
});

Step 7: Navamsa Chart Support

Support D9 Navamsa chart using the /navamsa endpoint:

// components/NavamsaChartViewer.tsx
export function NavamsaChartViewer({ birthData }: Props) {
  const [navamsaData, setNavamsaData] = useState<ChartData | null>(null);

  async function loadNavamsa() {
    const response = await fetch('https://roxyapi.com/api/v2/vedic-astrology/navamsa', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': 'YOUR_API_KEY',
      },
      body: JSON.stringify(birthData),
    });
    
    const navamsa = await response.json();
    
    // Response structure is same as birth chart, but in chart property
    const data = transformBirthChart(navamsa.chart);
    setNavamsaData(data);
  }

  return navamsaData ? <ChartViewer chartData={navamsaData} /> : null;
}

Step 8: Interactive Features

Add zoom, pan, and planet details:

// components/InteractiveChart.tsx
import React, { useState } from 'react';
import { PanResponder, Animated } from 'react-native';

export function InteractiveChart({ chartData, config }: Props) {
  const [scale] = useState(new Animated.Value(1));
  const [pan] = useState(new Animated.ValueXY());
  const [selectedPlanet, setSelectedPlanet] = useState<string | null>(null);

  const panResponder = PanResponder.create({
    onMoveShouldSetPanResponder: () => true,
    onPanResponderMove: Animated.event([null, { dx: pan.x, dy: pan.y }], {
      useNativeDriver: false,
    }),
    onPanResponderRelease: () => {
      Animated.spring(pan, {
        toValue: { x: 0, y: 0 },
        useNativeDriver: false,
      }).start();
    },
  });

  function handlePinch(event: any) {
    const { scale: newScale } = event.nativeEvent;
    Animated.spring(scale, {
      toValue: newScale,
      useNativeDriver: true,
    }).start();
  }

  function handlePlanetTap(planet: string) {
    setSelectedPlanet(planet);
  }

  return (
    <View>
      <Animated.View
        {...panResponder.panHandlers}
        style={{
          transform: [
            { scale },
            { translateX: pan.x },
            { translateY: pan.y },
          ],
        }}
      >
        {/* Chart component */}
      </Animated.View>

      {/* Planet detail modal */}
      {selectedPlanet && (
        <PlanetDetailModal
          planet={chartData.planets.find(p => p.planet === selectedPlanet)!}
          onClose={() => setSelectedPlanet(null)}
        />
      )}
    </View>
  );
}

Step 9: Export Functionality

Save charts as images or PDFs:

// services/chart-export.ts
import ViewShot from 'react-native-view-shot';
import * as Sharing from 'expo-sharing';
import * as FileSystem from 'expo-file-system';

export class ChartExportService {
  async exportAsPNG(chartRef: any, fileName: string) {
    try {
      const uri = await ViewShot.captureRef(chartRef, {
        format: 'png',
        quality: 1.0,
      });

      await Sharing.shareAsync(uri, {
        mimeType: 'image/png',
        dialogTitle: 'Share Chart',
      });
    } catch (error) {
      console.error('Export failed:', error);
    }
  }

  async exportAsPDF(chartData: ChartData, style: ChartStyle) {
    // Generate PDF with chart and planetary details
    const html = this.generatePDFHTML(chartData, style);
    
    // Convert to PDF using react-native-html-to-pdf
    const { filePath } = await RNHTMLtoPDF.convert({
      html,
      fileName: 'kundli_chart',
      directory: 'Documents',
    });

    await Sharing.shareAsync(filePath);
  }

  private generatePDFHTML(chartData: ChartData, style: ChartStyle): string {
    return `
      <!DOCTYPE html>
      <html>
        <head>
          <style>
            body { font-family: Arial, sans-serif; }
            .chart { width: 600px; height: 600px; margin: 20px auto; }
            .planets { margin: 20px; }
            .planet-row { padding: 10px; border-bottom: 1px solid #ccc; }
          </style>
        </head>
        <body>
          <h1>Kundli Chart (${style.charAt(0).toUpperCase() + style.slice(1)} Indian Style)</h1>
          <div class="chart">
            <!-- SVG chart here -->
          </div>
          <div class="planets">
            <h2>Planetary Positions</h2>
            ${chartData.planets.map(planet => `
              <div class="planet-row">
                <strong>${planet.planet}</strong>: 
                ${planet.rashi} • House ${planet.house} • ${planet.nakshatra}
                ${planet.isRetrograde ? ' (Retrograde)' : ''}
              </div>
            `).join('')}
          </div>
        </body>
      </html>
    `;
  }
}

Step 10: Production Considerations

1. Performance Optimization

// Memoize expensive calculations
const chartPath = useMemo(() => {
  return generateChartPath(chartData, config);
}, [chartData, config]);

// Use React.memo for chart components
export const NorthIndianChart = React.memo(NorthIndianChartComponent);

2. Responsive Sizing

function useChartSize() {
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const subscription = Dimensions.addEventListener('change', ({ window }) => {
      const size = Math.min(window.width * 0.9, 500);
      setDimensions({ width: size, height: size });
    });

    return () => subscription?.remove();
  }, []);

  return dimensions;
}

3. Accessibility

// Add accessibility labels
<Svg accessible={true} accessibilityLabel="Vedic Astrology Birth Chart">
  {/* Chart elements */}
</Svg>

<TouchableOpacity
  accessible={true}
  accessibilityLabel={`${planet} in ${rashi}`}
  accessibilityRole="button"
>
  {/* Planet glyph */}
</TouchableOpacity>

Conclusion

You now have a production-ready Rashi chart visualizer supporting all three Indian chart styles. Users can view birth charts in their preferred regional format with accurate planetary positions from the API.

Key features implemented:

  • North Indian diamond layout (Lagna at top)
  • South Indian square grid layout (fixed rashi positions)
  • East Indian rectangular layout
  • Planetary glyphs with retrograde indicators (℞)
  • Lagna (ascendant) highlighting
  • Rashi-based planetary grouping (matches API structure)
  • Interactive chart style switching
  • Planetary position display with nakshatras and padas
  • Longitude display in degrees
  • Export to PNG
  • Navamsa (D9) chart support

The API structure groups planets by rashi (zodiac sign), making rendering straightforward. Each chart style displays the same data with different visual conventions suited to regional preferences.

Ready to add chart visualization to your app? Sign up for RoxyAPI and get accurate birth chart data with proper rashi grouping. Full documentation at roxyapi.com/docs.