Build Interactive Birth Chart Visualizations with SVG & Vedika API

Interactive Birth Chart SVG Visualization with Vedika API
Published: March 13, 2026 | By Vedika Intelligence | Reading time: 14 minutes

A birth chart is only as powerful as its presentation. Raw JSON data — "Sun at 24.3° Gemini in house 3" — means nothing to most users. But a beautifully rendered zodiac wheel, with planets plotted at exact positions, house lines radiating from the center, and interactive tooltips explaining each placement, transforms raw astrology data into an experience users understand and share.

In this tutorial, we'll build a complete SVG birth chart renderer driven by real planetary positions from Vedika API. We'll start with pure SVG for the zodiac structure, add D3.js for coordinate calculations, implement interactive tooltips and hover effects, and make the whole thing responsive for mobile and print. No external chart library required — just SVG, JavaScript, and Vedika's Swiss Ephemeris-precision data.

What You'll Build: An interactive SVG birth chart wheel that fetches real planetary positions from Vedika API, renders the zodiac wheel with house divisions, plots all 9 Vedic grahas at their exact longitudes, shows interactive tooltips on hover, animates planet entry with CSS transitions, and scales responsively from 300px mobile to 1200px print.

SVG vs Canvas: Which to Use for Birth Charts?

FeatureSVGCanvas
Interactive eventsNative DOM events per elementManual hit-testing required
AccessibilityARIA attributes, screen readersNone without overlay
ResponsivenessviewBox scales perfectlyManual resize handling
CSS animationsFull CSS transition supportJavaScript-only
Print qualityVector — infinite resolutionPixel-based — blurry at 2x
PerformanceGood up to ~500 elementsBetter for 1000+ elements

For a 9-planet birth chart with 12 house lines and zodiac ring labels, SVG is clearly superior. We get free interactivity, crisp print output, and responsive scaling at no extra cost.

Step 1: Fetch Birth Chart Data from Vedika API

Start by fetching real planetary positions. Vedika API returns decimal longitudes (0-360°) for each planet — exactly what we need for chart rendering:

// vedika-chart.js — Fetch birth chart data const VEDIKA_BASE = 'https://api.vedika.io'; const API_KEY = 'vk_live_YOUR_KEY_HERE'; async function fetchBirthChart(datetime, latitude, longitude, timezone = '+05:30') { const response = await fetch(`${VEDIKA_BASE}/v2/astrology/birth-chart`, { method: 'POST', headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ datetime, latitude, longitude, timezone }) }); if (!response.ok) throw new Error(`API error: ${response.status}`); return response.json(); } // Example usage — Delhi birth chart const chartData = await fetchBirthChart( '1990-06-15T10:30:00', 28.6139, // New Delhi latitude 77.2090, // New Delhi longitude '+05:30' ); // Vedika API response includes planets array: // [{ name: 'Sun', sign: 'Gemini', house: 3, degree: 24.3, longitude: 84.3 }, // { name: 'Moon', sign: 'Scorpio', house: 8, degree: 12.7, longitude: 222.7 }, ...]

Step 2: Core SVG Zodiac Wheel Structure

The birth chart wheel has three concentric rings: the outer zodiac ring (12 signs × 30°), the middle house ring (12 houses), and the inner planet placement area. We use a 500×500 viewBox with center at (250, 250):

<!-- HTML structure --> <div id="chart-container" style="max-width: 600px; margin: 0 auto;"> <svg id="birth-chart" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: auto;" role="img" aria-label="Vedic birth chart" > <!-- Background --> <circle cx="250" cy="250" r="240" fill="#0f0f23"/> <!-- Zodiac ring (outer) --> <circle cx="250" cy="250" r="220" fill="none" stroke="#4a4a8a" stroke-width="1"/> <circle cx="250" cy="250" r="190" fill="none" stroke="#4a4a8a" stroke-width="1"/> <!-- House ring (middle) --> <circle cx="250" cy="250" r="150" fill="none" stroke="#6a6aaa" stroke-width="0.5"/> <!-- Planet placement ring (inner) --> <circle cx="250" cy="250" r="60" fill="#1a1a3e" stroke="#9333ea" stroke-width="1"/> <!-- Dynamic elements injected by JavaScript --> <g id="zodiac-signs"></g> <g id="house-lines"></g> <g id="planets"></g> </svg> <!-- Tooltip overlay --> <div id="planet-tooltip" style=" position: absolute; display: none; background: rgba(10,10,40,0.95); color: white; padding: 0.5rem 0.75rem; border-radius: 6px; font-size: 0.85rem; pointer-events: none; border: 1px solid #9333ea; z-index: 10; "></div> </div>

Step 3: Coordinate Math — Longitude to SVG Position

Converting planetary longitudes (0-360°) to SVG (x, y) coordinates is the core of chart rendering. The formula maps ecliptic longitude to a point on a circle, with Aries 0° at the 9 o'clock position (traditional chart orientation):

// Coordinate conversion utilities const CX = 250, CY = 250; // SVG center /** * Convert ecliptic longitude to SVG (x, y) at given radius * longitude: 0-360 (0 = Aries, 90 = Cancer, 180 = Libra, 270 = Capricorn) * Traditional charts: Aries at 9 o'clock, counter-clockwise in Western, * clockwise in Vedic. We use counter-clockwise (Western convention). */ function longitudeToXY(longitude, radius) { // Shift so Aries 0° is at left (9 o'clock = 180° in SVG math) const angle = (longitude + 180) * Math.PI / 180; return { x: CX + radius * Math.cos(angle), y: CY - radius * Math.sin(angle) // Negate y: SVG y-axis is inverted }; } // Draw a line from inner radius to outer radius at a given longitude function drawRadialLine(svg, longitude, r1, r2, stroke = '#4a4a8a', width = 1) { const p1 = longitudeToXY(longitude, r1); const p2 = longitudeToXY(longitude, r2); const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); line.setAttribute('x1', p1.x); line.setAttribute('y1', p1.y); line.setAttribute('x2', p2.x); line.setAttribute('y2', p2.y); line.setAttribute('stroke', stroke); line.setAttribute('stroke-width', width); svg.appendChild(line); } // Draw zodiac sign dividers (every 30°) function drawZodiacDividers(svgGroup) { for (let i = 0; i < 12; i++) { const lon = i * 30; drawRadialLine(svgGroup, lon, 190, 220, '#6a6aaa', 1.5); } }

Step 4: Render Zodiac Sign Labels

Place the 12 zodiac glyphs at the midpoint of each sign's 30° arc, in the outer ring between radius 190 and 220:

const ZODIAC_GLYPHS = [ '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓' ]; const SIGN_COLORS = [ '#FF6B6B', '#4CAF50', '#FFC107', '#2196F3', // Fire, Earth, Air, Water '#FF6B6B', '#4CAF50', '#FFC107', '#2196F3', '#FF6B6B', '#4CAF50', '#FFC107', '#2196F3' ]; function renderZodiacSigns(svgGroup) { ZODIAC_GLYPHS.forEach((glyph, i) => { const midLon = i * 30 + 15; // Midpoint of each sign const pos = longitudeToXY(midLon, 205); // Between ring radii 190-220 const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); text.setAttribute('x', pos.x); text.setAttribute('y', pos.y); text.setAttribute('text-anchor', 'middle'); text.setAttribute('dominant-baseline', 'central'); text.setAttribute('font-size', '14'); text.setAttribute('fill', SIGN_COLORS[i]); text.textContent = glyph; svgGroup.appendChild(text); }); }

Step 5: Plot Planets from Vedika API Data

Now the critical part — plotting planets at their exact positions from Vedika API's Swiss Ephemeris calculations. Each planet gets a symbol, colored circle, and hover event for tooltips:

const PLANET_SYMBOLS = { Sun: '☉', Moon: '☽', Mars: '♂', Mercury: '☿', Jupiter: '♃', Venus: '♀', Saturn: '♄', Rahu: '☊', Ketu: '☋' }; const PLANET_COLORS = { Sun: '#FFD700', Moon: '#C0C0C0', Mars: '#FF4444', Mercury: '#00BCD4', Jupiter: '#FF9800', Venus: '#E91E63', Saturn: '#9E9E9E', Rahu: '#673AB7', Ketu: '#795548' }; function renderPlanets(svgGroup, planets, tooltip) { planets.forEach((planet, idx) => { // Stagger radius slightly to prevent planet overlap const radius = 165 + (idx % 3) * 8; const pos = longitudeToXY(planet.longitude, radius); const color = PLANET_COLORS[planet.name] || '#fff'; // Planet group (circle + symbol) const g = document.createElementNS('http://www.w3.org/2000/svg', 'g'); g.setAttribute('class', 'planet-group'); g.setAttribute('style', 'cursor: pointer; opacity: 0; transition: opacity 0.5s;'); g.setAttribute('data-delay', idx * 100); // Background circle const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); circle.setAttribute('cx', pos.x); circle.setAttribute('cy', pos.y); circle.setAttribute('r', '12'); circle.setAttribute('fill', `${color}22`); circle.setAttribute('stroke', color); circle.setAttribute('stroke-width', '1.5'); g.appendChild(circle); // Planet symbol const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); text.setAttribute('x', pos.x); text.setAttribute('y', pos.y); text.setAttribute('text-anchor', 'middle'); text.setAttribute('dominant-baseline', 'central'); text.setAttribute('font-size', '12'); text.setAttribute('fill', color); text.textContent = PLANET_SYMBOLS[planet.name] || planet.name[0]; g.appendChild(text); // Tooltip events g.addEventListener('mouseenter', (e) => { tooltip.style.display = 'block'; tooltip.innerHTML = ` <strong>${planet.name}</strong><br> ${planet.sign} ${planet.degree.toFixed(1)}°<br> House ${planet.house} ${planet.isRetrograde ? '<br><em>Retrograde (R)</em>' : ''} ${planet.dignity ? `<br>${planet.dignity}` : ''} `; const rect = e.target.closest('svg').getBoundingClientRect(); tooltip.style.left = `${e.clientX - rect.left + 15}px`; tooltip.style.top = `${e.clientY - rect.top - 10}px`; }); g.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; }); svgGroup.appendChild(g); // Staggered entry animation setTimeout(() => { g.style.opacity = '1'; }, idx * 100 + 300); }); }

Step 6: D3.js Integration for House Arcs

Vedika API's birth chart response includes ascendant and house cusp longitudes. Use D3.js to render the house division arcs cleanly:

// Requires D3.js v7: <script src="https://d3js.org/d3.v7.min.js"></script> function renderHouseCusps(chartData, svgElement) { const cusps = chartData.houseCusps || []; // Array of 12 longitudes from Vedika API const svg = d3.select(svgElement); const houseGroup = svg.select('#house-lines'); // Draw house cusp lines from inner circle to middle ring cusps.forEach((cuspLon, i) => { const p1 = longitudeToXY(cuspLon, 60); // Inner circle const p2 = longitudeToXY(cuspLon, 150); // Middle ring houseGroup.append('line') .attr('x1', p1.x).attr('y1', p1.y) .attr('x2', p2.x).attr('y2', p2.y) .attr('stroke', i === 0 ? '#FF6B6B' : '#4a4a8a') // ASC in red .attr('stroke-width', i === 0 ? 2 : 0.8); // House number label at midpoint between this cusp and next const nextCusp = cusps[(i + 1) % 12]; let midLon = (cuspLon + nextCusp) / 2; if (nextCusp < cuspLon) midLon = (cuspLon + nextCusp + 360) / 2; // Wrap-around const labelPos = longitudeToXY(midLon, 105); houseGroup.append('text') .attr('x', labelPos.x).attr('y', labelPos.y) .attr('text-anchor', 'middle').attr('dominant-baseline', 'central') .attr('font-size', '9').attr('fill', '#8888aa') .text(i + 1); }); }

Step 7: Assemble and Initialize

Wire everything together — fetch the Vedika API data, render all chart layers in sequence, and handle errors gracefully:

async function initChart(birthData) { const svg = document.getElementById('birth-chart'); const tooltip = document.getElementById('planet-tooltip'); // Show loading state svg.innerHTML += '<text x="250" y="250" text-anchor="middle" fill="#9333ea" font-size="14">Loading chart...</text>'; try { const chartData = await fetchBirthChart( birthData.datetime, birthData.latitude, birthData.longitude, birthData.timezone ); // Clear loading text svg.querySelector('text')?.remove(); // Render in layer order (back to front) drawZodiacDividers(svg.querySelector('#zodiac-signs')); renderZodiacSigns(svg.querySelector('#zodiac-signs')); renderHouseCusps(chartData, svg); renderPlanets(svg.querySelector('#planets'), chartData.planets, tooltip); // Add ascendant label const ascData = chartData.ascendant; const ascPos = longitudeToXY(ascData.longitude, 35); const ascText = document.createElementNS('http://www.w3.org/2000/svg', 'text'); ascText.setAttribute('x', ascPos.x); ascText.setAttribute('y', ascPos.y); ascText.setAttribute('text-anchor', 'middle'); ascText.setAttribute('font-size', '9'); ascText.setAttribute('fill', '#FF6B6B'); ascText.textContent = 'ASC'; svg.appendChild(ascText); } catch (err) { console.error('Chart error:', err); svg.innerHTML = '<text x="250" y="250" text-anchor="middle" fill="#FF4444" font-size="12">Chart load failed. Check API key.</text>'; } } // Initialize with sample birth data initChart({ datetime: '1990-06-15T10:30:00', latitude: 28.6139, longitude: 77.2090, timezone: '+05:30' });

Start Visualizing Real Planetary Data

Get Swiss Ephemeris-precision longitudes for all 9 grahas, house cusps, and ascendant. Free sandbox — no credit card required.

API Docs Pricing

Responsive Scaling and CSS Animations

Add these CSS rules to make the chart responsive and animate the planet entry:

/* birth-chart.css */ #chart-container { position: relative; width: 100%; max-width: 600px; margin: 0 auto; } #birth-chart { width: 100%; height: auto; /* viewBox handles aspect ratio */ } /* Animate planet appearance */ .planet-group { transition: opacity 0.5s ease-in, transform 0.3s ease; transform-origin: center; } .planet-group:hover { transform: scale(1.3); } /* Hide planet labels on small screens */ @media (max-width: 350px) { .planet-label { display: none; } } /* Print: black and white chart */ @media print { #birth-chart { filter: grayscale(100%); max-width: 400px; } #planet-tooltip { display: none !important; } }

Why Vedika API for Chart Rendering?

Exact Degrees

Swiss Ephemeris returns planetary longitudes to 4 decimal places. Competing APIs return rounded sign positions — not usable for chart drawing.

House Cusps Included

Birth chart response includes all 12 house cusp longitudes — essential for drawing house division lines in the wheel.

Dignity & Status

Each planet includes dignity (exalted/debilitated/own), retrograde flag, and combust status — display these in your tooltip layer.

Vedic & Western

Vedic (sidereal) and Western (tropical) charts from the same API. Switch between renderings with a single parameter change.

Conclusion

Building SVG birth charts from Vedika API data requires less code than most developers expect. The Vedika API handles all astronomical computation — your renderer only needs to map longitudes to screen coordinates. The result is a crisp, interactive, print-quality chart that works on every screen size.

Key takeaways:

View the complete source code and live demo in our developer documentation. Have questions? Our engineering team responds within one business day.


About Vedika Intelligence: Vedika is the only B2B astrology API with AI-powered chatbot capabilities, serving production apps worldwide. Our platform provides 140+ endpoints with Swiss Ephemeris precision, supports 30 languages, and starts at $12/month. The world's first astrology MCP server enables AI agents to query planetary positions and predictions natively.

Try the #1 Vedic Astrology API

140+ endpoints, 30 languages, Swiss Ephemeris precision. Free sandbox included — no credit card required.

Get Free API Key