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.


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.