Skip to content

Commit 0f5a876

Browse files
authored
Deploy v1.2.2
Develop v1.2.2
2 parents 5a4f9ee + be0d4e1 commit 0f5a876

File tree

8 files changed

+136
-30
lines changed

8 files changed

+136
-30
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ Pixels Visualiser is a client-side web app that allows users to load a JSON file
66

77
- Interactive line chart of mood evolution over time
88
- Rolling average control for smoothing data
9-
- Word frequency section based on user notes (with % toggle)
109
- Tag analysis
11-
- Responsive layout for desktop and mobile
10+
- Weekdays and months distribution
11+
- Word frequency section based on user notes (with % toggle)
12+
- Wordcloud visualisation
13+
- Generate a PNG image of your Pixels
14+
- Search Pixels by date
15+
- Responsive layout for mobile (Note: the app is still best used on desktop. On mobile, some tooltips explaining buttons and controls might not be visible.)
1216

1317
## JSON Data Format
1418

index.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ <h1>Pixels Visualiser</h1>
6565
<div class="chart-container">
6666
<h2>📈 Mood evolution</h2>
6767
<div class="options">
68-
<label for="rollingSlider">Rolling average : <span id="rollingValue">1</span> days</label>
69-
<input type="range" id="rollingSlider" class="range-average" min="1" max="60" value="1">
68+
<label for="rollingSlider">Rolling average: <span id="rollingValue">1</span> days</label>
69+
<input type="range" id="rollingSlider" class="range-slider" min="1" max="60" value="1">
7070

7171
<div class="option-item">
7272
<label for="showAverageCheckbox"
@@ -175,6 +175,11 @@ <h2>📝 Most frequent words</h2>
175175
<input type="number" id="minCountInput" class="input-nb" min="1" value="10">
176176
</div>
177177

178+
<div class="option-item">
179+
<label for="minScoreSlider" title="Only count words appearing in a Pixel with a score above the defined threshold">Min score: <span id="minScoreValue">1</span></label>
180+
<input type="range" id="minScoreSlider" class="range-slider" min="10" max="50" value="10">
181+
</div>
182+
178183
<div class="option-item">
179184
<label for="searchInput" title='hint: use "*" to search for any word, e.g. "went to *"'>Search words</label>
180185
<input type="text" id="searchInput" class="input-nb input-large" placeholder='e.g. "happy", "good day", "been to *"'>
@@ -318,7 +323,7 @@ <h2>📅 Search Pixel by Date</h2>
318323
<img src="assets/pixels_memories_logo.png" alt="Pixels Memories">
319324
</a>
320325
</div>
321-
<div class="version">Version: v1.2.0</div>
326+
<div class="version">Version: v1.2.2</div>
322327
<div class="version">Last update: 2025-06-23</div>
323328
</footer>
324329

scripts/main.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const words_percentage_checkbox = document.querySelector("#wordsPercentageCheckb
2626
const words_order_checkbox = document.querySelector("#wordsOrderCheckbox");
2727
const words_words_input = document.querySelector("#maxWordsInput");
2828
const words_count_input = document.querySelector("#minCountInput");
29+
const min_score_slider = document.querySelector("#minScoreSlider");
30+
const min_score_slider_text_value = document.querySelector("#minScoreValue");
2931
const words_search_input = document.querySelector("#searchInput");
3032

3133
const wordcloud_container = document.querySelector("#wordcloudContainer");
@@ -37,6 +39,7 @@ const btn_download_wordcloud = document.querySelector("#btnDownloadWordcloud");
3739

3840
const DEV_MODE = false;
3941
const DEV_FILE_PATH = "../data/pixels.json"
42+
const SCROLL_TO = 50;
4043
const isMobile = window.innerWidth <= 800;
4144
let initial_data = [];
4245
let current_data = [];
@@ -65,6 +68,7 @@ let wordcloudPercentage = false;
6568
let wordcloudOrderCount = false;
6669
let nbMaxWords = 20;
6770
let nbMinCount = 10;
71+
let minScore = 1;
6872
let searchTerm = "";
6973

7074
let wordcloudSize = 4;
@@ -135,7 +139,7 @@ function filter_pixels(numberOfDays) {
135139
compute_tag_stats(current_data);
136140
compute_weekdays_stats(current_data);
137141
compute_months_stats(current_data);
138-
get_word_frequency(current_data, wordcloudOrderCount, searchTerm);
142+
get_word_frequency(current_data, wordcloudOrderCount, minScore, searchTerm);
139143

140144
calculate_and_display_stats(current_data);
141145
create_mood_chart(current_data, averagingValue, showAverage, showYears);
@@ -182,7 +186,7 @@ async function handle_file_upload(file) {
182186
compute_tag_stats(current_data);
183187
compute_weekdays_stats(current_data);
184188
compute_months_stats(current_data);
185-
get_word_frequency(current_data, wordcloudOrderCount, searchTerm);
189+
get_word_frequency(current_data, wordcloudOrderCount, minScore, searchTerm);
186190

187191
// Graphics
188192
create_mood_chart(current_data, averagingValue, showAverage, showYears);
@@ -236,7 +240,7 @@ document.addEventListener("DOMContentLoaded", () => {
236240
if (DEV_MODE) {
237241
auto_load_data(DEV_FILE_PATH);
238242
setTimeout(() => {
239-
window.scrollTo(0, document.body.scrollHeight - 2000);
243+
window.scrollTo(0, SCROLL_TO);
240244
}, 500);
241245
}
242246

@@ -341,7 +345,7 @@ document.addEventListener("DOMContentLoaded", () => {
341345

342346
words_order_checkbox.addEventListener("change", (e) => {
343347
wordcloudOrderCount = e.target.checked;
344-
get_word_frequency(current_data, wordcloudOrderCount, searchTerm);
348+
get_word_frequency(current_data, wordcloudOrderCount, minScore, searchTerm);
345349
create_word_frequency_section(current_data, nbMaxWords, nbMinCount, wordcloudPercentage, searchTerm);
346350
});
347351

@@ -355,9 +359,16 @@ document.addEventListener("DOMContentLoaded", () => {
355359
create_word_frequency_section(current_data, nbMaxWords, nbMinCount, wordcloudPercentage, searchTerm);
356360
});
357361

362+
min_score_slider.addEventListener("input", (e) => {
363+
minScore = (parseInt(e.target.value) / 10).toFixed(1);
364+
min_score_slider_text_value.textContent = minScore;
365+
get_word_frequency(current_data, wordcloudOrderCount, minScore, searchTerm);
366+
create_word_frequency_section(current_data, nbMaxWords, nbMinCount, wordcloudPercentage, searchTerm);
367+
});
368+
358369
words_search_input.addEventListener("input", (e) => {
359370
searchTerm = e.target.value.toLowerCase();
360-
get_word_frequency(current_data, wordcloudOrderCount, searchTerm);
371+
get_word_frequency(current_data, wordcloudOrderCount, minScore, searchTerm);
361372
create_word_frequency_section(current_data, nbMaxWords, nbMinCount, wordcloudPercentage, searchTerm);
362373
});
363374

scripts/png.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ function set_image_settings(settings) {
5959
}
6060

6161

62+
function get_user_colors() {
63+
return [
64+
setting_color1.value,
65+
setting_color2.value,
66+
setting_color3.value,
67+
setting_color4.value,
68+
setting_color5.value,
69+
setting_colorEmpty.value
70+
];
71+
}
72+
73+
6274
function get_pixel_color(pixel, scoreType, colorMap) {
6375
if (!pixel || !Array.isArray(pixel.scores) || pixel.scores.length === 0) {
6476
return colorMap.empty;

scripts/statistics.js

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,71 @@ function calculate_and_display_stats(data) {
7373
});
7474

7575
stats = [
76-
["Number of Pixels", data.length],
77-
["Average score", average(allScores).toFixed(2)],
78-
["Streaks", `Best: ${streaks.bestStreak} | Latest: ${streaks.latestStreak}`],
76+
["Number of Pixels", `<p>${data.length}</p>`],
77+
["Average score", `<p>${average(allScores).toFixed(2)}</p>`],
78+
["Streaks", `<p>Best: ${streaks.bestStreak} | Latest: ${streaks.latestStreak}</p>`],
79+
["Score distribution", "<canvas id='scoresPieChart' class='pie-chart' width='100' height='100'></canvas>"],
7980
];
8081

8182
stats_container.innerHTML = stats.map(([title, value]) => `
8283
<div class="stat-card">
8384
<h3>${title}</h3>
84-
<p>${value}</p>
85+
${value}
8586
</div>
86-
`).join("")
87+
`).join("");
88+
89+
create_scores_pie_chart();
90+
}
91+
92+
93+
async function create_scores_pie_chart() {
94+
const rawScores = current_data.reduce((acc, entry) => {
95+
entry.scores.forEach(score => {
96+
acc[score] = (acc[score] || 0) + 1;
97+
});
98+
return acc;
99+
}, {});
100+
101+
const scoresCount = Object.keys(rawScores).map(Number);
102+
const values = scoresCount.map(score => rawScores[score] || 0);
103+
104+
const ctx = document.querySelector("#scoresPieChart").getContext("2d");
105+
new Chart(ctx, {
106+
type: "pie",
107+
data: {
108+
labels: scoresCount,
109+
datasets: [{
110+
label: "Scores",
111+
data: values,
112+
backgroundColor: get_user_colors(),
113+
borderColor: "#000000",
114+
borderWidth: 1
115+
}]
116+
},
117+
options: {
118+
responsive: false,
119+
plugins: {
120+
legend: {
121+
display: false
122+
},
123+
tooltip: {
124+
callbacks: {
125+
label: function (context) {
126+
const value = context.parsed;
127+
const data = context.chart.data.datasets[0].data;
128+
const total = data.reduce((a, b) => a + b, 0);
129+
const percentage = ((value / total) * 100).toFixed(1);
130+
return `${percentage}%`;
131+
}
132+
}
133+
}
134+
}
135+
}
136+
});
87137
}
88138

89139

140+
90141
function compute_tag_stats(data) {
91142
const tagCounts = {};
92143
const tagScores = {};
@@ -152,13 +203,13 @@ function compute_months_stats(data) {
152203
}
153204

154205

155-
function get_word_frequency(data, orderByMood, searchText) {
206+
function get_word_frequency(data, orderByMood, minScore, searchText) {
156207
const wordData = {}; // { word: { count: X, scores: [n, n, ...] } }
157208
const searchTextLower = normalize_string(searchText);
158209
const searchWords = searchTextLower
159-
.split(/\s+/)
160-
.map(word => normalize_string(word))
161-
.filter(word => word);
210+
.split(/\s+/)
211+
.map(word => normalize_string(word))
212+
.filter(word => word);
162213

163214
let searchPattern = null;
164215
if (searchTextLower.includes("*")) {
@@ -197,6 +248,7 @@ function get_word_frequency(data, orderByMood, searchText) {
197248
(word.replace(/[^a-zA-Z]/g, "").length >= 3) && // Word is at least 3 letters long
198249
(!STOP_WORDS.has(word)) && // Word is not a stop word
199250
(searchTextLower !== word) && // Word is not the search term (already counted above)
251+
(average(entry.scores) >= minScore) && // Word meets min score requirement
200252
(
201253
(searchWords.length === 0) || // Either no search words or
202254
searchWords.some((sw, i) => {
@@ -257,19 +309,19 @@ async function create_word_frequency_section(data, maxWords, minCount, inPercent
257309
if (words_filtered.length > 0) {
258310
word_freq_container.innerHTML = `
259311
${words_filtered.map(word => {
260-
let isWordSearched = false;
261-
if (searchText) {
262-
const normalizedSearchText = normalize_string(searchText);
263-
isWordSearched = (normalize_string(word.word) === normalizedSearchText) ||
312+
let isWordSearched = false;
313+
if (searchText) {
314+
const normalizedSearchText = normalize_string(searchText);
315+
isWordSearched = (normalize_string(word.word) === normalizedSearchText) ||
264316
searchText.split(/\s+/)
265-
.some(term => normalize_string(word.word) === (normalize_string(term)));
266-
}
267-
return `<div class="word-card ${isWordSearched ? "searched-word" : ""}">
317+
.some(term => normalize_string(word.word) === (normalize_string(term)));
318+
}
319+
return `<div class="word-card ${isWordSearched ? "searched-word" : ""}">
268320
<h4>${capitalize(word.word)}</h4>
269321
<p title="Number of apearance">${inPercentage ? (100 * word.count / data.length).toFixed(1) + "%" : word.count}</p>
270322
<p title="Average score">${(word.avg_score).toFixed(2)}</p>
271323
</div>`
272-
}
324+
}
273325
).join("")}`
274326
}
275327
else { word_freq_container.innerHTML = "<p>No word frequency data available</p>"; }
@@ -281,7 +333,7 @@ async function create_word_frequency_section(data, maxWords, minCount, inPercent
281333
async function update_wordcloud(minCount) {
282334
const words = full_word_frequency
283335
.filter(word => word.count >= minCount)
284-
.sort((a, b) => {
336+
.sort((a, b) => {
285337
if (wordcloudOrderCount) { // Global variable
286338
return b.avg_score - a.avg_score;
287339
}

scripts/storage.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ async function store_settings() {
1919
wordcloudOrderCount,
2020
nbMaxWords,
2121
nbMinCount,
22+
minScore,
2223

2324
wordcloudSize,
2425
wordcloudSpacing,
@@ -94,6 +95,11 @@ async function load_settings() {
9495
nbMinCount = settings.nbMinCount;
9596
words_count_input.value = nbMinCount;
9697

98+
// Min score
99+
minScore = settings.minScore;
100+
min_score_slider.value = 10 * minScore;
101+
min_score_slider_text_value.textContent = minScore;
102+
97103
// Wordcloud size
98104
wordcloudSize = settings.wordcloudSize;
99105
wordcloud_size_input.value = wordcloudSize;

styles/main.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ a:hover {
128128

129129

130130
.stat-card {
131+
display: flex;
132+
flex-direction: column;
133+
justify-content: center;
134+
align-items: center;
131135
background-color: var(--secondary-bg);
132136
padding: 0.7rem;
133137
border-radius: 8px;
@@ -140,10 +144,22 @@ a:hover {
140144

141145
& p {
142146
font-size: 1.3rem;
147+
margin: 0 0 1rem;
143148
color: var(--text-color);
144149
}
150+
151+
.pie-chart {
152+
display: none;
153+
width: 150px;
154+
height: 150px;
155+
}
156+
157+
&:hover .pie-chart {
158+
display: block;
159+
}
145160
}
146161

162+
147163
.chart-container {
148164
background-color: var(--secondary-bg);
149165
padding: 1rem;

styles/options.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ input[type="color"], select {
7979
}
8080

8181

82-
.range-average {
82+
.range-slider {
8383
width: 100%;
84-
margin-bottom: 1rem;
8584
}
8685

86+
8787
.input-nb {
8888
width: 60px;
8989
height: 20px;

0 commit comments

Comments
 (0)