diff --git a/index.html b/index.html
index 52e3ab34f8e3d474e42e12d003f48c644646f947..555b1394946de0c08c4f3a4a70d18fbdf08ae749 100644
--- a/index.html
+++ b/index.html
@@ -31,10 +31,26 @@
     <div id="time-series-container"></div>
 </div>
 
+<div class="visualization-container">
+    <div id="loading">Loading...</div>
+    <div class="dropdown-container button">
+        <select id="state-dropdown" class="button-inner">
+            <option value="">All States</option>
+        </select>
+    </div>
+    <div id="parallel-coordinates-container"></div>
+</div>
+
+<div class="visualization-container">
+    <div id="bubble-container"></div>
+</div>
+
 <div id="bottom-spacer"></div>
 
 
-<script src="tree.js"></script>
-<script src="time-series.js"></script>
+<script src="modules/tree.js" type="module"></script>
+<script src="modules/time-series.js" type="module"></script>
+<script src="modules/parallel.js" type="module"></script>
+<script src="modules/bubble.js" type="module"></script>
 </body>
 </html>
\ No newline at end of file
diff --git a/modules/bubble.js b/modules/bubble.js
new file mode 100644
index 0000000000000000000000000000000000000000..ed711a803d72f6721b4d6976550b48bdae9729b2
--- /dev/null
+++ b/modules/bubble.js
@@ -0,0 +1,130 @@
+import {width, height, margin, colors} from "./constants.js";
+import {legend} from "./country-legend.js";
+
+
+// Load the combined dataset
+Promise.all([
+    d3.csv("schengen_data.csv"), // Schengen data
+    d3.csv("world-data-2023.csv")  // Country data
+]).then(([schengenData1, countryData]) => {
+    // Map country data by country name for quick lookup
+    const countryMap = {};
+    countryData.forEach(d => {
+        countryMap[d.Country] = {
+            gdp: +d.GDP.replace(/[$,]/g, ''), // Parse GDP
+            population: +d.Population.replace(/[$,]/g, '') // Parse population
+        };
+    });
+
+    // Merge GDP and population data into Schengen dataset
+    schengenData1.forEach(d => {
+        const countryInfo = countryMap[d["Schengen State"]];
+        d.GDP = countryInfo ? countryInfo.gdp : 0;
+        d.Population = countryInfo ? countryInfo.population : 0;
+    });
+
+    const schengenData = Array.from(d3.rollup(
+        schengenData1,
+        v => ({
+            GDP: v[0].GDP,
+            Population: v[0].Population,
+            "Visas Issued": d3.sum(v, d => d["Total  uniform visas issued (including MEV)"]),
+            "Visas Applied for": d3.sum(v, d => d["Total ATVs and uniform visas applied for"])
+        }),
+        d => d["Schengen State"]
+    )).map(([key, value]) => ({
+        "Schengen State": key,
+        GDP: value.GDP,
+        Population: value.Population,
+        "Visas Issued": value["Visas Issued"],
+        "Visas Applied for": value["Visas Applied for"]
+    }));
+
+    console.log(schengenData);
+
+
+    const xScale = d3.scaleLog()
+        .domain([d3.min(schengenData, d => d.GDP), d3.max(schengenData, d => d.GDP)])
+        .range([0, width-margin.left-margin.right-130]);
+
+    const yScale = d3.scaleLog()
+        .domain([d3.min(schengenData, d => d.Population), d3.max(schengenData, d => d.Population)])
+        .range([height-margin.top-margin.bottom-110, 0]);
+
+    const sizeScale = d3.scaleSqrt()
+        .domain([0, d3.max(schengenData, d => d["Visas Issued"] || 1)])
+        .range([8, 60]);
+
+    const opacityScale = d3.scaleSqrt()
+        .domain([0, d3.max(schengenData, d => d["Visas Applied for"] || 1)])
+        .range([0.1, 1]);
+
+    //const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
+    const colorScale = d3
+        .scaleOrdinal()
+        .domain(schengenData)
+        .range(colors)
+
+
+    // Append SVG container
+    const svg = d3.select("#bubble-container")
+        .append("svg")
+        .attr("width", width)
+        .attr("height", height)
+        .append("g")
+        .attr("transform", `translate(${margin.left+80},${margin.top+50})`);
+
+    // Add X axis
+    svg.append("g")
+        .attr("transform", `translate(0,${height-margin.top-margin.bottom-110})`)
+        .call(d3.axisBottom(xScale).ticks(10, ".1s"))
+        .append("text")
+        .attr("class", "axis-label")
+        .attr("transform", `translate(${(width-margin.left-margin.right-150)/2},50)`)
+        .text("GDP")
+        .style("fill", "black");
+
+    // Add Y axis
+    svg.append("g")
+        .call(d3.axisLeft(yScale))
+        .append("text")
+        .attr("class", "axis-label")
+        //.attr("x", -height / 2)
+        .attr("y", -50)
+        .attr("transform", `rotate(-90) translate(${-(height-margin.top-margin.bottom-120)/2},-10)`)
+        .text("Population")
+        .style("fill", "black");
+
+    // Draw bubbles
+    const tooltip = d3.select("body")
+        .append("div")
+        .attr("class", "tooltip")
+        .style("visibility", "hidden");
+
+    svg.selectAll("circle")
+        .data(schengenData)
+        .enter()
+        .append("circle")
+        .attr("cx", d => xScale(d.GDP || 1e10))
+        .attr("cy", d => yScale(d.Population || 1))
+        .attr("r", d => Math.round(sizeScale(d["Visas Issued"] || 1)))
+        .style("opacity", d => opacityScale(d["Visas Applied for"] || 1))
+        .attr("fill", d => colorScale(d["Schengen State"]))
+        .on("mouseover", (event, d) => {
+            tooltip.style("visibility", "visible")
+                .html(`
+                            <strong>${d["Schengen State"]}</strong><br>
+                            GDP: $${d.GDP.toLocaleString()}<br>
+                            Population: ${d.Population.toLocaleString()}<br>
+                            Total Visas Issued: ${d["Visas Issued"].toLocaleString()}
+                        `)
+                .style("left", `${event.pageX + 10}px`)
+                .style("top", `${event.pageY - 20}px`);
+        })
+        .on("mouseout", () => {
+            tooltip.style("visibility", "hidden");
+        });
+
+    const allStates = new Set(schengenData.map(d => d["Schengen State"]));
+    legend(svg, allStates, colorScale)
+});
diff --git a/modules/constants.js b/modules/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..7ca34a6e7a7db214d392abc85be465cf8a14d761
--- /dev/null
+++ b/modules/constants.js
@@ -0,0 +1,6 @@
+//const container = Array.from(document.querySelector('.visualization-container'))[0];
+
+export const margin = { top: 20, right: 30, bottom: 50, left: 50 };
+export const width = window.innerWidth - margin.left - margin.right;
+export const height = window.innerHeight - margin.top - margin.bottom;
+export const colors = ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928","#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"]
\ No newline at end of file
diff --git a/modules/country-legend.js b/modules/country-legend.js
new file mode 100644
index 0000000000000000000000000000000000000000..df205421c844d023faa969b0227d58a052b2c3b2
--- /dev/null
+++ b/modules/country-legend.js
@@ -0,0 +1,53 @@
+import {width, height} from "./constants.js";
+
+
+export function legend(svg, allStates, color) {
+    const legend = svg
+        .selectAll(".legend-group")
+        .data([null])
+        .enter()
+        .append("g")
+        .attr("class", "legend-group")
+        .attr("transform", `translate(${width - 200}, 0)`)
+        .selectAll(".legend-item")
+        .data(Array.from(allStates))
+        .enter()
+        .append("g")
+        .attr("class", "legend-item")
+        .attr("transform", (d, i) => `translate(0, ${i * 20})`)
+        .each(function(d, i) {
+            const group = d3.select(this);
+
+            group.append("rect")
+                .attr("width", 10)
+                .attr("height", 10)
+                .attr("fill", color(d))
+                .on("mouseover", () => {
+                    svg.selectAll(".line").classed("highlighted", line => line === d);
+                })
+                .on("mouseout", () => {
+                    svg.selectAll(".line").classed("highlighted", false);
+                });
+
+            group.append("text")
+                .attr("x", 20)
+                .attr("y", 10)
+                .attr("text-anchor", "start")
+                .style("text-transform", "capitalize")
+                .text(d);
+        });
+
+    const legendBBox = svg.select(".legend-group").node().getBBox()
+
+    svg.select(".legend-group")
+        .insert("rect", ":first-child")
+        .attr("width", legendBBox.width+20)
+        .attr("height", legendBBox.height+20)
+        .attr("transform", `translate(-10, -10)`)
+        .attr("fill", "white")
+        .attr("fill-opacity", 0.7)
+        .attr("stroke", "#c6c6c6")
+        .attr("stroke-width", 2)
+        .attr("rx", 5)
+        .attr("ry", 5)
+}
\ No newline at end of file
diff --git a/modules/parallel.js b/modules/parallel.js
new file mode 100644
index 0000000000000000000000000000000000000000..66a71919f0a2a0f1df29251c53d66c0477e2a655
--- /dev/null
+++ b/modules/parallel.js
@@ -0,0 +1,139 @@
+import {width, height, margin} from "./constants.js";
+
+// Append SVG
+const svg = d3.select("#parallel-coordinates-container")
+    .append("svg")
+    .attr("width", width)
+    .attr("height", height)
+    .append("g")
+    .attr("transform", `translate(${margin.left+margin.right-10},${margin.top+margin.bottom})`);
+
+const loadingIndicator = d3.select("#loading");
+
+// Show loading indicator
+function showLoading() {
+    loadingIndicator.style("display", "block");
+}
+
+// Hide loading indicator
+function hideLoading() {
+    loadingIndicator.style("display", "none");
+}
+
+// Load data
+showLoading();
+d3.csv("schengen_data.csv").then((data) => {
+    // Parse data
+    data.forEach(d => {
+        for (const key in d) {
+            if (!isNaN(+d[key])) {
+                d[key] = +d[key];
+            }
+        }
+    });
+
+    // Define dimensions and scales
+    const dimensions = [
+        "Airport transit visas (ATVs) applied for",
+        "ATVs issued (including multiple)",
+        "Not issued rate for ATVs",
+        "Uniform visas applied for",
+        "Total  uniform visas issued (including MEV)",
+        "Not issued rate for uniform visas"
+    ];
+
+    const yScales = {};
+    dimensions.forEach(dim => {
+        yScales[dim] = d3.scaleLinear()
+            .domain(d3.extent(data, d => d[dim]))
+            .range([height-margin.top-margin.bottom-30, 0]);
+    });
+
+    const xScale = d3.scalePoint()
+        .domain(dimensions)
+        .range([0, width-margin.left-margin.right]);
+
+    // Line generator
+    const lineGenerator = d3.line()
+        .defined(d => !isNaN(d[1]))
+        .x(d => xScale(d[0]))
+        .y(d => yScales[d[0]](d[1]));
+
+    // Draw axes with labels
+    dimensions.forEach(dim => {
+        svg.append("g")
+            .attr("transform", `translate(${xScale(dim)},0)`)
+            .call(d3.axisLeft(yScales[dim]));
+
+        // Add axis label
+        svg.append("text")
+            .attr("transform", `translate(${xScale(dim)},${height-margin.left-margin.right})`)
+            .style("text-anchor", "middle")
+            .style("font-size", "12px")
+            .text(dim);
+    });
+
+
+    // Group data by state
+    const groupedData = d3.group(data, d => d["Schengen State"]);
+    const states = Array.from(groupedData.keys());
+
+    // Populate dropdown
+    const dropdown = d3.select("#state-dropdown");
+    dropdown.selectAll("option")
+        .data(states)
+        .enter()
+        .append("option")
+        .attr("value", d => d)
+        .text(d => d);
+
+    // Update plot function
+    function updatePlot(state) {
+        const filteredData = state ? groupedData.get(state) : data;
+
+        if (!filteredData) {
+            console.warn(`No data available for state: ${state}`);
+            svg.selectAll(".data-line").remove();
+            return;
+        }
+
+        const paths = svg.selectAll(".data-line")
+            .data(filteredData);
+
+        // Enter and update paths
+        paths.enter()
+            .append("path")
+            .attr("class", "data-line")
+            .merge(paths)
+            .attr("d", d => lineGenerator(dimensions.map(dim => [dim, d[dim]])))
+            .style("stroke", "steelblue")
+            .style("opacity", 0.1)
+            .attr("fill", "none");
+
+
+        // Remove old paths
+        paths.exit().remove();
+    }
+
+    // Initialize with full dataset
+    updatePlot(null);
+
+    // Dropdown change event
+    dropdown.on("change", function() {
+        const selectedState = d3.select(this).property("value");
+        updatePlot(selectedState);
+    });
+
+    // Add reset button functionality
+    d3.select("#reset-button")
+        .on("click", () => {
+            dropdown.property("value", "");
+            updatePlot(null);
+        });
+
+    // Hide loading indicator after rendering
+    hideLoading();
+}).catch(err => {
+    console.error("Error loading data: ", err);
+    hideLoading();
+});
\ No newline at end of file
diff --git a/time-series.js b/modules/time-series.js
similarity index 61%
rename from time-series.js
rename to modules/time-series.js
index aebd213114f13ecf97fe40d3a2d386305d9e3552..fc0106081ce51b3b2dfb32822b6435b9a54719bb 100644
--- a/time-series.js
+++ b/modules/time-series.js
@@ -1,4 +1,6 @@
-//const margin = { top: 20, right: 30, bottom: 50, left: 50 };
+import {legend} from "./country-legend.js";
+import {width, height, margin, colors} from "./constants.js";
+
 
 function resize() {
     const container = document.getElementById('time-series-container');
@@ -10,7 +12,10 @@ function resize() {
 d3.csv("schengen_data.csv").then((data) => {
     const { width, height } = resize();
     const allStates = new Set(data.map(d => d["Schengen State"]));
-    const color = d3.scaleOrdinal(d3.schemeCategory10).domain(allStates);
+    const color = d3
+        .scaleOrdinal()
+        .domain(data)
+        .range(colors)
 
     const svg = d3.select("#time-series-container")
         .append("svg")
@@ -79,55 +84,7 @@ d3.csv("schengen_data.csv").then((data) => {
                 })
         }
 
-        const legend = svg
-            .selectAll(".legend-group")
-            .data([null])
-            .enter()
-            .append("g")
-            .attr("class", "legend-group")
-            .attr("transform", `translate(${width - 150}, 0)`)
-            .selectAll(".legend-item")
-            .data(Array.from(allStates))
-            .enter()
-            .append("g")
-            .attr("class", "legend-item")
-            .attr("transform", (d, i) => `translate(0, ${i * 20})`)
-            .each(function(d, i) {
-                const group = d3.select(this);
-
-                group.append("rect")
-                    .attr("width", 10)
-                    .attr("height", 10)
-                    .attr("fill", color(d))
-                    .on("mouseover", () => {
-                        svg.selectAll(".line").classed("highlighted", line => line === d);
-                    })
-                    .on("mouseout", () => {
-                        svg.selectAll(".line").classed("highlighted", false);
-                    });
-
-                group.append("text")
-                    .attr("x", 20)
-                    .attr("y", 10)
-                    .attr("text-anchor", "start")
-                    .style("text-transform", "capitalize")
-                    .text(d);
-            });
-
-        const legendBBox = svg.select(".legend-group").node().getBBox()
-        console.log(legendBBox)
-
-        svg.select(".legend-group")
-            .insert("rect", ":first-child")
-            .attr("width", legendBBox.width+20)
-            .attr("height", legendBBox.height+20)
-            .attr("transform", `translate(-10, -10)`)
-            .attr("fill", "white")
-            .attr("fill-opacity", 0.7)
-            .attr("stroke", "#c6c6c6")
-            .attr("stroke-width", 2)
-            .attr("rx", 5)
-            .attr("ry", 5)
+        legend(svg, allStates, color)
     }
 
     document.getElementById("visa-type-selector").addEventListener("change", (event) => {
diff --git a/tree.js b/modules/tree.js
similarity index 94%
rename from tree.js
rename to modules/tree.js
index e39a8b50af20dfa3b399a6e01e094fc333acd068..6fecec6922d98210e0c43aa00c423569800b4f01 100644
--- a/tree.js
+++ b/modules/tree.js
@@ -1,7 +1,5 @@
-const margin = { top: 20, right: 30, bottom: 50, left: 50 };
-const container = document.getElementById('tree-container');
-const width = container.offsetWidth-15//- margin.left - margin.right;
-const height = container.offsetHeight-15//- margin.top - margin.bottom;
+import {width, height, margin} from "./constants.js";
+
 
 let currentData;
 let hierarchyStack = ["Schengen States"];
diff --git a/style.css b/style.css
index 5e6f19a5b4e06e8728f505e38c86abe3476b2bed..58bb909669d1d47232f30fa951f94b9bfd49949c 100644
--- a/style.css
+++ b/style.css
@@ -10,6 +10,19 @@ html, body, #tree-container, #time-series-container {
     padding: 0;
 }
 
+#loading {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 1.5em;
+    background: rgba(255, 255, 255, 0.8);
+    padding: 10px;
+    border: 1px solid #ccc;
+    border-radius: 5px;
+    display: none;
+}
+
 #bottom-spacer {
     height: 1px;
 }