Rainwater Trapping Visualization

Table of Contents

Introduction

This document contains the code and instructions for a web application that visualizes the rainwater trapping problem. Users can interact with the algorithm step-by-step and modify column heights.

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rainwater Trapping Visualization</title>
</head>
<body>
    <div id="root"></div>
    <script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
    <script type="text/babel" src="app.js"></script>
</body>
</html>

CSS

body {
    font-family: Arial, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
    background-color: #f0f0f0;
}

.container {
    text-align: center;
}

.visualization {
    display: flex;
    align-items: flex-end;
    height: 300px;
    background-color: #e0e0e0;
    border: 1px solid #ccc;
    margin-bottom: 20px;
}

.column {
    width: 30px;
    background-color: #3498db;
    margin: 0 2px;
    position: relative;
}

.water {
    background-color: rgba(52, 152, 219, 0.5);
    position: absolute;
    bottom: 100%;
    width: 100%;
}

.pointer {
    position: absolute;
    bottom: -20px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 20px;
    color: #e74c3c;
}

button {
    margin: 5px;
    padding: 5px 10px;
    font-size: 16px;
}

input {
    width: 50px;
    margin: 5px;
    padding: 5px;
    font-size: 16px;
}

JavaScript (React)

const { useState, useEffect } = React;

function RainwaterVisualization() {
    const [heights, setHeights] = useState([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]);
    const [water, setWater] = useState(Array(heights.length).fill(0));
    const [leftPointer, setLeftPointer] = useState(0);
    const [rightPointer, setRightPointer] = useState(heights.length - 1);
    const [step, setStep] = useState(0);
    const [totalWater, setTotalWater] = useState(0);
    const [newHeight, setNewHeight] = useState('');
    const [selectedIndex, setSelectedIndex] = useState('');

    useEffect(() => {
        calculateWater();
    }, [heights]);

    const calculateWater = () => {
        let left = 0;
        let right = heights.length - 1;
        let leftMax = 0;
        let rightMax = 0;
        const waterLevels = Array(heights.length).fill(0);
        let total = 0;

        while (left < right) {
            if (heights[left] < heights[right]) {
                if (heights[left] >= leftMax) {
                    leftMax = heights[left];
                } else {
                    waterLevels[left] = leftMax - heights[left];
                    total += waterLevels[left];
                }
                left++;
            } else {
                if (heights[right] >= rightMax) {
                    rightMax = heights[right];
                } else {
                    waterLevels[right] = rightMax - heights[right];
                    total += waterLevels[right];
                }
                right--;
            }
        }

        setWater(waterLevels);
        setTotalWater(total);
    };

    const nextStep = () => {
        if (leftPointer >= rightPointer) {
            setStep(0);
            setLeftPointer(0);
            setRightPointer(heights.length - 1);
            return;
        }

        if (heights[leftPointer] < heights[rightPointer]) {
            setLeftPointer(leftPointer + 1);
        } else {
            setRightPointer(rightPointer - 1);
        }
        setStep(step + 1);
    };

    const resetVisualization = () => {
        setStep(0);
        setLeftPointer(0);
        setRightPointer(heights.length - 1);
    };

    const handleHeightChange = (e) => {
        setNewHeight(e.target.value);
    };

    const handleIndexChange = (e) => {
        setSelectedIndex(e.target.value);
    };

    const updateHeight = () => {
        if (selectedIndex !== '' && newHeight !== '') {
            const index = parseInt(selectedIndex);
            const height = parseInt(newHeight);
            if (index >= 0 && index < heights.length && height >= 0) {
                const newHeights = [...heights];
                newHeights[index] = height;
                setHeights(newHeights);
                setNewHeight('');
                setSelectedIndex('');
            }
        }
    };

    return (
        <div className="container">
            <h1>Rainwater Trapping Visualization</h1>
            <div className="visualization">
                {heights.map((height, index) => (
                    <div
                        key={index}
                        className="column"
                        style={{ height: `${height * 30}px` }}
                    >
                        <div
                            className="water"
                            style={{ height: `${water[index] * 30}px` }}
                        ></div>
                        {index === leftPointer && <div className="pointer">L</div>}
                        {index === rightPointer && <div className="pointer">R</div>}
                    </div>
                ))}
            </div>
            <p>Step: {step}</p>
            <p>Total Water: {totalWater} units</p>
            <button onClick={nextStep}>Next Step</button>
            <button onClick={resetVisualization}>Reset</button>
            <div>
                <input
                    type="number"
                    placeholder="Index"
                    value={selectedIndex}
                    onChange={handleIndexChange}
                    min="0"
                    max={heights.length - 1}
                />
                <input
                    type="number"
                    placeholder="New Height"
                    value={newHeight}
                    onChange={handleHeightChange}
                    min="0"
                />
                <button onClick={updateHeight}>Update Height</button>
            </div>
        </div>
    );
}

ReactDOM.render(<RainwaterVisualization />, document.getElementById('root'));

Setup and Usage Instructions

To set up and run this web application using Emacs:

  1. Create a new directory for your project.
  2. Open Emacs and create a new file named rainwater-visualization.org.
  3. Copy the entire content of this org-mode document into the file.
  4. Save the file.
  5. Use C-c C-e t to tangle the org-mode file. This will create three files: index.html, styles.css, and app.js.
  6. Start the Emacs built-in HTTP server:
    • Run M-x httpd-start
    • The server will start on http://localhost:8080/ by default
  7. Open a web browser and navigate to http://localhost:8080/index.html to view the application.

Troubleshooting

  • If you encounter any issues with tangling, make sure you have org-babel configured correctly in your Emacs setup.
  • If the HTTP server doesn't start, check if port 8080 is already in use. You can change the port by customizing the httpd-port variable.
  • If the visualization doesn't appear, check the browser's console for any JavaScript errors.
  • Ensure that you have an active internet connection, as the application relies on external React and Babel scripts.

Conclusion

This org-mode document provides a complete setup for a rainwater trapping visualization web application. Users can interact with the algorithm step-by-step and modify column heights to see how it affects the water trapping calculation.

Author: Your Name

jwalsh@nexus

Last Updated: 2025-07-30 13:45:28

build: 2025-12-23 09:12 | sha: e32f33e