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?
| Feature | SVG | Canvas |
| Interactive events | Native DOM events per element | Manual hit-testing required |
| Accessibility | ARIA attributes, screen readers | None without overlay |
| Responsiveness | viewBox scales perfectly | Manual resize handling |
| CSS animations | Full CSS transition support | JavaScript-only |
| Print quality | Vector — infinite resolution | Pixel-based — blurry at 2x |
| Performance | Good up to ~500 elements | Better 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:
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();
}
const chartData = await fetchBirthChart(
'1990-06-15T10:30:00',
28.6139,
77.2090,
'+05:30'
);
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):
<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):
const CX = 250, CY = 250;
function longitudeToXY(longitude, radius) {
const angle = (longitude + 180) * Math.PI / 180;
return {
x: CX + radius * Math.cos(angle),
y: CY - radius * Math.sin(angle)
};
}
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);
}
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',
'#FF6B6B', '#4CAF50', '#FFC107', '#2196F3',
'#FF6B6B', '#4CAF50', '#FFC107', '#2196F3'
];
function renderZodiacSigns(svgGroup) {
ZODIAC_GLYPHS.forEach((glyph, i) => {
const midLon = i * 30 + 15;
const pos = longitudeToXY(midLon, 205);
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) => {
const radius = 165 + (idx % 3) * 8;
const pos = longitudeToXY(planet.longitude, radius);
const color = PLANET_COLORS[planet.name] || '#fff';
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);
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);
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);
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);
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:
function renderHouseCusps(chartData, svgElement) {
const cusps = chartData.houseCusps || [];
const svg = d3.select(svgElement);
const houseGroup = svg.select('#house-lines');
cusps.forEach((cuspLon, i) => {
const p1 = longitudeToXY(cuspLon, 60);
const p2 = longitudeToXY(cuspLon, 150);
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')
.attr('stroke-width', i === 0 ? 2 : 0.8);
const nextCusp = cusps[(i + 1) % 12];
let midLon = (cuspLon + nextCusp) / 2;
if (nextCusp < cuspLon) midLon = (cuspLon + nextCusp + 360) / 2;
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');
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
);
svg.querySelector('text')?.remove();
drawZodiacDividers(svg.querySelector('#zodiac-signs'));
renderZodiacSigns(svg.querySelector('#zodiac-signs'));
renderHouseCusps(chartData, svg);
renderPlanets(svg.querySelector('#planets'), chartData.planets, tooltip);
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>';
}
}
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:
#chart-container {
position: relative;
width: 100%;
max-width: 600px;
margin: 0 auto;
}
#birth-chart {
width: 100%;
height: auto;
}
.planet-group {
transition: opacity 0.5s ease-in, transform 0.3s ease;
transform-origin: center;
}
.planet-group:hover {
transform: scale(1.3);
}
@media (max-width: 350px) {
.planet-label { display: none; }
}
@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:
- Use SVG + viewBox for resolution-independent, interactive charts
- Vedika API's
/v2/astrology/birth-chart returns planetary longitudes ready for plotting
- The
longitudeToXY() formula converts any ecliptic position to SVG coordinates
- D3.js simplifies arc and path calculations for house divisions
- Cache birth chart API responses — they're deterministic and never change
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.