Skip to content

Commit

Permalink
Bubble mosaic v1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
yseoo committed May 27, 2024
1 parent 91fa726 commit 46e016d
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 3 deletions.
7 changes: 7 additions & 0 deletions assets/css/mosaic.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#bubble_zoom {
display: flex;
justify-content: center;
align-items: center;
width: 80%;
height: auto;
}
8 changes: 8 additions & 0 deletions assets/scripts/globe.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,12 @@ document.getElementById('yearSlider').addEventListener('input', function() {
// Update the globe data based on the selected outcomes when checkboxes change
document.querySelectorAll('#checkboxes input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', () => updateGlobeData(parseInt(document.getElementById('yearSlider').value)));
});

const globe_buttons = d3.selectAll("#globe_section .stadium-button");

globe_buttons.on("click", function() {
globe_buttons.classed("clicked", false);
d3.select(this).classed("clicked", true);
let value = d3.select(this).text();
});
159 changes: 159 additions & 0 deletions assets/scripts/mosaic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
const PACKING_ELEMENT_ID = "bubble_zoom";
const PACKING_TEXT_ELEMENT_ID = "circle-packing-text";

const LIGHT_BLUE_COLOR = '#cceeff'; // Light blue color to match the background
const GREEN_COLOR = '#2e5d52';
const BLACK_COLOR = '#000000';

const CIRCLE_PADDING = 10;

const MIN_FONT_SIZE = 5;
const MAX_FONT_SIZE = 50;

const DEFAULT_PACKING_TEXT = 'Click on each circle to zoom in and explore the different types of events.';
const PACKING_TEXT = {
// Add relevant text descriptions here
};

const width = 800; // Adjust the width for better centering
const height = 600; // Adjust the height for better centering

d3.csv('data/modified_data.csv').then(function(data) {
const filteredData = data.filter(d => d.Outcome !== "Ongoing" && d['Broad Category'] !== "unknown");
const root = d3.hierarchy({ children: groupData(filteredData) })
.sum(d => d.value)
.sort((a, b) => b.value - a.value);

initPacking(root);
});

function groupData(data) {
const groupedData = d3.group(data, d => d.Outcome);
return Array.from(groupedData, ([outcome, values]) => ({
name: outcome,
children: Array.from(d3.group(values, d => d.Continent), ([continent, values]) => ({
name: continent,
children: Array.from(d3.group(values, d => d['Broad Category']), ([category, values]) => ({
name: category,
value: values.length
}))
}))
}));
}

function initPacking(root) {
const pack = d3.pack()
.size([width, height])
.padding(CIRCLE_PADDING);

const nodes = pack(root).descendants();
let focus = root;

const colorScale = d3.scaleLinear()
.domain([0, 2])
.range([LIGHT_BLUE_COLOR, GREEN_COLOR]);

const fontSizeScale = d3.scaleSqrt()
.domain([d3.min(nodes, d => d.r), d3.max(nodes, d => d.r)])
.range([MIN_FONT_SIZE, MAX_FONT_SIZE]);

const svg = d3.select(`#${PACKING_ELEMENT_ID}`)
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`) // Adjust the viewBox for better centering
.attr("preserveAspectRatio", "xMidYMid meet") // Ensure the SVG is centered
.style("display", "block")
.style("background", colorScale(0))
.style("cursor", "pointer")
.on("click", (event) => zoom(event, root));

const node = svg.append("g")
.attr("transform", `translate(${width / 2},${height / 2})`) // Center the group within the SVG
.selectAll("circle")
.data(nodes.slice(1))
.join("circle")
.attr("fill", d => d.children ? colorScale(d.depth) : LIGHT_BLUE_COLOR)
.attr("pointer-events", d => !d.children ? "none" : null)
.on("mouseover", function() { d3.select(this).attr("stroke", BLACK_COLOR); })
.on("mouseout", function() { d3.select(this).attr("stroke", null); })
.on("click", (event, d) => focus !== d && (zoom(event, d), event.stopPropagation()));

const label = svg.append("g")
.attr("transform", `translate(${width / 2},${height / 2})`) // Center the group within the SVG
.style("font-family", "'Comic Sans MS', 'Comic Sans', 'Chalkboard', 'Marker Felt', sans-serif") // Bubbly and fat font
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.selectAll("text")
.data(nodes)
.join("text")
.attr("dy", "0.3em")
.style("font-size", d => `${Math.min(2 * d.r / 3, (2 * d.r - 8) / getTextWidth(d.data.name, `${d.r}px 'Comic Sans MS', 'Comic Sans', 'Chalkboard', 'Marker Felt', sans-serif`))}px`) // Adjust font size to fit within bubbles
.style("fill-opacity", d => d.parent === root ? 1 : 0)
.style("display", d => d.parent === root ? "inline" : "none")
.text(d => d.data.name);

let view;
const zoomTo = (v) => {
const k = Math.min(width, height) / v[2];

view = v;

label.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`);
node.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`);
node.attr("r", d => d.r * k);
};

zoomTo([root.x, root.y, root.r * 2]);

function zoom(event, d) {
const packingText = document.getElementById(PACKING_TEXT_ELEMENT_ID);
if (packingText) {
const p = packingText.getElementsByTagName("p")[0];
const packingStr = PACKING_TEXT[d.data.name] || DEFAULT_PACKING_TEXT;
p.innerHTML = packingStr;
}

focus = d;

const transition = svg.transition()
.duration(event.altKey ? 7500 : 750)
.tween("zoom", () => {
const i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2]);
return t => zoomTo(i(t));
});

label
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.transition(transition)
.style("fill-opacity", d => d.parent === focus ? 1 : 0)
.on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}
}

// Utility function to measure text width
function getTextWidth(text, font) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.font = font;
const metrics = context.measureText(text);
return metrics.width;
}

















11 changes: 8 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<link rel="stylesheet" href="assets/css/main.css">
<link rel="stylesheet" href="assets/css/globe.css">
<link rel="stylesheet" href="assets/css/button.css">
<link rel="stylesheet" href="assets/css/mosaic.css">

</head>
<body>
Expand Down Expand Up @@ -119,9 +120,12 @@ <h1>Bubble type distribution</h1>
<div id="type_event_continent"></div>
</div>
<div class="slide" id="slide2">
<div id="bubble_zoom"></div>
<h1>Bubble Mosaic</h1>
<img src="assets/img/bubble_zoom.jpg" alt="Sketch of the type zoomed bubble" width="70%" class="type_of_event">
<div class="header">
<h1>Bubble Mosaic</h1>
</div>
<div class="content">
<div id="bubble_zoom"></div>
</div>
</div>
<div class="slide" id="slide3">
<div id="events_link"></div>
Expand Down Expand Up @@ -217,6 +221,7 @@ <h1 class="title">About Us</h1>
<!-- Custom JavaScript -->
<script type="module" src='assets/scripts/main.js'></script>
<script src='assets/scripts/globe.js'></script>
<script src='assets/scripts/mosaic.js'></script>
<!-- <script src='assets/scripts/bubble.js'></script> -->
</body>
</html>

0 comments on commit 46e016d

Please sign in to comment.