fixed code commenting
This commit is contained in:
parent
93ecc88ade
commit
b578c53594
3 changed files with 173 additions and 108 deletions
171
CSS/style.css
171
CSS/style.css
|
|
@ -1,76 +1,97 @@
|
||||||
|
/* Base body styles */
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
.bar {
|
|
||||||
opacity: 0.8;
|
/* Chart bars */
|
||||||
transition: opacity 0.3s;
|
.bar {
|
||||||
}
|
opacity: 0.8;
|
||||||
.bar:hover {
|
transition: opacity 0.3s;
|
||||||
opacity: 1;
|
}
|
||||||
}
|
.bar:hover {
|
||||||
.axis-label {
|
opacity: 1;
|
||||||
font-size: 14px;
|
}
|
||||||
font-weight: bold;
|
|
||||||
}
|
/* Axis labels */
|
||||||
.tooltip {
|
.axis-label {
|
||||||
position: absolute;
|
font-size: 14px;
|
||||||
padding: 6px;
|
font-weight: bold;
|
||||||
background: #fff;
|
}
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
/* Tooltip styles */
|
||||||
box-shadow: 0px 2px 8px rgba(0,0,0,0.2);
|
.tooltip {
|
||||||
font-size: 12px;
|
position: absolute;
|
||||||
pointer-events: none;
|
padding: 6px;
|
||||||
opacity: 0;
|
background: #fff;
|
||||||
transition: all 0.3s ease;
|
border: 1px solid #ccc;
|
||||||
}
|
border-radius: 5px;
|
||||||
.controls {
|
box-shadow: 0px 2px 8px rgba(0,0,0,0.2);
|
||||||
margin-bottom: 20px;
|
font-size: 12px;
|
||||||
}
|
pointer-events: none;
|
||||||
select {
|
opacity: 0;
|
||||||
margin-right: 10px;
|
transition: all 0.3s ease;
|
||||||
padding: 5px;
|
}
|
||||||
}
|
|
||||||
button {
|
/* Controls section (dropdowns and buttons) */
|
||||||
margin: 5px;
|
.controls {
|
||||||
padding: 8px 12px;
|
margin-bottom: 20px;
|
||||||
font-size: 14px;
|
}
|
||||||
cursor: pointer;
|
|
||||||
background-color: #4E79A7;
|
/* Dropdown (select) styling */
|
||||||
color: white;
|
select {
|
||||||
border: none;
|
margin-right: 10px;
|
||||||
border-radius: 4px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
button:hover {
|
|
||||||
background-color: #3a5f8c;
|
/* Buttons styling */
|
||||||
}
|
button {
|
||||||
.legend {
|
margin: 5px;
|
||||||
margin-top: 20px;
|
padding: 8px 12px;
|
||||||
}
|
font-size: 14px;
|
||||||
.legend-item {
|
cursor: pointer;
|
||||||
display: inline-block;
|
background-color: #4E79A7;
|
||||||
margin-right: 20px;
|
color: white;
|
||||||
font-size: 14px;
|
border: none;
|
||||||
}
|
border-radius: 4px;
|
||||||
.legend-color {
|
}
|
||||||
width: 12px;
|
button:hover {
|
||||||
height: 12px;
|
background-color: #3a5f8c;
|
||||||
display: inline-block;
|
}
|
||||||
margin-right: 5px;
|
|
||||||
vertical-align: middle;
|
/* Legend area */
|
||||||
}
|
.legend {
|
||||||
#loading {
|
margin-top: 20px;
|
||||||
font-size: 18px;
|
}
|
||||||
text-align: center;
|
.legend-item {
|
||||||
margin: 20px;
|
display: inline-block;
|
||||||
}
|
margin-right: 20px;
|
||||||
.hidden {
|
font-size: 14px;
|
||||||
display: none;
|
}
|
||||||
}
|
.legend-color {
|
||||||
.page-title {
|
width: 12px;
|
||||||
text-align: center;
|
height: 12px;
|
||||||
margin-top: 20px;
|
display: inline-block;
|
||||||
margin-bottom: 10px;
|
margin-right: 5px;
|
||||||
font-size: 28px;
|
vertical-align: middle;
|
||||||
font-weight: bold;
|
}
|
||||||
}
|
|
||||||
|
/* Loading message */
|
||||||
|
#loading {
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hidden class (for hiding elements like loading spinner) */
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main page title */
|
||||||
|
.page-title {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
16
index.html
16
index.html
|
|
@ -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>
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue