Inline SVG and Manipulation

Integrating and Dynamically Controlling Vector Graphics with JavaScript

Introduction to Inline SVG

Inline SVG is the practice of embedding SVG code directly within HTML documents, rather than referencing external SVG files. This approach offers powerful advantages for creating interactive, responsive, and dynamic graphics that can be controlled through JavaScript and styled with CSS.

flowchart TD A[SVG Integration Methods] A --> B[Inline SVG] A --> C[External SVG] B -->|Benefits| D[Direct DOM Access] B -->|Benefits| E[CSS Styling] B -->|Benefits| F[JavaScript Manipulation] B -->|Benefits| G[No Additional HTTP Requests] C -->|Benefits| H[Browser Caching] C -->|Benefits| I[Separation of Concerns] C -->|Benefits| J[Reuse Across Pages]

By embedding SVG directly in the HTML document, the SVG elements become part of the Document Object Model (DOM), allowing for:

Think of inline SVG as having the graphic's building blocks directly accessible in your code, similar to how you can interact with other HTML elements like buttons or divs. This is in contrast to using an image tag with an SVG source, which treats the entire SVG as a single, opaque unit.

Embedding SVG in HTML

There are multiple ways to include SVG in a web page, each with different characteristics and capabilities:

Method Code Example DOM Access CSS Styling JavaScript Best For
Inline SVG <svg>...</svg> Full access Full styling Full control Interactive graphics
Image Tag <img src="file.svg"> No access Limited (filter effects only) No control Static images
CSS Background background: url(file.svg); No access Limited (filter effects only) No control Decorative elements
Object Tag <object data="file.svg"> Limited access External CSS only Limited control Isolated graphics
Iframe <iframe src="file.svg"> Limited access External CSS only Limited control Isolated documents
Use with External Source <use href="file.svg#id"> Limited access Limited styling Limited control Reusable components

For this lecture, we'll focus on the inline approach, which offers the greatest flexibility and control:

<!-- Basic inline SVG example -->
<svg width="200" height="200" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <circle id="myCircle" cx="50" cy="50" r="40" fill="#2196f3" stroke="#0d47a1" stroke-width="2" />
    <text x="50" y="50" text-anchor="middle" fill="white" font-family="Arial" font-size="10">Inline SVG</text>
</svg>

This code directly embeds an SVG element in the HTML document, making all its child elements (the circle and text) accessible to JavaScript and CSS.

Manipulating SVG with JavaScript

One of the key advantages of inline SVG is the ability to manipulate its elements using standard DOM methods:

// Get a reference to an SVG element by ID
const circle = document.getElementById('myCircle');

// Change attributes
circle.setAttribute('fill', 'red');
circle.setAttribute('r', '30');

// Change styles
circle.style.opacity = '0.8';

// Add event listeners
circle.addEventListener('click', function() {
    this.setAttribute('fill', 'green');
});

// Create new SVG elements
const svgNS = "http://www.w3.org/2000/svg";
const newRect = document.createElementNS(svgNS, 'rect');
newRect.setAttribute('x', '10');
newRect.setAttribute('y', '10');
newRect.setAttribute('width', '80');
newRect.setAttribute('height', '80');
newRect.setAttribute('fill', '#ff9800');

// Add the new element to the SVG
const svg = document.querySelector('svg');
svg.appendChild(newRect);

// Remove elements
svg.removeChild(circle);

Note the use of createElementNS() instead of createElement(). This is because SVG elements belong to the SVG namespace, not the HTML namespace.

This JavaScript-based manipulation is similar to how you might work with HTML elements, with the key difference being the namespace and some SVG-specific attributes.

Styling SVG with CSS

Inline SVG elements can be styled using CSS, either inline, in a style tag, or in an external stylesheet:

/* CSS Styling for SVG Elements */
svg {
    border: 1px solid #ccc;
    background-color: #f5f5f5;
}

circle {
    fill: #2196f3;
    stroke: #0d47a1;
    stroke-width: 2;
    transition: all 0.3s ease;
}

circle:hover {
    fill: #ff5722;
    transform: scale(1.1);
}

.special-rect {
    fill: #4caf50;
    opacity: 0.8;
}

#specific-path {
    stroke-dasharray: 10 5;
    stroke-linecap: round;
}

You can use:

SVG-specific CSS properties include:

Standard CSS properties like opacity, transform, and filter also work with SVG elements. This allows for powerful effects like hover states, animations, and visual transformations.

Basic SVG :hover state stroke-dasharray transform: rotate

SVG Animation with CSS

One of the most powerful aspects of inline SVG is the ability to animate elements using CSS transitions and animations:

CSS Transitions

/* Simple transition on hover */
.circle {
    fill: #2196f3;
    transition: fill 0.3s ease, transform 0.3s ease;
}

.circle:hover {
    fill: #ff5722;
    transform: scale(1.2);
}

CSS Keyframe Animations

/* Pulsing animation */
@keyframes pulse {
    0% {
        transform: scale(1);
        opacity: 1;
    }
    50% {
        transform: scale(1.2);
        opacity: 0.8;
    }
    100% {
        transform: scale(1);
        opacity: 1;
    }
}

.pulsing-circle {
    fill: #4caf50;
    animation: pulse 2s infinite ease-in-out;
}

/* Path drawing animation */
@keyframes draw {
    0% {
        stroke-dashoffset: 1000;
    }
    100% {
        stroke-dashoffset: 0;
    }
}

.drawing-path {
    stroke: #673ab7;
    stroke-width: 2;
    fill: none;
    stroke-dasharray: 1000;
    stroke-dashoffset: 1000;
    animation: draw 3s forwards ease-in-out;
}

The stroke-dasharray and stroke-dashoffset properties are particularly useful for line drawing animations, creating the effect of a path being drawn over time.

Pulse Animation Rotation Animation Path Drawing Animation Color Animation

CSS animations with SVG enable sophisticated visual effects without requiring JavaScript, making them efficient and easy to implement.

Dynamic SVG Creation

JavaScript can be used to generate SVG content dynamically, which is particularly useful for data visualization, interactive applications, and generative art:

// Create an SVG element
function createSVG(width, height) {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.setAttribute("width", width);
    svg.setAttribute("height", height);
    svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
    return svg;
}

// Create a circle element
function createCircle(cx, cy, r, fill) {
    const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
    circle.setAttribute("cx", cx);
    circle.setAttribute("cy", cy);
    circle.setAttribute("r", r);
    circle.setAttribute("fill", fill);
    return circle;
}

// Usage example: create a dynamic set of circles
function createBubbleChart(data, container) {
    const svg = createSVG(500, 300);
    
    // Add circles based on data
    data.forEach(item => {
        const circle = createCircle(
            item.x,
            item.y,
            item.value * 2, // radius based on value
            `hsl(${item.category * 30}, 70%, 60%)` // color based on category
        );
        
        // Add tooltip behavior
        circle.setAttribute("data-tooltip", item.label);
        circle.addEventListener("mouseover", showTooltip);
        circle.addEventListener("mouseout", hideTooltip);
        
        svg.appendChild(circle);
    });
    
    container.appendChild(svg);
}

// Example data for our chart
const sampleData = [
    { x: 50, y: 50, value: 10, category: 0, label: "Item A" },
    { x: 150, y: 80, value: 15, category: 1, label: "Item B" },
    { x: 100, y: 150, value: 20, category: 2, label: "Item C" },
    { x: 200, y: 120, value: 12, category: 3, label: "Item D" },
    { x: 250, y: 200, value: 18, category: 4, label: "Item E" }
];

// Create the chart
createBubbleChart(sampleData, document.getElementById("chart-container"));

This pattern allows you to create SVG content based on data, user interactions, or application state. The key steps are:

  1. Create SVG elements using createElementNS()
  2. Set attributes to define appearance and behavior
  3. Append elements to the SVG container
  4. Add any needed event listeners for interactivity

This approach is similar to how you might generate HTML content dynamically but requires the use of the SVG namespace and specific SVG attributes.

SVG and Data Visualization

Inline SVG is particularly well-suited for creating interactive data visualizations. Here's a simple bar chart example:

<!-- SVG bar chart with JavaScript interactivity -->
<svg id="barChart" width="600" height="400" viewBox="0 0 600 400">
    <!-- Chart will be generated here -->
</svg>

<script>
    // Sample data
    const data = [
        { label: "Jan", value: 120 },
        { label: "Feb", value: 150 },
        { label: "Mar", value: 180 },
        { label: "Apr", value: 110 },
        { label: "May", value: 200 },
        { label: "Jun", value: 170 }
    ];
    
    // Chart dimensions
    const svg = document.getElementById('barChart');
    const width = 600;
    const height = 400;
    const margin = { top: 40, right: 20, bottom: 50, left: 50 };
    const chartWidth = width - margin.left - margin.right;
    const chartHeight = height - margin.top - margin.bottom;
    
    // Create a group for the chart content with margins
    const chart = document.createElementNS("http://www.w3.org/2000/svg", "g");
    chart.setAttribute("transform", `translate(${margin.left}, ${margin.top})`);
    svg.appendChild(chart);
    
    // Add title
    const title = document.createElementNS("http://www.w3.org/2000/svg", "text");
    title.textContent = "Monthly Sales 2025";
    title.setAttribute("x", width / 2);
    title.setAttribute("y", 20);
    title.setAttribute("text-anchor", "middle");
    title.setAttribute("font-weight", "bold");
    svg.appendChild(title);
    
    // Calculate scales
    const barWidth = chartWidth / data.length * 0.8;
    const barSpacing = chartWidth / data.length * 0.2;
    const maxValue = Math.max(...data.map(d => d.value));
    const yScale = chartHeight / maxValue;
    
    // Add bars and labels
    data.forEach((item, i) => {
        // Calculate positions
        const x = i * (barWidth + barSpacing);
        const barHeight = item.value * yScale;
        const y = chartHeight - barHeight;
        
        // Create bar
        const bar = document.createElementNS("http://www.w3.org/2000/svg", "rect");
        bar.setAttribute("x", x);
        bar.setAttribute("y", y);
        bar.setAttribute("width", barWidth);
        bar.setAttribute("height", barHeight);
        bar.setAttribute("fill", "#2196f3");
        bar.setAttribute("data-value", item.value);
        
        // Add hover effect
        bar.addEventListener("mouseover", function() {
            this.setAttribute("fill", "#ff5722");
            valueText.textContent = item.value;
            valueText.setAttribute("x", x + barWidth / 2);
            valueText.setAttribute("y", y - 10);
            valueText.style.display = "block";
        });
        
        bar.addEventListener("mouseout", function() {
            this.setAttribute("fill", "#2196f3");
            valueText.style.display = "none";
        });
        
        chart.appendChild(bar);
        
        // Add x-axis label
        const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
        label.textContent = item.label;
        label.setAttribute("x", x + barWidth / 2);
        label.setAttribute("y", chartHeight + 20);
        label.setAttribute("text-anchor", "middle");
        chart.appendChild(label);
    });
    
    // Add y-axis line
    const yAxis = document.createElementNS("http://www.w3.org/2000/svg", "line");
    yAxis.setAttribute("x1", 0);
    yAxis.setAttribute("y1", 0);
    yAxis.setAttribute("x2", 0);
    yAxis.setAttribute("y2", chartHeight);
    yAxis.setAttribute("stroke", "#333");
    chart.appendChild(yAxis);
    
    // Add x-axis line
    const xAxis = document.createElementNS("http://www.w3.org/2000/svg", "line");
    xAxis.setAttribute("x1", 0);
    xAxis.setAttribute("y1", chartHeight);
    xAxis.setAttribute("x2", chartWidth);
    xAxis.setAttribute("y2", chartHeight);
    xAxis.setAttribute("stroke", "#333");
    chart.appendChild(xAxis);
    
    // Create value text element (for hover)
    const valueText = document.createElementNS("http://www.w3.org/2000/svg", "text");
    valueText.setAttribute("text-anchor", "middle");
    valueText.setAttribute("font-weight", "bold");
    valueText.style.display = "none";
    chart.appendChild(valueText);
</script>

This example demonstrates several key concepts:

The ability to create, style, and manipulate individual elements makes SVG ideal for data visualization, where each graphical element typically represents a specific data point.

SVG Interactivity Patterns

There are several common patterns for adding interactivity to inline SVG:

Hover Effects

// CSS approach
.interactive-element:hover {
    fill: #ff5722;
    transform: scale(1.1);
}

// JavaScript approach
element.addEventListener('mouseover', function() {
    this.setAttribute('fill', '#ff5722');
    this.setAttribute('transform', 'scale(1.1)');
});

element.addEventListener('mouseout', function() {
    this.setAttribute('fill', '#2196f3');
    this.setAttribute('transform', 'scale(1)');
});

Click Handling

element.addEventListener('click', function(event) {
    // Toggle selected state
    const isSelected = this.classList.toggle('selected');
    
    // Update appearance based on state
    if (isSelected) {
        this.setAttribute('stroke-width', 3);
        this.setAttribute('stroke', '#ff5722');
    } else {
        this.setAttribute('stroke-width', 1);
        this.setAttribute('stroke', '#333');
    }
    
    // Prevent event from bubbling up
    event.stopPropagation();
});

Tooltips

// HTML tooltip element
const tooltip = document.getElementById('tooltip');

// Add tooltip behavior to SVG elements
element.addEventListener('mousemove', function(event) {
    const value = this.getAttribute('data-value');
    tooltip.textContent = `Value: ${value}`;
    tooltip.style.left = `${event.pageX + 10}px`;
    tooltip.style.top = `${event.pageY - 20}px`;
    tooltip.style.display = 'block';
});

element.addEventListener('mouseout', function() {
    tooltip.style.display = 'none';
});

Drag and Drop

let draggedElement = null;
let offsetX = 0;
let offsetY = 0;

element.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', endDrag);

function startDrag(event) {
    draggedElement = this;
    
    // Calculate offset from element's current position
    const transform = draggedElement.getAttribute('transform');
    const match = /translate\((\d+),\s*(\d+)\)/.exec(transform) || [0, 0, 0];
    const currentX = parseInt(match[1]) || 0;
    const currentY = parseInt(match[2]) || 0;
    
    offsetX = event.clientX - currentX;
    offsetY = event.clientY - currentY;
    
    // Add a class for styling during drag
    draggedElement.classList.add('dragging');
    
    // Prevent default to avoid text selection, etc.
    event.preventDefault();
}

function drag(event) {
    if (!draggedElement) return;
    
    // Calculate new position
    const x = event.clientX - offsetX;
    const y = event.clientY - offsetY;
    
    // Update element position
    draggedElement.setAttribute('transform', `translate(${x}, ${y})`);
    
    event.preventDefault();
}

function endDrag() {
    if (!draggedElement) return;
    
    // Remove styling class
    draggedElement.classList.remove('dragging');
    draggedElement = null;
}

These interaction patterns make SVG graphics feel responsive and engaging, transforming static illustrations into interactive elements that enhance the user experience.

Practical Example: Interactive Organization Chart

Let's implement a comprehensive example that showcases many of the techniques we've discussed:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Interactive Organization Chart</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 900px;
            margin: 0 auto;
            padding: 20px;
        }
        
        #org-chart {
            width: 100%;
            height: 500px;
            border: 1px solid #ccc;
            margin: 20px 0;
        }
        
        /* SVG Styles */
        .person-box {
            fill: #e3f2fd;
            stroke: #1976d2;
            stroke-width: 2;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        
        .person-box:hover {
            fill: #bbdefb;
            transform: scale(1.05);
        }
        
        .person-box.selected {
            fill: #bbdefb;
            stroke: #0d47a1;
            stroke-width: 3;
        }
        
        .person-name {
            font-weight: bold;
            font-size: 14px;
            text-anchor: middle;
        }
        
        .person-title {
            font-size: 12px;
            text-anchor: middle;
            fill: #555;
        }
        
        .connection {
            stroke: #90caf9;
            stroke-width: 2;
            fill: none;
        }
        
        /* Tooltip */
        #tooltip {
            position: absolute;
            background-color: #333;
            color: #fff;
            padding: 10px;
            border-radius: 4px;
            display: none;
            pointer-events: none;
        }
        
        /* Person details panel */
        #person-details {
            padding: 15px;
            border: 1px solid #ccc;
            background-color: #f5f5f5;
            margin-top: 20px;
            display: none;
        }
        
        #details-name {
            font-size: 20px;
            font-weight: bold;
            margin: 0 0 5px 0;
        }
        
        #details-title {
            font-style: italic;
            color: #666;
            margin: 0 0 15px 0;
        }
    </style>
</head>
<body>
    <h1>Company Organization Chart</h1>
    <p>Click on any person to view their details. Drag people to reorganize the chart.</p>
    
    <svg id="org-chart" viewBox="0 0 900 500">
        <defs>
            <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
                <polygon points="0 0, 10 3.5, 0 7" fill="#90caf9"/>
            </marker>
        </defs>
        
        <!-- SVG content will be generated by JavaScript -->
    </svg>
    
    <div id="tooltip"></div>
    
    <div id="person-details">
        <h2 id="details-name"></h2>
        <p id="details-title"></p>
        <div id="details-info"></div>
    </div>
    
    <script>
        // Organization data
        const orgData = {
            id: "CEO001",
            name: "Alex Morgan",
            title: "Chief Executive Officer",
            info: "Joined the company in 2018. Previously CEO at TechInnovate. MBA from Harvard.",
            reports: [
                {
                    id: "CTO001",
                    name: "Jamie Chen",
                    title: "Chief Technology Officer",
                    info: "15 years of experience in software architecture. Ph.D. in Computer Science.",
                    reports: [
                        {
                            id: "DEV001",
                            name: "Taylor Wilson",
                            title: "Development Manager",
                            info: "Manages the engineering teams. 8 years at the company."
                        },
                        {
                            id: "DEV002",
                            name: "Jordan Smith",
                            title: "QA Lead",
                            info: "Leads quality assurance and testing. Previously at Microsoft."
                        }
                    ]
                },
                {
                    id: "CFO001",
                    name: "Casey Rivera",
                    title: "Chief Financial Officer",
                    info: "CPA with 12 years of finance experience. Previously worked at Deloitte.",
                    reports: [
                        {
                            id: "FIN001",
                            name: "Riley Johnson",
                            title: "Financial Controller",
                            info: "Manages financial reporting and accounting operations."
                        }
                    ]
                },
                {
                    id: "CMO001",
                    name: "Avery Peters",
                    title: "Chief Marketing Officer",
                    info: "20 years in digital marketing. Led campaigns for Fortune 500 companies."
                }
            ]
        };

        // SVG elements
        const svg = document.getElementById('org-chart');
        const tooltip = document.getElementById('tooltip');
        const personDetails = document.getElementById('person-details');
        const detailsName = document.getElementById('details-name');
        const detailsTitle = document.getElementById('details-title');
        const detailsInfo = document.getElementById('details-info');
        
        // Box dimensions
        const boxWidth = 150;
        const boxHeight = 70;
        const levelHeight = 120;
        
        // Person and connections tracking
        const people = new Map();
        const connections = [];
        let selectedPerson = null;
        
        // Draw the organization chart
        function drawChart() {
            // Clear existing content
            svg.innerHTML = '';
            
            // Add marker definition for arrows
            const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
            const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
            marker.setAttribute("id", "arrowhead");
            marker.setAttribute("markerWidth", "10");
            marker.setAttribute("markerHeight", "7");
            marker.setAttribute("refX", "9");
            marker.setAttribute("refY", "3.5");
            marker.setAttribute("orient", "auto");
            
            const arrowPath = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
            arrowPath.setAttribute("points", "0 0, 10 3.5, 0 7");
            arrowPath.setAttribute("fill", "#90caf9");
            
            marker.appendChild(arrowPath);
            defs.appendChild(marker);
            svg.appendChild(defs);
            
            // Add a group for connections
            const connectionsGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
            svg.appendChild(connectionsGroup);
            
            // Add a group for people
            const peopleGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
            svg.appendChild(peopleGroup);
            
            // Position and draw the CEO
            const ceoX = 450;
            const ceoY = 50;
            drawPerson(orgData, ceoX, ceoY, 0, peopleGroup);
            
            // Draw connections after all people are positioned
            connections.forEach(conn => {
                const { fromId, toId } = conn;
                const fromPerson = people.get(fromId);
                const toPerson = people.get(toId);
                
                if (fromPerson && toPerson) {
                    drawConnection(fromPerson.x + boxWidth/2, fromPerson.y + boxHeight,
                                  toPerson.x + boxWidth/2, toPerson.y, connectionsGroup);
                }
            });
        }
        
        // Draw a person box
        function drawPerson(person, x, y, level, parentGroup) {
            // Store position information
            people.set(person.id, { x, y, data: person });
            
            // Create group for this person
            const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
            group.setAttribute("transform", `translate(${x}, ${y})`);
            group.setAttribute("data-id", person.id);
            
            // Create rectangle
            const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
            rect.setAttribute("width", boxWidth);
            rect.setAttribute("height", boxHeight);
            rect.setAttribute("rx", "5");
            rect.setAttribute("ry", "5");
            rect.setAttribute("class", "person-box");
            
            // Create text for name
            const nameText = document.createElementNS("http://www.w3.org/2000/svg", "text");
            nameText.textContent = person.name;
            nameText.setAttribute("x", boxWidth / 2);
            nameText.setAttribute("y", 30);
            nameText.setAttribute("class", "person-name");
            
            // Create text for title
            const titleText = document.createElementNS("http://www.w3.org/2000/svg", "text");
            titleText.textContent = person.title;
            titleText.setAttribute("x", boxWidth / 2);
            titleText.setAttribute("y", 50);
            titleText.setAttribute("class", "person-title");
            
            // Add elements to the group
            group.appendChild(rect);
            group.appendChild(nameText);
            group.appendChild(titleText);
            
            // Add event listeners
            group.addEventListener("click", function() {
                showPersonDetails(person);
                
                // Update selected state
                if (selectedPerson) {
                    const prevSelected = document.querySelector(`.person-box.selected`);
                    if (prevSelected) prevSelected.classList.remove("selected");
                }
                
                rect.classList.add("selected");
                selectedPerson = person.id;
            });
            
            group.addEventListener("mouseover", function(e) {
                tooltip.textContent = person.name + " - " + person.title;
                tooltip.style.display = "block";
                tooltip.style.left = (e.pageX + 10) + "px";
                tooltip.style.top = (e.pageY - 30) + "px";
            });
            
            group.addEventListener("mousemove", function(e) {
                tooltip.style.left = (e.pageX + 10) + "px";
                tooltip.style.top = (e.pageY - 30) + "px";
            });
            
            group.addEventListener("mouseout", function() {
                tooltip.style.display = "none";
            });
            
            // Add drag behavior
            makeDraggable(group);
            
            // Add to parent group
            parentGroup.appendChild(group);
            
            // Draw direct reports if any
            if (person.reports && person.reports.length > 0) {
                const numReports = person.reports.length;
                const totalWidth = numReports * boxWidth + (numReports - 1) * 30;
                let startX = x + (boxWidth / 2) - (totalWidth / 2);
                
                person.reports.forEach((report, i) => {
                    const reportX = startX + i * (boxWidth + 30);
                    const reportY = y + levelHeight;
                    
                    // Draw the person
                    drawPerson(report, reportX, reportY, level + 1, parentGroup);
                    
                    // Add connection information
                    connections.push({ fromId: person.id, toId: report.id });
                });
            }
        }
        
        // Draw a connection line between two points
        function drawConnection(x1, y1, x2, y2, parentGroup) {
            const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
            
            // Calculate control points for a curved line
            const midY = y1 + (y2 - y1) / 2;
            
            // Path data for a smooth curve
            const d = `M${x1},${y1} C${x1},${midY} ${x2},${midY} ${x2},${y2}`;
            
            path.setAttribute("d", d);
            path.setAttribute("class", "connection");
            path.setAttribute("marker-end", "url(#arrowhead)");
            
            parentGroup.appendChild(path);
        }
        
        // Make an element draggable
        function makeDraggable(element) {
            let selectedElement = null;
            let offset = { x: 0, y: 0 };
            
            element.addEventListener("mousedown", startDrag);
            element.addEventListener("mousemove", drag);
            element.addEventListener("mouseup", endDrag);
            element.addEventListener("mouseleave", endDrag);
            
            function startDrag(evt) {
                if (evt.target.classList.contains("person-box")) {
                    selectedElement = element;
                    
                    // Get current position from transform attribute
                    const transform = selectedElement.getAttribute("transform");
                    const translateMatch = /translate\(([^,]+),\s*([^)]+)\)/.exec(transform);
                    
                    const currentX = parseFloat(translateMatch[1]);
                    const currentY = parseFloat(translateMatch[2]);
                    
                    offset.x = evt.clientX - currentX;
                    offset.y = evt.clientY - currentY;
                    
                    // Bring to front
                    selectedElement.parentNode.appendChild(selectedElement);
                }
            }
            
            function drag(evt) {
                if (selectedElement) {
                    evt.preventDefault();
                    
                    const x = evt.clientX - offset.x;
                    const y = evt.clientY - offset.y;
                    
                    selectedElement.setAttribute("transform", `translate(${x}, ${y})`);
                    
                    // Update position in our data structure
                    const id = selectedElement.getAttribute("data-id");
                    if (people.has(id)) {
                        const person = people.get(id);
                        person.x = x;
                        person.y = y;
                        
                        // Update connections
                        updateConnections();
                    }
                }
            }
            
            function endDrag() {
                selectedElement = null;
            }
        }
        
        // Update connections after dragging
        function updateConnections() {
            // Get all connection paths
            const connectionPaths = document.querySelectorAll(".connection");
            
            // Update each connection
            connections.forEach((conn, i) => {
                if (i >= connectionPaths.length) return;
                
                const { fromId, toId } = conn;
                const fromPerson = people.get(fromId);
                const toPerson = people.get(toId);
                
                if (fromPerson && toPerson) {
                    const x1 = fromPerson.x + boxWidth/2;
                    const y1 = fromPerson.y + boxHeight;
                    const x2 = toPerson.x + boxWidth/2;
                    const y2 = toPerson.y;
                    
                    // Calculate control points for a curved line
                    const midY = y1 + (y2 - y1) / 2;
                    
                    // Path data for a smooth curve
                    const d = `M${x1},${y1} C${x1},${midY} ${x2},${midY} ${x2},${y2}`;
                    
                    connectionPaths[i].setAttribute("d", d);
                }
            });
        }
        
        // Show person details in the panel
        function showPersonDetails(person) {
            detailsName.textContent = person.name;
            detailsTitle.textContent = person.title;
            detailsInfo.textContent = person.info || "No additional information available.";
            
            // Show direct reports if any
            if (person.reports && person.reports.length > 0) {
                const reportsSection = document.createElement("div");
                
                const reportsTitle = document.createElement("h3");
                reportsTitle.textContent = "Direct Reports:";
                reportsSection.appendChild(reportsTitle);
                
                const reportsList = document.createElement("ul");
                person.reports.forEach(report => {
                    const listItem = document.createElement("li");
                    listItem.textContent = `${report.name}, ${report.title}`;
                    reportsList.appendChild(listItem);
                });
                
                reportsSection.appendChild(reportsList);
                detailsInfo.appendChild(reportsSection);
            }
            
            personDetails.style.display = "block";
        }
        
        // Initialize the chart
        drawChart();
    </script>
</body>
</html>

This comprehensive example demonstrates:

This type of interactive diagram would be much more difficult to implement with Canvas or static images, highlighting the power of inline SVG for interactive visualizations.

Optimizing SVG Performance

When working with complex or numerous SVG elements, performance can become a concern. Here are some optimization techniques:

flowchart TD A[SVG Performance Optimization] A --> B[Minimize DOM Size] A --> C[Reduce Reflows and Repaints] A --> D[Use CSS for Animations] A --> E[GPU Acceleration] A --> F[Grouping & Layering] B --> B1[Use symbols and <use>] B --> B2[Remove unnecessary elements] B --> B3[Simplify paths] C --> C1[Batch DOM operations] C --> C2[Use requestAnimationFrame] C --> C3[Optimize selectors] D --> D1[Use CSS transforms] D --> D2[Use CSS transitions] D --> D3[Use will-change property] E --> E1[Use transform: translate3d] E --> E2[Composite layers with isolation] F --> F1[Group related elements] F --> F2[Layer static and dynamic parts]

Specific Techniques:

// Example of symbol use for repeating elements
<svg>
    <defs>
        <symbol id="user-icon" viewBox="0 0 50 50">
            <circle cx="25" cy="15" r="10" fill="#e3f2fd" stroke="#1976d2" />
            <path d="M5,50 C5,35 45,35 45,50" fill="#e3f2fd" stroke="#1976d2" />
        </symbol>
    </defs>
    
    <!-- Use the symbol multiple times -->
    <use href="#user-icon" x="10" y="10" width="40" height="40" />
    <use href="#user-icon" x="60" y="10" width="40" height="40" />
    <use href="#user-icon" x="110" y="10" width="40" height="40" />
</svg>

// Example of batching SVG updates
function updateMultipleElements() {
    // Bad approach: triggers multiple reflows
    elements.forEach(el => {
        el.setAttribute('x', calculateNewX(el));
        el.setAttribute('y', calculateNewY(el));
    });
    
    // Better approach: use requestAnimationFrame and batch updates
    requestAnimationFrame(() => {
        elements.forEach(el => {
            // Batch attribute changes
            el.setAttribute('x', calculateNewX(el));
            el.setAttribute('y', calculateNewY(el));
        });
    });
}

// Example of using CSS transforms for animation
const element = document.querySelector('#animated-element');

// Inefficient way (triggers reflows)
function animateWithAttributes(progress) {
    element.setAttribute('x', 100 + progress * 200);
    requestAnimationFrame(() => animateWithAttributes(progress + 0.01));
}

// More efficient way
element.style.transition = 'transform 2s ease-in-out';
element.style.transform = 'translateX(200px)';
// Even better for performance
element.style.willChange = 'transform';
element.style.transform = 'translate3d(200px, 0, 0)';

SVG Manipulation Libraries

While the native SVG DOM API is powerful, several libraries can simplify complex operations:

Library Key Features Best For
SVG.js Lightweight, chainable API, animations, filters General-purpose SVG creation and manipulation
Snap.svg Created by Adobe, advanced SVG features, animations Complex SVG manipulation and animations
D3.js Data binding, complex visualizations, transitions Data visualization and interactive charts
GreenSock (GSAP) High-performance animations, timeline control Complex SVG animations and interactions
Vivus.js SVG line drawing animations, path control Path drawing and stroke animations
Two.js Resolution-agnostic, canvas/SVG/WebGL rendering Cross-renderer 2D graphics

These libraries can significantly simplify complex operations. Here are some examples:

SVG.js Example

// Create SVG drawing surface
const draw = SVG().addTo('#drawing').size(300, 300);

// Create and manipulate shapes
const rect = draw.rect(100, 100).fill('#f06').move(50, 50);
const circle = draw.circle(50).fill('#09c').move(200, 200);

// Animate elements
rect.animate(1000).move(100, 200).rotate(45);

// Event handling
circle.click(function() {
    this.fill({ color: '#f00' });
});

D3.js Example

// Create SVG container
const svg = d3.select('#chart')
    .append('svg')
    .attr('width', 500)
    .attr('height', 300);

// Create scales
const xScale = d3.scaleLinear()
    .domain([0, 100])
    .range([50, 450]);
    
const yScale = d3.scaleLinear()
    .domain([0, 100])
    .range([250, 50]);

// Bind data to circles
svg.selectAll('circle')
    .data(dataPoints)
    .enter()
    .append('circle')
    .attr('cx', d => xScale(d.x))
    .attr('cy', d => yScale(d.y))
    .attr('r', d => d.value)
    .attr('fill', d => d.category);

GSAP Animation Example

// Select SVG elements
const star = document.querySelector('#star');
const circle = document.querySelector('#circle');

// Create a timeline
const tl = gsap.timeline({repeat: -1, yoyo: true});

// Add animations to the timeline
tl.to(star, {
    duration: 1,
    rotation: 360,
    scale: 1.5,
    x: 100,
    ease: "power2.inOut"
})
.to(circle, {
    duration: 0.5,
    fill: "#ff5722",
    y: -50,
    ease: "bounce.out"
}, "-=0.5");

Accessibility in Interactive SVG

As SVG becomes more interactive, accessibility becomes increasingly important:

<svg role="img" aria-labelledby="chart-title chart-desc">
    <title id="chart-title">Quarterly Sales 2025</title>
    <desc id="chart-desc">Bar chart showing sales by quarter for 2025, with Q3 showing the highest growth.</desc>
    
    <!-- Interactive element with ARIA attributes -->
    <g tabindex="0" 
       role="button" 
       aria-label="Q1 Sales: $250,000" 
       onkeydown="if(event.key === 'Enter') showDetails('q1');"
       onclick="showDetails('q1');">
        <rect class="bar" x="50" y="150" width="50" height="100" />
        <text x="75" y="265" text-anchor="middle">Q1</text>
    </g>
</svg>

Key accessibility techniques for interactive SVG include:

Practice Activities

Basic Exercise: Interactive Icon System

Create a set of 4-6 interactive icons using inline SVG. Each icon should change appearance on hover and click. Use a combination of CSS and JavaScript to handle the interactions. Add proper accessibility attributes.

Intermediate Exercise: SVG Data Chart

Build a simple bar chart or pie chart using inline SVG and JavaScript. Populate the chart with sample data and add tooltip functionality to display values when users hover over chart elements. Include a feature to update the chart with new data values.

Advanced Exercise: Interactive Diagram

Create an interactive diagram (e.g., a simple floor plan, network diagram, or process flow) where users can click on different elements to show more information. Include the ability to drag and reposition elements, and ensure the diagram maintains proper connections between elements when they are moved.

Challenge Project: Data Visualization Dashboard

Build a small dashboard with multiple interactive SVG visualizations that display different aspects of a data set. Include features like filtering, sorting, and highlighting connections between related data points across different visualizations. Use a combination of CSS transitions and JavaScript manipulation for smooth interactions.

Exploration Activity: SVG Animation

Research and implement advanced SVG animation techniques using either native CSS, SMIL animations, or a library like GSAP. Create a sequence of animations that tell a story or demonstrate a process. Document your approach and the benefits/limitations of your chosen method.

Additional Resources

Conclusion

Inline SVG offers a powerful combination of visual precision and programmatic control that makes it ideal for creating interactive, responsive, and accessible graphics on the web. By embedding SVG directly in the HTML document, we gain direct access to its elements through the DOM, enabling rich interactions and dynamic updates.

We've explored how to:

These techniques unlock a world of possibilities for creating engaging web graphics, from simple interactive icons to complex data visualizations and applications. The combination of vector graphics with the interactivity afforded by inline SVG makes it an essential tool in the modern web developer's toolkit.

As you continue to work with SVG, remember that its true power lies in the seamless integration with HTML, CSS, and JavaScript. This integration allows you to create graphics that aren't just visually appealing but also responsive, interactive, and meaningful parts of your web applications.