-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathheatmap3d-reachability.html
More file actions
255 lines (229 loc) · 12.1 KB
/
heatmap3d-reachability.html
File metadata and controls
255 lines (229 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
---
layout: heatmap3d
title: Reachability – Keycube Heatmap 3D
default_mode: reachability
permalink: /heatmap3d/reachability/
lang: en
---
<a href="{{ '/software/' | relative_url }}" class="nav-link">← Software</a>
<!-- Full-screen 3D viewer -->
<div id="model-container"></div>
<!-- Floating overlay controls -->
<div class="overlay-controls">
<!-- ── Top bar: hand selector + quick actions ── -->
<div class="top-bar">
<div class="glass-panel" style="padding: 8px 14px;">
<div class="hand-selector-container">
<!-- Left Hand -->
<div class="hand-analog" id="left-hand-analog">
<div class="analog-label">Left</div>
<div class="analog-ring" data-hand="left">
<div class="analog-stick"><span class="hand-emoji">L</span></div>
<button class="finger-dot finger-thumb-left" data-finger="LT" data-tooltip="Left Thumb"><span class="finger-letter">T</span></button>
<button class="finger-dot finger-index-left" data-finger="LI" data-tooltip="Left Index"><span class="finger-letter">I</span></button>
<button class="finger-dot finger-middle-left" data-finger="LM" data-tooltip="Left Middle"><span class="finger-letter">M</span></button>
<button class="finger-dot finger-ring-left" data-finger="LR" data-tooltip="Left Ring"><span class="finger-letter">R</span></button>
<button class="finger-dot finger-little-left" data-finger="LL" data-tooltip="Left Little"><span class="finger-letter">L</span></button>
</div>
</div>
<!-- Center: All + Best buttons -->
<div class="hand-divider" style="display: flex; flex-direction: column; gap: 6px;">
<button class="hand-action-btn active" id="btn-all-fingers" data-tooltip="All fingers combined (total score)">All</button>
<button class="hand-action-btn" id="btn-best-finger" data-tooltip="Best finger per key (preferred)">⭐ Best</button>
</div>
<!-- Right Hand -->
<div class="hand-analog" id="right-hand-analog">
<div class="analog-label">Right</div>
<div class="analog-ring" data-hand="right">
<div class="analog-stick"><span class="hand-emoji">R</span></div>
<button class="finger-dot finger-thumb-right" data-finger="RT" data-tooltip="Right Thumb"><span class="finger-letter">T</span></button>
<button class="finger-dot finger-index-right" data-finger="RI" data-tooltip="Right Index"><span class="finger-letter">I</span></button>
<button class="finger-dot finger-middle-right" data-finger="RM" data-tooltip="Right Middle"><span class="finger-letter">M</span></button>
<button class="finger-dot finger-ring-right" data-finger="RR" data-tooltip="Right Ring"><span class="finger-letter">R</span></button>
<button class="finger-dot finger-little-right" data-finger="RL" data-tooltip="Right Little"><span class="finger-letter">L</span></button>
</div>
</div>
</div>
</div>
</div>
<!-- ── Bottom bar: badge + finger context + legend + reset ── -->
<div class="bottom-bar">
<div class="selection-badge" id="selection-badge">
<span class="selection-icon">🖐️</span>
<span class="selection-text" id="selection-text">All Fingers (Total)</span>
</div>
<div class="finger-context" id="finger-context">
Combined reachability across all 10 fingers and 22 participants.
</div>
<div class="heatmap-legend">
<span>Unreachable (0)</span>
<div class="heatmap-gradient"></div>
<span>Reachable (3)</span>
</div>
<button class="reset-view-btn" id="reset-view-btn">↻ Reset View</button>
</div>
</div>
<!-- Hidden select for dataviz.js compatibility -->
<select class="finger-select" id="finger-select" style="display:none;">
<option value="total">All Fingers (Total)</option>
<option value="LT">Left Thumb</option>
<option value="LI">Left Index</option>
<option value="LM">Left Middle</option>
<option value="LR">Left Ring</option>
<option value="LL">Left Little</option>
<option value="RT">Right Thumb</option>
<option value="RI">Right Index</option>
<option value="RM">Right Middle</option>
<option value="RR">Right Ring</option>
<option value="RL">Right Little</option>
</select>
<!-- Side panel toggle -->
<button class="side-panel-toggle" onclick="document.getElementById('info-panel').classList.toggle('hidden')">ℹ️ Info</button>
<!-- Side panel -->
<div id="info-panel" class="side-panel glass-panel hidden">
<h4>How to Use</h4>
<div class="mode-description">
<p style="margin-bottom: 8px;">1. Click a <strong>finger dot</strong> to see its reach</p>
<p style="margin-bottom: 8px;">2. Click the <strong>hand center</strong> for all 5 fingers</p>
<p>3. Use <strong>All / Best</strong> buttons for quick filtering</p>
</div>
<h4>Hand-Face Mapping</h4>
<div class="hand-face-map" style="margin-top: 8px;">
<div class="hand-face-card">
<div class="hand-face-title">🤚 Left Hand</div>
<div class="face-tags">
<span class="face-tag" style="background:#4dabf7;color:#fff;">B</span>
<span class="face-tag" style="background:#f8f9fa;color:#333;border:1px solid #ccc;">W</span>
<span class="face-tag" style="background:#ff6b6b;color:#fff;">R½</span>
</div>
</div>
<div class="hand-face-card">
<div class="hand-face-title">Right Hand 🤚</div>
<div class="face-tags">
<span class="face-tag" style="background:#ffd43b;color:#333;">Y</span>
<span class="face-tag" style="background:#51cf66;color:#fff;">G</span>
<span class="face-tag" style="background:#ff6b6b;color:#fff;">R½</span>
</div>
</div>
</div>
<h4>Study Info</h4>
<div class="study-info">
<div class="study-stat-grid">
<div class="study-stat-item"><strong>22</strong>Participants</div>
<div class="study-stat-item"><strong>80</strong>Keys</div>
<div class="study-stat-item"><strong>10</strong>Fingers</div>
<div class="study-stat-item"><strong>3-pt</strong>Scale</div>
</div>
<p>Scoring: <span style="color:#51cf66">3</span> = easily reachable, <span style="color:#ffd43b">1</span> = with effort, <span style="color:#ff6b6b">0</span> = unreachable.</p>
<p>Data: Sum across 22 participants. Max per key per finger = 66.</p>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
var fingerSelect = document.getElementById('finger-select');
var selectionText = document.getElementById('selection-text');
var selectionIcon = document.querySelector('#selection-badge .selection-icon');
var fingerContext = document.getElementById('finger-context');
var allFingerDots = document.querySelectorAll('.finger-dot');
var analogRings = document.querySelectorAll('.analog-ring');
var btnAllFingers = document.getElementById('btn-all-fingers');
var btnBestFinger = document.getElementById('btn-best-finger');
var handActionBtns = document.querySelectorAll('.hand-action-btn');
var FACES = ['R','B','G','W','Y'];
var fingerData = {
'total': { name: 'All Fingers (Total)', icon: '🖐️', info: 'Combined reachability across all 10 fingers and 22 participants. Max score ~220.' },
'best': { name: 'Best Finger (per key)', icon: '⭐', info: 'Shows the score of the best-performing finger for each key. Max score = 66 per key.' },
'left-all': { name: 'Left Hand (All)', icon: '🤚', info: 'Left hand covers Blue (external), White (internal), and left half of Red (top).' },
'right-all': { name: 'Right Hand (All)', icon: '🤚', info: 'Right hand covers Yellow (external), Green (internal), and right half of Red (top).' },
'LT': { name: 'Left Thumb', icon: '👍', info: 'Left Thumb — widest reach on the left side.' },
'LI': { name: 'Left Index', icon: '☝️', info: 'Left Index — upper rows of Blue face.' },
'LM': { name: 'Left Middle', icon: '🖕', info: 'Left Middle — middle rows of Blue face.' },
'LR': { name: 'Left Ring', icon: '💍', info: 'Left Ring — lower rows of Blue face.' },
'LL': { name: 'Left Little', icon: '🤙', info: 'Left Little — bottom corner of Blue only.' },
'RT': { name: 'Right Thumb', icon: '👍', info: 'Right Thumb — widest reach on right side.' },
'RI': { name: 'Right Index', icon: '☝️', info: 'Right Index — upper rows of Yellow face.' },
'RM': { name: 'Right Middle', icon: '🖕', info: 'Right Middle — middle rows of Yellow face.' },
'RR': { name: 'Right Ring', icon: '💍', info: 'Right Ring — lower rows of Yellow face.' },
'RL': { name: 'Right Little', icon: '🤙', info: 'Right Little — bottom corner of Yellow only.' }
};
function updateUI(key, activeFingers) {
var data = fingerData[key] || { name: key, icon: '🖐️', info: '' };
if (selectionText) selectionText.textContent = data.name;
if (selectionIcon) selectionIcon.textContent = data.icon;
if (fingerContext) fingerContext.textContent = data.info;
allFingerDots.forEach(function (dot) {
var f = dot.getAttribute('data-finger');
dot.classList.toggle('active', activeFingers ? activeFingers.indexOf(f) !== -1 : f === key);
});
analogRings.forEach(function (ring) { ring.classList.remove('active'); });
if (key.charAt(0) === 'L' || key === 'left-all') {
var lr = document.querySelector('.analog-ring[data-hand="left"]');
if (lr) lr.classList.add('active');
} else if (key.charAt(0) === 'R' || key === 'right-all') {
var rr = document.querySelector('.analog-ring[data-hand="right"]');
if (rr) rr.classList.add('active');
}
}
function applyHeatmap(data) {
var min = Infinity, max = -Infinity;
FACES.forEach(function (f) { data[f].forEach(function (v) { if (v < min) min = v; if (v > max) max = v; }); });
if (window.updateModel) {
window.updateModel({ heatmap: data, heatmapMin: min, heatmapMax: max, scores: data, showScores: true, isReachability: true });
}
}
function computeHandData(fingers) {
var result = {};
FACES.forEach(function (face) {
result[face] = [];
for (var i = 0; i < 16; i++) {
var sum = 0;
fingers.forEach(function (f) {
var fData = window.perFingerReachability[f];
if (fData) fData.forEach(function (p) { sum += p[face][i]; });
});
result[face].push(sum);
}
});
return result;
}
function updateSelection(fingerValue) {
if (fingerSelect) { fingerSelect.value = fingerValue; fingerSelect.dispatchEvent(new Event('change')); }
updateUI(fingerValue);
handActionBtns.forEach(function (btn) { btn.classList.remove('active'); });
if (fingerValue === 'total') btnAllFingers.classList.add('active');
}
function handleHandAll(hand) {
var fingers = hand === 'left' ? ['LT','LI','LM','LR','LL'] : ['RT','RI','RM','RR','RL'];
applyHeatmap(computeHandData(fingers));
updateUI(hand + '-all', fingers);
handActionBtns.forEach(function (btn) { btn.classList.remove('active'); });
}
function handleBestFinger() {
var allFingers = ['LT','LI','LM','LR','LL','RT','RI','RM','RR','RL'];
var result = {};
FACES.forEach(function (face) {
result[face] = [];
for (var i = 0; i < 16; i++) {
var best = 0;
allFingers.forEach(function (f) {
var fData = window.perFingerReachability[f];
if (fData) { var s = 0; fData.forEach(function (p) { s += p[face][i]; }); if (s > best) best = s; }
});
result[face].push(best);
}
});
applyHeatmap(result);
updateUI('best');
handActionBtns.forEach(function (btn) { btn.classList.remove('active'); });
btnBestFinger.classList.add('active');
}
allFingerDots.forEach(function (dot) {
dot.addEventListener('click', function (e) { e.stopPropagation(); updateSelection(dot.getAttribute('data-finger')); });
});
analogRings.forEach(function (ring) {
ring.addEventListener('click', function (e) { if (!e.target.closest('.finger-dot')) handleHandAll(ring.getAttribute('data-hand')); });
});
if (btnAllFingers) btnAllFingers.addEventListener('click', function () { updateSelection('total'); });
if (btnBestFinger) btnBestFinger.addEventListener('click', handleBestFinger);
});
</script>