week4-Word Tree Visualization
Intro
For Week 4, I am thinking a project related to word counting, and I was inspired by the Word Tree project on Hint.fm. My goal was to build an interactive word tree visualization that allows users to input text, choose from the top 5 most frequent words, and generate a tree based on the word contexts. The project also includes zoom, pan, and dynamic word frequency-based font sizing to enhance the visualization's usability and aesthetics.
This document outlines how I approached the project and explains the key steps in building this tool.
project link
Step 1: Counting Word Frequencies
The first step was to create a function that counts the frequency of each word in a case-insensitive manner. This function allows us to later identify the most frequent words and offer them as options for generating the word tree.
function getWordFrequency(text) {
const wordCounts = {};
const words = text.split(/\s+/);
words.forEach(word => {
word = word.toLowerCase(); // Convert words to lowercase
wordCounts[word] = (wordCounts[word] || 0) + 1;
});
return wordCounts;
}
This ensures that common words like "the" and "The" are treated as the same word in the frequency count.
Step 2: Creating the Word Tree Structure
Next, I implemented the logic for generating a word tree. This function takes the input text and a selected word, then builds a tree structure showing how the selected word connects to the words that follow it in sentences.
function generateWordTree(text, searchWord) {
const sentences = text.toLowerCase().split(/[.!?]/).map(sentence => sentence.trim());
let treeData = { name: searchWord, children: [] };
sentences.forEach(sentence => {
let words = sentence.split(/\s+/);
for (let i = 0; i < words.length - 1; i++) {
if (words[i] === searchWord) {
let parentNode = treeData;
for (let j = i + 1; j < words.length; j++) {
const word = words[j];
let childNode = parentNode.children.find(c => c.name === word);
if (!childNode) {
childNode = { name: word, children: [], frequency: 1 };
parentNode.children.push(childNode);
} else {
childNode.frequency++;
}
parentNode = childNode;
}
}
}
});
return treeData;
}
This code builds the hierarchical structure necessary for visualizing the word tree based on the user's selected word.
Step 3: Visualizing the Word Tree Using D3.js
The third step was to visualize the tree structure using D3.js. I created a layout that ensures enough space between nodes and added links between parent and child nodes.
const treeLayout = d3.tree().nodeSize([nodeSpacingY, nodeSpacingX]);
const root = d3.hierarchy(treeData);
treeLayout(root);
// Links between nodes
svg.selectAll(".link")
.data(root.descendants().slice(1))
.enter()
.append("path")
.attr("class", "link")
.attr("d", d => `
M${d.y},${d.x}
C${(d.y + d.parent.y) / 2},${d.x}
${(d.y + d.parent.y) / 2},${d.parent.x}
${d.parent.y},${d.parent.x}
`)
.style("fill", "none")
.style("stroke", "#ccc")
.style("stroke-width", 2);
This ensures that the tree layout is clearly structured and that links between words are properly displayed.
Step 4: Adding Node Colors and Text Styling
To improve visual clarity, I assigned random colors to both the nodes and their corresponding text labels, ensuring that they match. I also made the font size dynamic based on the frequency of each word.
node.each(function(d) {
const color = getRandomColor();
d3.select(this).append("circle")
.attr("r", 5)
.style("fill", color);
d3.select(this).append("text")
.attr("dy", -10)
.style("fill", color) // Text color matches node color
.text(d => d.data.name)
.style("font-size", d => `${Math.min(12 + (d.data.frequency || 1) * 3, 30)}px`); // Font size based on word frequency
});
This approach adds a playful and informative visual element, making the tree more engaging to explore.
Step 5: Implementing Zoom and Pan
To allow users to explore the word tree more easily, I added zoom and pan functionality. This makes it easier to navigate the visualization, especially when dealing with large or complex trees.
const zoom = d3.zoom()
.scaleExtent([0.5, 5])
.on("zoom", (event) => {
svg.attr("transform", event.transform);
});
d3.select("svg").call(zoom);
The zoom and pan functionality enables users to inspect different parts of the tree without worrying about overlapping text or cramped spaces.
Step 6: User Interaction and Word Selection
Finally, I added an interactive element that allows users to choose from the top 5 most frequent words in the text. Clicking on any of these words generates a new word tree based on the chosen word.
document.getElementById("submit-text-btn").addEventListener("click", () => {
const text = document.getElementById("text-input").value;
const wordFrequency = getWordFrequency(text);
const topWords = Object.keys(wordFrequency).sort((a, b) => wordFrequency[b] - wordFrequency[a]).slice(0, 5);
topWords.forEach(word => {
const wordOption = document.createElement("div");
wordOption.className = "word-option";
wordOption.textContent = word;
wordOption.addEventListener("click", () => {
const treeData = generateWordTree(text, word);
drawWordTree(treeData);
});
document.getElementById("word-options").appendChild(wordOption);
});
});
This functionality provides an easy way for users to explore different words and their connections within the text.