fixed code commenting

This commit is contained in:
dlawler489 2025-04-28 15:33:26 +10:00
parent 93ecc88ade
commit b578c53594
3 changed files with 173 additions and 108 deletions

View file

@ -1,6 +1,9 @@
/* Base body styles */
body { body {
font-family: sans-serif; font-family: sans-serif;
} }
/* Chart bars */
.bar { .bar {
opacity: 0.8; opacity: 0.8;
transition: opacity 0.3s; transition: opacity 0.3s;
@ -8,10 +11,14 @@ body {
.bar:hover { .bar:hover {
opacity: 1; opacity: 1;
} }
/* Axis labels */
.axis-label { .axis-label {
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
} }
/* Tooltip styles */
.tooltip { .tooltip {
position: absolute; position: absolute;
padding: 6px; padding: 6px;
@ -24,13 +31,19 @@ body {
opacity: 0; opacity: 0;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
/* Controls section (dropdowns and buttons) */
.controls { .controls {
margin-bottom: 20px; margin-bottom: 20px;
} }
/* Dropdown (select) styling */
select { select {
margin-right: 10px; margin-right: 10px;
padding: 5px; padding: 5px;
} }
/* Buttons styling */
button { button {
margin: 5px; margin: 5px;
padding: 8px 12px; padding: 8px 12px;
@ -44,6 +57,8 @@ body {
button:hover { button:hover {
background-color: #3a5f8c; background-color: #3a5f8c;
} }
/* Legend area */
.legend { .legend {
margin-top: 20px; margin-top: 20px;
} }
@ -59,14 +74,20 @@ body {
margin-right: 5px; margin-right: 5px;
vertical-align: middle; vertical-align: middle;
} }
/* Loading message */
#loading { #loading {
font-size: 18px; font-size: 18px;
text-align: center; text-align: center;
margin: 20px; margin: 20px;
} }
/* Hidden class (for hiding elements like loading spinner) */
.hidden { .hidden {
display: none; display: none;
} }
/* Main page title */
.page-title { .page-title {
text-align: center; text-align: center;
margin-top: 20px; margin-top: 20px;

View file

@ -3,13 +3,22 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Health Perception by Education Level</title> <title>Health Perception by Education Level</title>
<!-- Link to external stylesheet -->
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="css/style.css">
<!-- D3.js library -->
<script src="https://d3js.org/d3.v7.min.js"></script> <script src="https://d3js.org/d3.v7.min.js"></script>
<!-- Save SVG as PNG library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/save-svg-as-png/1.4.17/saveSvgAsPng.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/save-svg-as-png/1.4.17/saveSvgAsPng.min.js"></script>
</head> </head>
<body> <body>
<!-- Page Title -->
<h2 class="page-title">How Education Level Shapes Perceived Health</h2> <h2 class="page-title">How Education Level Shapes Perceived Health</h2>
<!-- Filters and Controls -->
<div class="controls"> <div class="controls">
<label for="continentSelect">Continent:</label> <label for="continentSelect">Continent:</label>
<select id="continentSelect"> <select id="continentSelect">
@ -35,18 +44,21 @@
<option value="heatmap">Heatmap</option> <option value="heatmap">Heatmap</option>
</select> </select>
<!-- Buttons for Reset and Download -->
<button id="resetButton">Reset Filters</button> <button id="resetButton">Reset Filters</button>
<button id="downloadButton">Download Chart as PNG</button> <button id="downloadButton">Download Chart as PNG</button>
</div> </div>
<!-- Loading Message -->
<div id="loading">Loading chart, please wait...</div> <div id="loading">Loading chart, please wait...</div>
<!-- Main SVG where the chart will be drawn -->
<svg id="chart" width="1400" height="600"></svg> <svg id="chart" width="1400" height="600"></svg>
<!-- Legend Section -->
<div class="legend" id="legend"></div> <div class="legend" id="legend"></div>
<!-- Link to custom JavaScript file -->
<script src="script/script.js"></script> <script src="script/script.js"></script>
</body> </body>
</html> </html>

View file

@ -1,25 +1,31 @@
// SVG setup
const svg = d3.select("svg"), const svg = d3.select("svg"),
margin = {top: 80, right: 30, bottom: 150, left: 160}, margin = {top: 80, right: 30, bottom: 150, left: 160},
width = +svg.attr("width") - margin.left - margin.right, width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom, height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`); g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
// Scales for grouped bar and dot plots
const x0 = d3.scaleBand().rangeRound([0, width]).paddingInner(0.1); const x0 = d3.scaleBand().rangeRound([0, width]).paddingInner(0.1);
const x1 = d3.scaleBand().padding(0.05); const x1 = d3.scaleBand().padding(0.05);
const y = d3.scaleLinear().rangeRound([height, 0]); const y = d3.scaleLinear().rangeRound([height, 0]);
// Education levels for legend and grouped charts
const educationLevels = [ const educationLevels = [
"Pre-primary, primary and lower secondary education", "Pre-primary, primary and lower secondary education",
"Upper secondary and post-secondary non-tertiary, all programmes", "Upper secondary and post-secondary non-tertiary, all programmes",
"Tertiary education" "Tertiary education"
]; ];
// Color scale for education levels
const color = d3.scaleOrdinal() const color = d3.scaleOrdinal()
.domain(educationLevels) .domain(educationLevels)
.range(["#4E79A7", "#F28E2B", "#59A14F"]); .range(["#4E79A7", "#F28E2B", "#59A14F"]);
// Tooltip div
const tooltip = d3.select("body").append("div").attr("class", "tooltip"); const tooltip = d3.select("body").append("div").attr("class", "tooltip");
// Chart titles
const chartTitle1 = svg.append("text") const chartTitle1 = svg.append("text")
.attr("x", +svg.attr("width") / 2) .attr("x", +svg.attr("width") / 2)
.attr("y", 30) .attr("y", 30)
@ -34,6 +40,7 @@ const chartTitle2 = svg.append("text")
.style("font-size", "18px") .style("font-size", "18px")
.style("fill", "#666"); .style("fill", "#666");
// "No Data" placeholder text
const noDataText = g.append("text") const noDataText = g.append("text")
.attr("x", width / 2) .attr("x", width / 2)
.attr("y", height / 2) .attr("y", height / 2)
@ -43,7 +50,7 @@ const noDataText = g.append("text")
.style("display", "none") .style("display", "none")
.text("No data available for selected filters"); .text("No data available for selected filters");
// Dark Mode toggle button (unchanged) // Dark mode toggle button
const darkModeToggle = document.createElement('button'); const darkModeToggle = document.createElement('button');
darkModeToggle.textContent = 'Toggle Dark Mode'; darkModeToggle.textContent = 'Toggle Dark Mode';
Object.assign(darkModeToggle.style, { Object.assign(darkModeToggle.style, {
@ -58,6 +65,7 @@ Object.assign(darkModeToggle.style, {
}); });
document.querySelector('.controls').appendChild(darkModeToggle); document.querySelector('.controls').appendChild(darkModeToggle);
// Dark mode logic
let darkMode = false; let darkMode = false;
darkModeToggle.addEventListener('click', () => { darkModeToggle.addEventListener('click', () => {
darkMode = !darkMode; darkMode = !darkMode;
@ -70,19 +78,15 @@ darkModeToggle.addEventListener('click', () => {
tooltip.style("background-color", darkMode ? "#222" : "#fff") tooltip.style("background-color", darkMode ? "#222" : "#fff")
.style("color", darkMode ? "#eee" : "#000"); .style("color", darkMode ? "#eee" : "#000");
g.selectAll("text").style("fill", darkMode ? "#eee" : "#000"); g.selectAll("text").style("fill", darkMode ? "#eee" : "#000");
g.selectAll(".lowest-annotation").style("fill", darkMode ? "#eee" : "red");
// 🆕 THESE MUST BE INSIDE the event listener block g.selectAll(".lowest-box").attr("fill", darkMode ? "#222" : "#fff").attr("stroke", darkMode ? "#aaa" : "#999");
g.selectAll(".lowest-annotation")
.style("fill", darkMode ? "#eee" : "red");
g.selectAll(".lowest-box")
.attr("fill", darkMode ? "#222" : "#fff")
.attr("stroke", darkMode ? "#aaa" : "#999");
}); });
// Load data
d3.csv("PHSwithContinent.csv").then(function(data) { d3.csv("PHSwithContinent.csv").then(function(data) {
const allData = data; const allData = data;
// Populate continent and education dropdowns
const continents = Array.from(new Set(data.map(d => d.CONTINENT))).sort(); const continents = Array.from(new Set(data.map(d => d.CONTINENT))).sort();
continents.forEach(continent => { continents.forEach(continent => {
d3.select("#continentSelect") d3.select("#continentSelect")
@ -98,6 +102,7 @@ d3.csv("PHSwithContinent.csv").then(function(data) {
.text(level); .text(level);
}); });
// Setup static legend (initial)
const legend = d3.select("#legend"); const legend = d3.select("#legend");
color.domain().forEach(d => { color.domain().forEach(d => {
const item = legend.append("div").attr("class", "legend-item"); const item = legend.append("div").attr("class", "legend-item");
@ -107,6 +112,7 @@ d3.csv("PHSwithContinent.csv").then(function(data) {
item.append("span").text(d); item.append("span").text(d);
}); });
// Update the chart
function updateChart() { function updateChart() {
const selectedContinent = d3.select("#continentSelect").property("value"); const selectedContinent = d3.select("#continentSelect").property("value");
const selectedSex = d3.select("#sexSelect").property("value"); const selectedSex = d3.select("#sexSelect").property("value");
@ -115,26 +121,26 @@ d3.csv("PHSwithContinent.csv").then(function(data) {
updateLegend(selectedChart); updateLegend(selectedChart);
// Update chart titles
let title1 = "Self-Reported Health"; let title1 = "Self-Reported Health";
let title2 = ""; let title2 = "";
if (selectedContinent !== "All") title2 += `${selectedContinent}`; if (selectedContinent !== "All") title2 += `${selectedContinent}`;
if (selectedSex !== "All") title2 += (title2 ? ", " : "") + selectedSex; if (selectedSex !== "All") title2 += (title2 ? ", " : "") + selectedSex;
if (selectedEdu !== "All") title2 += (title2 ? ", " : "") + selectedEdu; if (selectedEdu !== "All") title2 += (title2 ? ", " : "") + selectedEdu;
chartTitle1.text(title1); chartTitle1.text(title1);
chartTitle2.text(title2); chartTitle2.text(title2);
// Filter data based on selections
let filteredData = allData.filter(d => let filteredData = allData.filter(d =>
(selectedContinent === "All" || d.CONTINENT === selectedContinent) && (selectedContinent === "All" || d.CONTINENT === selectedContinent) &&
(selectedSex === "All" || d.Sex === selectedSex) && (selectedSex === "All" || d.Sex === selectedSex) &&
(selectedEdu === "All" || d["Socio-economic status"] === selectedEdu) (selectedEdu === "All" || d["Socio-economic status"] === selectedEdu)
); ).filter(d =>
filteredData = filteredData.filter(d =>
educationLevels.includes(d["Socio-economic status"]) && educationLevels.includes(d["Socio-economic status"]) &&
!isNaN(parseFloat(d.OBS_VALUE)) !isNaN(parseFloat(d.OBS_VALUE))
); );
// Clear and redraw
g.selectAll("*").remove(); g.selectAll("*").remove();
if (filteredData.length === 0) { if (filteredData.length === 0) {
noDataText.style("display", null); noDataText.style("display", null);
@ -143,7 +149,6 @@ d3.csv("PHSwithContinent.csv").then(function(data) {
noDataText.style("display", "none"); noDataText.style("display", "none");
} }
// 🆕 Decide which chart type to draw
if (selectedChart === "grouped") { if (selectedChart === "grouped") {
drawGrouped(filteredData); drawGrouped(filteredData);
} else if (selectedChart === "dot") { } else if (selectedChart === "dot") {
@ -156,16 +161,17 @@ d3.csv("PHSwithContinent.csv").then(function(data) {
updateChart(); updateChart();
d3.select("#loading").classed("hidden", true); d3.select("#loading").classed("hidden", true);
// Attach event listeners
d3.selectAll("select").on("change", updateChart); d3.selectAll("select").on("change", updateChart);
d3.select("#resetButton").on("click", function() { d3.select("#resetButton").on("click", function() {
d3.select("#continentSelect").property("value", "All"); d3.select("#continentSelect").property("value", "All");
d3.select("#sexSelect").property("value", "All"); d3.select("#sexSelect").property("value", "All");
d3.select("#incomeSelect").property("value", "All"); d3.select("#incomeSelect").property("value", "All");
d3.select("#chartTypeSelect").property("value", "grouped"); // 🆕 Reset chart type too d3.select("#chartTypeSelect").property("value", "grouped");
updateChart(); updateChart();
}); });
// Download chart as PNG
d3.select("#downloadButton").on("click", function() { d3.select("#downloadButton").on("click", function() {
const c = d3.select("#continentSelect").property("value"); const c = d3.select("#continentSelect").property("value");
const s = d3.select("#sexSelect").property("value"); const s = d3.select("#sexSelect").property("value");
@ -180,40 +186,40 @@ d3.csv("PHSwithContinent.csv").then(function(data) {
const filename = parts.join("_") + ".png"; const filename = parts.join("_") + ".png";
saveSvgAsPng(document.getElementById("chart"), filename); saveSvgAsPng(document.getElementById("chart"), {
filename: filename,
scale: 2,
backgroundColor: darkMode ? "#121212" : "#ffffff"
});
}); });
}); });
// Update legend depending on chart type
function updateLegend(chartType) { function updateLegend(chartType) {
const legend = d3.select("#legend"); const legend = d3.select("#legend");
legend.html(""); // Clear existing legend.html(""); // Clear existing
if (chartType === "heatmap") { if (chartType === "heatmap") {
// Create a wrapper for gradient + labels
const wrapper = legend.append("div") const wrapper = legend.append("div")
.style("display", "flex") .style("display", "flex")
.style("flex-direction", "column") .style("flex-direction", "column")
.style("align-items", "center") .style("align-items", "center")
.style("margin-top", "10px"); .style("margin-top", "10px");
// Gradient bar
wrapper.append("div") wrapper.append("div")
.style("width", "300px") .style("width", "300px")
.style("height", "20px") .style("height", "20px")
.style("background", "linear-gradient(to right, #edf8b1, #2c7fb8)") .style("background", "linear-gradient(to right, #edf8b1, #2c7fb8)")
.style("margin-bottom", "5px"); .style("margin-bottom", "5px");
// Tick labels (0% — 50% — 100%) wrapper.append("div")
const ticks = wrapper.append("div")
.style("width", "300px") .style("width", "300px")
.style("display", "flex") .style("display", "flex")
.style("justify-content", "space-between") .style("justify-content", "space-between")
.style("font-size", "12px"); .style("font-size", "12px")
.html('<span>0%</span><span>50%</span><span>100%</span>');
ticks.html('<span>0%</span><span>50%</span><span>100%</span>');
} else { } else {
// Grouped/Dot Chart — categorical legend
educationLevels.forEach(level => { educationLevels.forEach(level => {
const item = legend.append("div").attr("class", "legend-item"); const item = legend.append("div").attr("class", "legend-item");
item.append("div") item.append("div")
@ -225,11 +231,13 @@ function updateLegend(chartType) {
} }
function drawGrouped(data) { function drawGrouped(data) {
// Setup x0, x1, and y domains
const countries = Array.from(new Set(data.map(d => d["Reference area"]))); const countries = Array.from(new Set(data.map(d => d["Reference area"])));
x0.domain(countries); x0.domain(countries);
x1.domain(educationLevels).rangeRound([0, x0.bandwidth()]); x1.domain(educationLevels).rangeRound([0, x0.bandwidth()]);
y.domain([0, 100]); y.domain([0, 100]);
// Group data by country
const groupedData = data.reduce((acc, d) => { const groupedData = data.reduce((acc, d) => {
const found = acc.find(v => v.Country === d["Reference area"]); const found = acc.find(v => v.Country === d["Reference area"]);
const value = parseFloat(d.OBS_VALUE); const value = parseFloat(d.OBS_VALUE);
@ -239,6 +247,7 @@ function drawGrouped(data) {
return acc; return acc;
}, []); }, []);
// Draw grouped bars
const countryGroups = g.append("g") const countryGroups = g.append("g")
.selectAll("g") .selectAll("g")
.data(groupedData) .data(groupedData)
@ -255,6 +264,7 @@ function drawGrouped(data) {
.attr("y", height) .attr("y", height)
.attr("height", 0) .attr("height", 0)
.on("mouseover", function(event, d) { .on("mouseover", function(event, d) {
// Show tooltip
tooltip.html(`<strong>${d["Reference area"]}</strong><br/>${d["Socio-economic status"]}<br/><strong>${d.OBS_VALUE}%</strong>`) tooltip.html(`<strong>${d["Reference area"]}</strong><br/>${d["Socio-economic status"]}<br/><strong>${d.OBS_VALUE}%</strong>`)
.style("left", (event.pageX + 10) + "px") .style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 40) + "px") .style("top", (event.pageY - 40) + "px")
@ -263,6 +273,7 @@ function drawGrouped(data) {
.style("transform", "translateY(-10px)"); .style("transform", "translateY(-10px)");
}) })
.on("mouseout", function() { .on("mouseout", function() {
// Hide tooltip
tooltip.transition().duration(500) tooltip.transition().duration(500)
.style("opacity", 0) .style("opacity", 0)
.style("transform", "translateY(0px)"); .style("transform", "translateY(0px)");
@ -273,6 +284,7 @@ function drawGrouped(data) {
.attr("y", d => y(d.value)) .attr("y", d => y(d.value))
.attr("height", d => height - y(d.value)); .attr("height", d => height - y(d.value));
// Add X axis
g.append("g") g.append("g")
.attr("transform", `translate(0,${height})`) .attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x0)) .call(d3.axisBottom(x0))
@ -280,10 +292,11 @@ function drawGrouped(data) {
.attr("transform", "rotate(45)") .attr("transform", "rotate(45)")
.style("text-anchor", "start"); .style("text-anchor", "start");
// Add Y axis
g.append("g") g.append("g")
.call(d3.axisLeft(y).ticks(10)); .call(d3.axisLeft(y).ticks(10));
// 📌 Annotate lowest bar with dynamic box // Annotate lowest bar
if (groupedData.length > 0) { if (groupedData.length > 0) {
const flat = groupedData.flatMap(g => g.values); const flat = groupedData.flatMap(g => g.values);
const lowest = flat.reduce((min, d) => d.value < min.value ? d : min, flat[0]); const lowest = flat.reduce((min, d) => d.value < min.value ? d : min, flat[0]);
@ -293,6 +306,7 @@ function drawGrouped(data) {
const labelText = `Lowest: ${lowest["Reference area"]}`; const labelText = `Lowest: ${lowest["Reference area"]}`;
// Measure text width
const tempText = g.append("text") const tempText = g.append("text")
.attr("x", -9999) .attr("x", -9999)
.attr("y", -9999) .attr("y", -9999)
@ -303,6 +317,7 @@ function drawGrouped(data) {
const textWidth = tempText.node().getBBox().width; const textWidth = tempText.node().getBBox().width;
tempText.remove(); tempText.remove();
// Draw annotation background box
g.append("rect") g.append("rect")
.attr("class", "lowest-box") .attr("class", "lowest-box")
.attr("x", barX - textWidth / 2 - 6) .attr("x", barX - textWidth / 2 - 6)
@ -315,6 +330,7 @@ function drawGrouped(data) {
.attr("stroke-width", 0.5) .attr("stroke-width", 0.5)
.attr("opacity", 0.9); .attr("opacity", 0.9);
// Draw annotation text
g.append("text") g.append("text")
.attr("class", "lowest-annotation") .attr("class", "lowest-annotation")
.attr("x", barX) .attr("x", barX)
@ -328,6 +344,7 @@ function drawGrouped(data) {
} }
function drawDotPlot(data) { function drawDotPlot(data) {
// Setup x and y scales
const x = d3.scaleLinear() const x = d3.scaleLinear()
.domain([0, 100]) .domain([0, 100])
.range([0, width]); .range([0, width]);
@ -337,6 +354,7 @@ function drawDotPlot(data) {
.range([0, height]) .range([0, height])
.padding(0.3); .padding(0.3);
// Group data by country
const groupedData = data.reduce((acc, d) => { const groupedData = data.reduce((acc, d) => {
const found = acc.find(v => v.Country === d["Reference area"]); const found = acc.find(v => v.Country === d["Reference area"]);
const value = parseFloat(d.OBS_VALUE); const value = parseFloat(d.OBS_VALUE);
@ -346,6 +364,7 @@ function drawDotPlot(data) {
return acc; return acc;
}, []); }, []);
// Draw dot points
const countryGroups = g.append("g") const countryGroups = g.append("g")
.selectAll("g") .selectAll("g")
.data(groupedData) .data(groupedData)
@ -361,6 +380,7 @@ function drawDotPlot(data) {
.attr("r", 0) .attr("r", 0)
.attr("fill", d => color(d["Socio-economic status"])) .attr("fill", d => color(d["Socio-economic status"]))
.on("mouseover", function(event, d) { .on("mouseover", function(event, d) {
// Show tooltip
tooltip.html(`<strong>${d["Reference area"]}</strong><br/>${d["Socio-economic status"]}<br/><strong>${d.OBS_VALUE}%</strong>`) tooltip.html(`<strong>${d["Reference area"]}</strong><br/>${d["Socio-economic status"]}<br/><strong>${d.OBS_VALUE}%</strong>`)
.style("left", (event.pageX + 10) + "px") .style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 40) + "px") .style("top", (event.pageY - 40) + "px")
@ -369,6 +389,7 @@ function drawDotPlot(data) {
.style("transform", "translateY(-10px)"); .style("transform", "translateY(-10px)");
}) })
.on("mouseout", function() { .on("mouseout", function() {
// Hide tooltip
tooltip.transition().duration(500) tooltip.transition().duration(500)
.style("opacity", 0) .style("opacity", 0)
.style("transform", "translateY(0px)"); .style("transform", "translateY(0px)");
@ -379,6 +400,7 @@ function drawDotPlot(data) {
.attr("cx", d => x(d.value)) .attr("cx", d => x(d.value))
.attr("r", 6); .attr("r", 6);
// Add axes
g.append("g") g.append("g")
.attr("transform", `translate(0,${height})`) .attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x).ticks(10)); .call(d3.axisBottom(x).ticks(10));
@ -386,6 +408,7 @@ function drawDotPlot(data) {
g.append("g") g.append("g")
.call(d3.axisLeft(y)); .call(d3.axisLeft(y));
// Add x-axis label
g.append("text") g.append("text")
.attr("x", width / 2) .attr("x", width / 2)
.attr("y", height + 50) .attr("y", height + 50)
@ -394,7 +417,7 @@ function drawDotPlot(data) {
.style("fill", darkMode ? "#fff" : "#000") .style("fill", darkMode ? "#fff" : "#000")
.text("Percentage Reporting Good Health (%)"); .text("Percentage Reporting Good Health (%)");
// 📌 Annotate lowest dot with dynamic box // Annotate lowest dot
if (groupedData.length > 0) { if (groupedData.length > 0) {
const flat = groupedData.flatMap(g => g.values); const flat = groupedData.flatMap(g => g.values);
const lowest = flat.reduce((min, d) => d.value < min.value ? d : min, flat[0]); const lowest = flat.reduce((min, d) => d.value < min.value ? d : min, flat[0]);
@ -404,6 +427,7 @@ function drawDotPlot(data) {
const labelText = `Lowest: ${lowest["Reference area"]}`; const labelText = `Lowest: ${lowest["Reference area"]}`;
// Measure text width
const tempText = g.append("text") const tempText = g.append("text")
.attr("x", -9999) .attr("x", -9999)
.attr("y", -9999) .attr("y", -9999)
@ -414,6 +438,7 @@ function drawDotPlot(data) {
const textWidth = tempText.node().getBBox().width; const textWidth = tempText.node().getBBox().width;
tempText.remove(); tempText.remove();
// Draw annotation background box
g.append("rect") g.append("rect")
.attr("class", "lowest-box") .attr("class", "lowest-box")
.attr("x", dotX - textWidth / 2 - 6) .attr("x", dotX - textWidth / 2 - 6)
@ -426,6 +451,7 @@ function drawDotPlot(data) {
.attr("stroke-width", 0.5) .attr("stroke-width", 0.5)
.attr("opacity", 0.9); .attr("opacity", 0.9);
// Draw annotation text
g.append("text") g.append("text")
.attr("class", "lowest-annotation") .attr("class", "lowest-annotation")
.attr("x", dotX) .attr("x", dotX)
@ -439,6 +465,7 @@ function drawDotPlot(data) {
} }
function drawHeatmap(data) { function drawHeatmap(data) {
// Setup x and y scales for heatmap
const countries = [...new Set(data.map(d => d["Reference area"]))]; const countries = [...new Set(data.map(d => d["Reference area"]))];
const x = d3.scaleBand() const x = d3.scaleBand()
@ -451,10 +478,12 @@ function drawHeatmap(data) {
.range([0, height]) .range([0, height])
.padding(0.05); .padding(0.05);
// Color scale for heatmap (sequential)
const colorScale = d3.scaleSequential() const colorScale = d3.scaleSequential()
.interpolator(d3.interpolateYlGnBu) .interpolator(d3.interpolateYlGnBu)
.domain([0, 100]); .domain([0, 100]);
// Draw heatmap squares
g.selectAll() g.selectAll()
.data(data) .data(data)
.join("rect") .join("rect")
@ -464,6 +493,7 @@ function drawHeatmap(data) {
.attr("height", y.bandwidth()) .attr("height", y.bandwidth())
.attr("fill", d => colorScale(parseFloat(d.OBS_VALUE))) .attr("fill", d => colorScale(parseFloat(d.OBS_VALUE)))
.on("mouseover", function(event, d) { .on("mouseover", function(event, d) {
// Show tooltip
tooltip.html(`<strong>${d["Reference area"]}</strong><br/>${d["Socio-economic status"]}: ${d.OBS_VALUE}%`) tooltip.html(`<strong>${d["Reference area"]}</strong><br/>${d["Socio-economic status"]}: ${d.OBS_VALUE}%`)
.style("left", (event.pageX + 10) + "px") .style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 40) + "px") .style("top", (event.pageY - 40) + "px")
@ -472,11 +502,13 @@ function drawHeatmap(data) {
.style("transform", "translateY(-10px)"); .style("transform", "translateY(-10px)");
}) })
.on("mouseout", function() { .on("mouseout", function() {
// Hide tooltip
tooltip.transition().duration(500) tooltip.transition().duration(500)
.style("opacity", 0) .style("opacity", 0)
.style("transform", "translateY(0px)"); .style("transform", "translateY(0px)");
}); });
// Add axes
g.append("g") g.append("g")
.attr("transform", `translate(0,${height})`) .attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x)); .call(d3.axisBottom(x));