sync
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
from flask import Flask, jsonify, render_template
|
||||
import re
|
||||
import os
|
||||
from collections import Counter
|
||||
import html
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
MESSAGE_FILE_PATH = '/home/david/code/personal_development/sms/michelle_kifer_messages.txt'
|
||||
|
||||
# Simple list of common English stop words
|
||||
STOP_WORDS = set([
|
||||
'a', 'about', 'above', 'after', 'again', 'against', 'all', 'am', 'an', 'and', 'any', 'are', 'as', 'at',
|
||||
'be', 'because', 'been', 'before', 'being', 'below', 'between', 'both', 'but', 'by', 'can', 'did', 'do',
|
||||
'does', 'doing', 'down', 'during', 'each', 'few', 'for', 'from', 'further', 'had', 'has', 'have', 'having',
|
||||
'he', 'her', 'here', 'hers', 'herself', 'him', 'himself', 'his', 'how', 'i', 'if', 'in', 'into', 'is', 'it',
|
||||
'its', 'itself', 'just', 'me', 'more', 'most', 'my', 'myself', 'no', 'nor', 'not', 'now', 'of', 'off', 'on',
|
||||
'once', 'only', 'or', 'other', 'our', 'ours', 'ourselves', 'out', 'over', 'own', 's', 'same', 'she', 'should',
|
||||
'so', 'some', 'such', 't', 'than', 'that', 'the', 'their', 'theirs', 'them', 'themselves', 'then', 'there',
|
||||
'these', 'they', 'this', 'those', 'through', 'to', 'too', 'under', 'until', 'up', 'very', 'was', 'we', 'were',
|
||||
'what', 'when', 'where', 'which', 'while', 'who', 'whom', 'why', 'will', 'with', 'you', 'your', 'yours',
|
||||
'yourself', 'yourselves', 'im', 'ive', 'id', 'like', 'its', 'im', 'get'
|
||||
])
|
||||
|
||||
# Words that indicate a desire or a gift-related action
|
||||
GIFT_TRIGGER_WORDS = set(['want', 'wants', 'needed', 'needs', 'wishing', 'wish', 'buy', 'buying', 'get', 'getting', 'love', 'loves', 'like', 'likes'])
|
||||
|
||||
# Words that are themselves often gifts
|
||||
GIFT_ITEM_WORDS = set(['book', 'books', 'jewelry', 'necklace', 'earrings', 'bracelet', 'ring', 'concert', 'tickets', 'shoes', 'boots', 'jacket', 'shirt', 'dress', 'album', 'game', 'watch', 'bag', 'purse'])
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def process_text():
|
||||
"""Reads and analyzes the message file to produce word data."""
|
||||
if not os.path.exists(MESSAGE_FILE_PATH):
|
||||
return []
|
||||
|
||||
with open(MESSAGE_FILE_PATH, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
# Clean and tokenize the text
|
||||
words = re.findall(r'\b\w+\b', text.lower())
|
||||
|
||||
# Calculate frequency of all non-stop-words
|
||||
word_counts = Counter(word for word in words if word not in STOP_WORDS and len(word) > 1)
|
||||
|
||||
processed_data = []
|
||||
for i, word in enumerate(words):
|
||||
if word in word_counts:
|
||||
# Calculate gift score
|
||||
gift_score = 0
|
||||
# Check for nearby trigger words (within a 5-word window before the current word)
|
||||
window = words[max(0, i-5):i]
|
||||
if GIFT_TRIGGER_WORDS.intersection(window):
|
||||
gift_score += 2
|
||||
|
||||
# Check if the word itself is a common gift item
|
||||
if word in GIFT_ITEM_WORDS:
|
||||
gift_score += 3
|
||||
|
||||
# Add word to our data list - we handle aggregation on the client-side for simplicity
|
||||
processed_data.append({
|
||||
'text': word,
|
||||
'size': word_counts[word],
|
||||
'gift_score': gift_score
|
||||
})
|
||||
|
||||
# Deduplicate and aggregate scores
|
||||
final_data = {}
|
||||
for item in processed_data:
|
||||
if item['text'] not in final_data:
|
||||
final_data[item['text']] = {'size': item['size'], 'gift_score': 0}
|
||||
final_data[item['text']]['gift_score'] += item['gift_score']
|
||||
|
||||
# Convert back to list
|
||||
return [{'text': k, 'size': v['size'], 'gift_score': v['gift_score']} for k, v in final_data.items()]
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Serve the main HTML page."""
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/get-word-data')
|
||||
def get_word_data():
|
||||
"""Provide the processed word data as JSON."""
|
||||
data = process_text()
|
||||
return jsonify(data)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Starting the Gift Idea Visualizer application.")
|
||||
print("Please open your web browser and go to: http://127.0.0.1:5000")
|
||||
app.run(debug=True)
|
||||
@@ -0,0 +1,61 @@
|
||||
document.getElementById('generateBtn').addEventListener('click', function() {
|
||||
console.log("Button clicked, fetching word data...");
|
||||
// Show a temporary loading message
|
||||
const cloudDiv = document.getElementById('wordcloud');
|
||||
cloudDiv.innerHTML = '<p>Generating... please wait.</p>';
|
||||
|
||||
fetch('/get-word-data')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log("Data received from backend:", data);
|
||||
generateCloud(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching word data:', error);
|
||||
cloudDiv.innerHTML = '<p>Could not load word data. Is the server running?</p>';
|
||||
});
|
||||
});
|
||||
|
||||
function generateCloud(words) {
|
||||
// Clear the loading message
|
||||
const cloudDiv = document.getElementById('wordcloud');
|
||||
cloudDiv.innerHTML = '';
|
||||
|
||||
const width = 800;
|
||||
const height = 400;
|
||||
|
||||
// Create a color scale. Words with a higher gift_score get a gold color.
|
||||
const colorScale = d3.scale.linear()
|
||||
.domain([0, 1, 2, 3, 5]) // Corresponds to gift_score
|
||||
.range(["#606770", "#1877f2", "#f5b400", "#f5b400", "#ff8c00"]); // Grey -> Blue -> Gold -> Orange
|
||||
|
||||
// The main word cloud layout logic
|
||||
d3.layout.cloud().size([width, height])
|
||||
.words(words)
|
||||
.padding(5)
|
||||
.rotate(function() { return ~~(Math.random() * 2) * 90; }) // 0 or 90 degrees rotation
|
||||
.font("Impact")
|
||||
.fontSize(function(d) { return 10 + Math.sqrt(d.size) * 5; }) // Scale font size
|
||||
.on("end", draw)
|
||||
.start();
|
||||
|
||||
// This function is called when the layout algorithm finishes
|
||||
function draw(words) {
|
||||
d3.select("#wordcloud").append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
|
||||
.selectAll("text")
|
||||
.data(words)
|
||||
.enter().append("text")
|
||||
.style("font-size", function(d) { return d.size + "px"; })
|
||||
.style("font-family", "Impact")
|
||||
.style("fill", function(d) { return colorScale(d.gift_score); })
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("transform", function(d) {
|
||||
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
|
||||
})
|
||||
.text(function(d) { return d.text; });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background-color: #f0f2f5;
|
||||
color: #1c1e21;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #ffffff;
|
||||
padding: 2rem 3rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #1877f2;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #606770;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #e9ebee;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
}
|
||||
|
||||
.highlight-gold {
|
||||
color: #f5b400;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #1877f2;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
padding: 0.8rem 1.5rem;
|
||||
cursor: pointer;
|
||||
margin-top: 1rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #166fe5;
|
||||
}
|
||||
|
||||
#wordcloud {
|
||||
width: 800px;
|
||||
height: 400px;
|
||||
margin-top: 1rem;
|
||||
border: 1px solid #dddfe2;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#wordcloud svg {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gift Idea Visualizer</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h1>Gift Idea Visualizer</h1>
|
||||
<p>This word cloud is generated from the messages in <code>michelle_kifer_messages.txt</code>.</p>
|
||||
<p>Words are sized by frequency. Potential gift ideas are highlighted in <span class="highlight-gold">gold</span>.</p>
|
||||
<button id="generateBtn">Generate Visualization</button>
|
||||
<div id="wordcloud"></div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-cloud/1.2.5/d3.layout.cloud.min.js"></script>
|
||||
<script src="/static/script.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user