494 lines
19 KiB
HTML
494 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>D3 Visualization 1: Interactive Technology Adoption Dashboard</title>
|
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
padding: 20px;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
#visualization-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
padding: 40px;
|
|
}
|
|
|
|
h1 {
|
|
color: #2c3e50;
|
|
margin-bottom: 10px;
|
|
font-size: 2.5em;
|
|
text-align: center;
|
|
}
|
|
|
|
.subtitle {
|
|
text-align: center;
|
|
color: #7f8c8d;
|
|
margin-bottom: 30px;
|
|
font-size: 1.1em;
|
|
}
|
|
|
|
#controls {
|
|
margin-bottom: 30px;
|
|
padding: 20px;
|
|
background: #f8f9fa;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
gap: 15px;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
|
|
.control-btn {
|
|
padding: 12px 24px;
|
|
border: 2px solid #667eea;
|
|
background: white;
|
|
color: #667eea;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 1em;
|
|
font-weight: 600;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.control-btn:hover {
|
|
background: #667eea;
|
|
color: white;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
|
}
|
|
|
|
.control-btn.active {
|
|
background: #667eea;
|
|
color: white;
|
|
}
|
|
|
|
#viz {
|
|
margin: 40px 0;
|
|
overflow: visible;
|
|
}
|
|
|
|
.bar {
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.bar:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.bar-label {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
fill: #2c3e50;
|
|
}
|
|
|
|
.value-label {
|
|
font-size: 12px;
|
|
fill: white;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.axis-label {
|
|
font-size: 14px;
|
|
fill: #7f8c8d;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.axis path,
|
|
.axis line {
|
|
stroke: #bdc3c7;
|
|
}
|
|
|
|
.axis text {
|
|
fill: #2c3e50;
|
|
font-size: 12px;
|
|
}
|
|
|
|
#insights {
|
|
padding: 25px;
|
|
background: #ecf0f1;
|
|
border-radius: 8px;
|
|
border-left: 5px solid #667eea;
|
|
}
|
|
|
|
#insights h3 {
|
|
color: #2c3e50;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.legend {
|
|
display: flex;
|
|
gap: 20px;
|
|
margin-top: 15px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.legend-color {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
#selected-info {
|
|
margin-top: 15px;
|
|
padding: 15px;
|
|
background: white;
|
|
border-radius: 6px;
|
|
min-height: 60px;
|
|
}
|
|
|
|
footer {
|
|
margin-top: 40px;
|
|
padding: 30px;
|
|
background: #2c3e50;
|
|
color: white;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
footer h3 {
|
|
color: #3498db;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
footer ul {
|
|
list-style-position: inside;
|
|
line-height: 1.8;
|
|
}
|
|
|
|
footer li {
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
footer strong {
|
|
color: #3498db;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="visualization-container">
|
|
<h1>Technology Adoption Dashboard</h1>
|
|
<p class="subtitle">Interactive visualization demonstrating D3 selection patterns and data binding</p>
|
|
|
|
<div id="controls">
|
|
<button class="control-btn active" data-sort="name">Sort by Name</button>
|
|
<button class="control-btn" data-sort="adoption">Sort by Adoption</button>
|
|
<button class="control-btn" data-sort="growth">Sort by Growth</button>
|
|
<button class="control-btn" data-action="randomize">Randomize Data</button>
|
|
<button class="control-btn" data-action="reset">Reset</button>
|
|
</div>
|
|
|
|
<div id="viz"></div>
|
|
|
|
<div id="insights">
|
|
<h3>Visualization Insights</h3>
|
|
<p>Click on any bar to see detailed information. Hover for interactive effects.</p>
|
|
<div class="legend">
|
|
<div class="legend-item">
|
|
<div class="legend-color" style="background: #3498db;"></div>
|
|
<span>High Adoption (>70%)</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-color" style="background: #2ecc71;"></div>
|
|
<span>Medium Adoption (40-70%)</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-color" style="background: #f39c12;"></div>
|
|
<span>Growing (<40%)</span>
|
|
</div>
|
|
</div>
|
|
<div id="selected-info">
|
|
<em style="color: #7f8c8d;">Select a technology to see details...</em>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Embedded realistic data - Technology adoption rates
|
|
const originalData = [
|
|
{ name: "React", adoption: 78, growth: 12, category: "Frontend", users: "12.5M" },
|
|
{ name: "Vue.js", adoption: 52, growth: 18, category: "Frontend", users: "4.2M" },
|
|
{ name: "Angular", adoption: 45, growth: -3, category: "Frontend", users: "3.8M" },
|
|
{ name: "Svelte", adoption: 22, growth: 35, category: "Frontend", users: "850K" },
|
|
{ name: "Node.js", adoption: 85, growth: 8, category: "Backend", users: "18.2M" },
|
|
{ name: "Python", adoption: 82, growth: 15, category: "Backend", users: "16.7M" },
|
|
{ name: "Rust", adoption: 18, growth: 42, category: "Backend", users: "2.1M" },
|
|
{ name: "Go", adoption: 38, growth: 22, category: "Backend", users: "3.5M" },
|
|
{ name: "TypeScript", adoption: 72, growth: 28, category: "Language", users: "10.8M" },
|
|
{ name: "GraphQL", adoption: 35, growth: 25, category: "API", users: "2.9M" },
|
|
{ name: "Docker", adoption: 88, growth: 10, category: "DevOps", users: "22.4M" },
|
|
{ name: "Kubernetes", adoption: 58, growth: 30, category: "DevOps", users: "6.8M" },
|
|
{ name: "PostgreSQL", adoption: 76, growth: 14, category: "Database", users: "14.3M" },
|
|
{ name: "MongoDB", adoption: 65, growth: 8, category: "Database", users: "9.2M" },
|
|
{ name: "Redis", adoption: 55, growth: 12, category: "Database", users: "6.5M" },
|
|
{ name: "TailwindCSS", adoption: 48, growth: 45, category: "CSS", users: "4.5M" },
|
|
{ name: "Jest", adoption: 62, growth: 5, category: "Testing", users: "7.8M" },
|
|
{ name: "Playwright", adoption: 28, growth: 55, category: "Testing", users: "1.8M" },
|
|
{ name: "Vite", adoption: 42, growth: 62, category: "Build Tool", users: "3.6M" },
|
|
{ name: "Next.js", adoption: 68, growth: 38, category: "Framework", users: "8.9M" }
|
|
];
|
|
|
|
// Clone data for manipulation
|
|
let data = JSON.parse(JSON.stringify(originalData));
|
|
|
|
// Dimensions and margins
|
|
const margin = { top: 20, right: 30, bottom: 60, left: 160 };
|
|
const width = 1100 - margin.left - margin.right;
|
|
const height = 800 - margin.top - margin.bottom;
|
|
|
|
// TECHNIQUE 1: select() - Select the viz container and append SVG
|
|
// Using d3.select() to query a single element
|
|
const svg = d3.select("#viz")
|
|
.append("svg")
|
|
.attr("width", width + margin.left + margin.right)
|
|
.attr("height", height + margin.top + margin.bottom)
|
|
.append("g")
|
|
.attr("transform", `translate(${margin.left},${margin.top})`);
|
|
|
|
// Create scales
|
|
const xScale = d3.scaleLinear()
|
|
.domain([0, 100])
|
|
.range([0, width]);
|
|
|
|
const yScale = d3.scaleBand()
|
|
.domain(data.map(d => d.name))
|
|
.range([0, height])
|
|
.padding(0.2);
|
|
|
|
// Color scale based on adoption rate
|
|
const colorScale = (adoption) => {
|
|
if (adoption >= 70) return "#3498db";
|
|
if (adoption >= 40) return "#2ecc71";
|
|
return "#f39c12";
|
|
};
|
|
|
|
// Add axes
|
|
const xAxis = d3.axisBottom(xScale)
|
|
.ticks(10)
|
|
.tickFormat(d => d + "%");
|
|
|
|
const yAxis = d3.axisLeft(yScale);
|
|
|
|
svg.append("g")
|
|
.attr("class", "axis x-axis")
|
|
.attr("transform", `translate(0,${height})`)
|
|
.call(xAxis)
|
|
.append("text")
|
|
.attr("class", "axis-label")
|
|
.attr("x", width / 2)
|
|
.attr("y", 40)
|
|
.attr("fill", "#2c3e50")
|
|
.text("Adoption Rate (%)");
|
|
|
|
svg.append("g")
|
|
.attr("class", "axis y-axis")
|
|
.call(yAxis);
|
|
|
|
// TECHNIQUE 2: selectAll() with data() and join() - The core data binding pattern
|
|
// This demonstrates the enter/update/exit pattern with join()
|
|
function updateChart(animationDuration = 750) {
|
|
// Update yScale domain with new data order
|
|
yScale.domain(data.map(d => d.name));
|
|
|
|
// TECHNIQUE 3: Data join with enter/update/exit using join()
|
|
// selectAll() selects all bars (even if they don't exist yet)
|
|
// data() binds our dataset
|
|
// join() handles enter/update/exit automatically
|
|
const bars = svg.selectAll(".bar")
|
|
.data(data, d => d.name) // Key function ensures object constancy
|
|
.join(
|
|
// Enter: new elements
|
|
enter => enter.append("rect")
|
|
.attr("class", "bar")
|
|
.attr("x", 0)
|
|
.attr("y", d => yScale(d.name))
|
|
.attr("width", 0)
|
|
.attr("height", yScale.bandwidth())
|
|
.attr("fill", d => colorScale(d.adoption))
|
|
.attr("rx", 4),
|
|
// Update: existing elements
|
|
update => update,
|
|
// Exit: removed elements
|
|
exit => exit.transition()
|
|
.duration(animationDuration)
|
|
.attr("width", 0)
|
|
.remove()
|
|
);
|
|
|
|
// TECHNIQUE 4: Method chaining with attr() and style()
|
|
// Demonstrates fluent API pattern for DOM manipulation
|
|
bars.transition()
|
|
.duration(animationDuration)
|
|
.attr("y", d => yScale(d.name))
|
|
.attr("width", d => xScale(d.adoption))
|
|
.attr("height", yScale.bandwidth())
|
|
.attr("fill", d => colorScale(d.adoption));
|
|
|
|
// TECHNIQUE 5: Event handling with on()
|
|
// Adding interactive behavior to selections
|
|
bars.on("click", function(event, d) {
|
|
// 'this' refers to the DOM element
|
|
// Remove previous selection highlighting
|
|
d3.selectAll(".bar").attr("stroke", "none").attr("stroke-width", 0);
|
|
|
|
// Highlight selected bar using select() on 'this'
|
|
d3.select(this)
|
|
.attr("stroke", "#2c3e50")
|
|
.attr("stroke-width", 3);
|
|
|
|
// Update info panel using select() and html()
|
|
d3.select("#selected-info")
|
|
.html(`
|
|
<strong style="font-size: 1.2em; color: #2c3e50;">${d.name}</strong><br>
|
|
<strong>Category:</strong> ${d.category} |
|
|
<strong>Adoption:</strong> ${d.adoption}% |
|
|
<strong>Growth:</strong> ${d.growth > 0 ? '+' : ''}${d.growth}% |
|
|
<strong>Users:</strong> ${d.users}
|
|
`);
|
|
})
|
|
.on("mouseenter", function(event, d) {
|
|
// Demonstrate style() method for CSS manipulation
|
|
d3.select(this)
|
|
.style("filter", "brightness(1.1)")
|
|
.style("transform", "translateX(5px)");
|
|
})
|
|
.on("mouseleave", function(event, d) {
|
|
d3.select(this)
|
|
.style("filter", "brightness(1)")
|
|
.style("transform", "translateX(0)");
|
|
});
|
|
|
|
// Update value labels using selectAll(), data(), and join()
|
|
const labels = svg.selectAll(".value-label")
|
|
.data(data, d => d.name)
|
|
.join(
|
|
enter => enter.append("text")
|
|
.attr("class", "value-label")
|
|
.attr("x", 0)
|
|
.attr("y", d => yScale(d.name) + yScale.bandwidth() / 2)
|
|
.attr("dy", "0.35em")
|
|
.attr("dx", "8px")
|
|
.text(d => d.adoption + "%"),
|
|
update => update,
|
|
exit => exit.remove()
|
|
);
|
|
|
|
// Chain multiple attr() calls for positioning
|
|
labels.transition()
|
|
.duration(animationDuration)
|
|
.attr("x", d => xScale(d.adoption))
|
|
.attr("y", d => yScale(d.name) + yScale.bandwidth() / 2)
|
|
.tween("text", function(d) {
|
|
// Animate the number
|
|
const i = d3.interpolate(+this.textContent.replace('%', '') || 0, d.adoption);
|
|
return function(t) {
|
|
this.textContent = Math.round(i(t)) + "%";
|
|
};
|
|
});
|
|
|
|
// Update Y axis with transition
|
|
svg.select(".y-axis")
|
|
.transition()
|
|
.duration(animationDuration)
|
|
.call(yAxis);
|
|
}
|
|
|
|
// Initial chart render
|
|
updateChart(1000);
|
|
|
|
// TECHNIQUE 6: selectAll() for control buttons with on() for events
|
|
// Demonstrates selecting multiple elements and attaching event handlers
|
|
d3.selectAll(".control-btn").on("click", function(event) {
|
|
const btn = d3.select(this); // select() on 'this' DOM element
|
|
const sortBy = btn.attr("data-sort"); // attr() to read attribute
|
|
const action = btn.attr("data-action");
|
|
|
|
if (sortBy) {
|
|
// Remove active class from all buttons, add to clicked
|
|
d3.selectAll(".control-btn").classed("active", false);
|
|
btn.classed("active", true);
|
|
|
|
// Sort data based on selection
|
|
switch(sortBy) {
|
|
case "name":
|
|
data.sort((a, b) => a.name.localeCompare(b.name));
|
|
break;
|
|
case "adoption":
|
|
data.sort((a, b) => b.adoption - a.adoption);
|
|
break;
|
|
case "growth":
|
|
data.sort((a, b) => b.growth - a.growth);
|
|
break;
|
|
}
|
|
updateChart();
|
|
}
|
|
|
|
if (action === "randomize") {
|
|
// Randomize adoption values
|
|
data.forEach(d => {
|
|
d.adoption = Math.floor(Math.random() * 80) + 20;
|
|
d.growth = Math.floor(Math.random() * 60) - 10;
|
|
});
|
|
updateChart();
|
|
}
|
|
|
|
if (action === "reset") {
|
|
// Reset to original data
|
|
data = JSON.parse(JSON.stringify(originalData));
|
|
data.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
// Reset selection info using select() and html()
|
|
d3.select("#selected-info")
|
|
.html('<em style="color: #7f8c8d;">Select a technology to see details...</em>');
|
|
|
|
// Remove highlighting using selectAll() and attr()
|
|
d3.selectAll(".bar").attr("stroke", "none").attr("stroke-width", 0);
|
|
|
|
updateChart();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<footer>
|
|
<h3>Iteration 1 - Web Learning Applied</h3>
|
|
<ul>
|
|
<li><strong>Web Source:</strong> https://d3js.org/d3-selection</li>
|
|
<li><strong>Topic:</strong> D3 Selections API - Core DOM manipulation patterns</li>
|
|
<li><strong>Techniques Learned & Applied:</strong>
|
|
<ul style="margin-left: 20px; margin-top: 8px;">
|
|
<li><strong>select() & selectAll():</strong> Used throughout for querying single (#viz) and multiple (.bar, .control-btn) elements</li>
|
|
<li><strong>data() & join():</strong> Implemented modern enter/update/exit pattern for dynamic bar chart updates with object constancy</li>
|
|
<li><strong>attr(), style(), html(), text():</strong> Chained DOM manipulation methods for setting SVG attributes, CSS styles, and content</li>
|
|
<li><strong>on() event handling:</strong> Interactive click, mouseenter, mouseleave events with 'this' context and d3.select(this) pattern</li>
|
|
<li><strong>Method chaining:</strong> Fluent API usage throughout for concise, readable code</li>
|
|
<li><strong>classed() method:</strong> Toggle CSS classes on button selections for active states</li>
|
|
</ul>
|
|
</li>
|
|
<li><strong>What This Demonstrates:</strong> An interactive horizontal bar chart showing technology adoption rates. Users can click bars to see details, sort by different criteria, randomize data, and reset. The visualization showcases all core D3 selection patterns: querying elements (select/selectAll), binding data (data/join), manipulating DOM (attr/style/html/text), and handling events (on). The enter/update/exit pattern via join() ensures smooth transitions when data changes, demonstrating D3's power for data-driven document transformation.</li>
|
|
<li><strong>Data:</strong> 20 real-world technologies with adoption rates, growth percentages, categories, and user counts</li>
|
|
<li><strong>Interactive Features:</strong> Click bars for details, hover for effects, sort controls, data randomization, full reset capability</li>
|
|
</ul>
|
|
</footer>
|
|
</body>
|
|
</html>
|