Skip to content

Commit c661bf4

Browse files
Enhance heatmap UI with new interactive elements and improved styles
1 parent 95d09dc commit c661bf4

5 files changed

Lines changed: 217 additions & 11 deletions

File tree

_includes/heatmap.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,7 @@
155155
}
156156
}
157157

158-
if (fingerSelect) {
159-
fingerSelect.addEventListener('change', handleSelect);
160-
}
158+
fingerSelect.addEventListener('change', handleSelect);
161159

162160
handleSelect();
163161
document.getElementById('model-container').classList.add('active');

_includes/heatmap2.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% assign e = site.data[page.lang]['heatmap-ui'] %}
22

33
<div style="flex-basis: 100%; display: flex; justify-content: center; align-items: center; pointer-events: auto; gap: 30px; height: 137px">
4-
<p class="finger-radio">
4+
<p class="finger-radio active">
55
<label for="all-fingers">
66
{{ e.total }}
77
</label>
@@ -19,7 +19,7 @@
1919
</label>
2020
<input id="left-hand" name="finger-select" type="radio" value="L"><br>
2121
</p>
22-
<p class="finger-radio" id="finger-radio" style="visibility: hidden">
22+
<p class="finger-radio" id="finger-radio">
2323
<label for="finger-all">
2424
{{ e.all_button }}
2525
</label>
@@ -186,15 +186,15 @@
186186
function handleSelect() {
187187
let selectValue = document.querySelector('input[name="finger-select"]:checked').value;
188188
if (selectValue === "total") {
189-
if (fingerRadio.style.visibility !== 'hidden')
190-
fingerRadio.style.visibility = "hidden";
189+
if (fingerRadio.classList.contains('active'))
190+
fingerRadio.classList.remove('active');
191191
updateSelection("total");
192192
} else if (selectValue === "preferred") {
193-
if (fingerRadio.style.visibility !== 'hidden')
194-
fingerRadio.style.visibility = "hidden";
193+
if (fingerRadio.classList.contains('active'))
194+
fingerRadio.classList.remove('active');
195195
handleBestFinger();
196196
} else {
197-
fingerRadio.style.removeProperty("visibility");
197+
fingerRadio.classList.add('active');
198198
let selectFinger = document.querySelector('input[name="finger"]:checked').value;
199199
if (selectFinger === 'H') {
200200
if (selectValue === 'L') {

_includes/heatmap3.html

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
{% assign e = site.data[page.lang]['heatmap-ui'] %}
2+
3+
<div style="flex-basis: 100%; display: flex; justify-content: center; align-items: center; pointer-events: auto; gap: 30px">
4+
<select class="finger-select active" id="all-fingers">
5+
<option value="total">{{ e.total }}</option>
6+
<option value="preferred">{{ e.preferred }}</option>
7+
<option value="L">{{ e.hf_format | replace: '%h%', e.left | replace: '%f%', e.hand }}</option>
8+
<option value="H">{{ e.hf_format | replace: '%h%', e.right | replace: '%f%', e.hand }}</option>
9+
</select>
10+
<select class="finger-select" id="finger-select">
11+
<option value="H">{{ e.all_button }}</option>
12+
<option value="T">{{ e.thumb }}</option>
13+
<option value="I">{{ e.index }}</option>
14+
<option value="M">{{ e.middle }}</option>
15+
<option value="R">{{ e.ring }}</option>
16+
<option value="L">{{ e.little }}</option>
17+
</select>
18+
</div>
19+
20+
<div id="model-container"></div>
21+
22+
<!-- Three.js import map -->
23+
<script type="importmap">
24+
{
25+
"imports": {
26+
"three": "/assets/js/three.module.js",
27+
"three/oc": "/assets/js/OrbitControls.js",
28+
"three/rgb": "/assets/js/RoundedBoxGeometry.js"
29+
}
30+
}
31+
</script>
32+
33+
<!-- Heatmap3D Canvas -->
34+
{% include model-viewer.html %}
35+
36+
<!-- 3D Render of the Keycube -->
37+
{% include dataviz-data.html %}
38+
39+
<!-- ── Bottom bar: badge + finger context + legend + reset ── -->
40+
<div class="bottom-bar">
41+
<div class="finger-context" id="finger-context">
42+
{{ e.finger_context }}
43+
</div>
44+
45+
<div class="heatmap-legend" style="flex-basis: 100%; display: flex; justify-content: center; align-items: center;">
46+
<span>{{ e.unreachable }}</span>
47+
<div class="heatmap-gradient"></div>
48+
<span>{{ e.reachable }}</span>
49+
<button class="reset-view-btn" id="reset-view-btn">{{ e.reset_view }}</button>
50+
</div>
51+
</div>
52+
53+
<script src="/assets/js/dataviz.js"></script>
54+
<script type="module">
55+
import langFinger from '/assets/js/ui-lang.json' with {type: 'json'};
56+
57+
document.addEventListener('DOMContentLoaded', function () {
58+
const selectionText = document.getElementById('selection-text');
59+
const selectionIcon = document.querySelector('#selection-badge .selection-icon');
60+
const fingerContext = document.getElementById('finger-context');
61+
const fingerSelect = document.getElementById('finger-select');
62+
const allFingers = document.getElementById('all-fingers');
63+
64+
const FACES = ['R', 'B', 'G', 'W', 'Y'];
65+
const ALL_FINGERS = ['LT', 'LI', 'LM', 'LR', 'LL', 'RT', 'RI', 'RM', 'RR', 'RL'];
66+
67+
const lang = document.documentElement.lang
68+
const fingerData = langFinger[lang] ? langFinger[lang] : langFinger["en"];
69+
70+
function updateUI(key) {
71+
const data = fingerData[key] || {name: key, icon: '', info: ''};
72+
if (selectionText) selectionText.textContent = data.name;
73+
if (selectionIcon) selectionIcon.textContent = data.icon;
74+
if (fingerContext) fingerContext.textContent = data.info;
75+
}
76+
77+
function applyHeatmap(data, fixedRange) {
78+
let min = fixedRange ? fixedRange.min : Infinity;
79+
let max = fixedRange ? fixedRange.max : -Infinity;
80+
if (!fixedRange) {
81+
FACES.forEach(function (f) {
82+
data[f].forEach(function (v) {
83+
if (v < min) min = v;
84+
if (v > max) max = v;
85+
});
86+
});
87+
}
88+
if (window.updateModel) {
89+
window.updateModel({
90+
heatmap: data,
91+
heatmapMin: min,
92+
heatmapMax: max,
93+
scores: data,
94+
showScores: true,
95+
isReachability: true,
96+
scoreFormat: 'integer',
97+
figure6View: false
98+
});
99+
}
100+
}
101+
102+
function computeHandData(fingers) {
103+
const result = {};
104+
FACES.forEach(function (face) {
105+
result[face] = [];
106+
for (let i = 0; i < 16; i++) {
107+
let sum = 0;
108+
fingers.forEach(function (f) {
109+
const fData = window.perFingerReachability[f];
110+
if (fData) fData.forEach(function (p) {
111+
sum += p[face][i];
112+
});
113+
});
114+
result[face].push(sum);
115+
}
116+
});
117+
return result;
118+
}
119+
120+
function updateSelection(fingerValue) {
121+
applyHeatmap(computeHandData(fingerValue === 'total' ? ALL_FINGERS : [fingerValue]));
122+
updateUI(fingerValue, fingerValue === 'total' ? ALL_FINGERS : null);
123+
}
124+
125+
function handleHandAll(hand) {
126+
const fingers = hand === 'left' ? ['LT', 'LI', 'LM', 'LR', 'LL'] : ['RT', 'RI', 'RM', 'RR', 'RL'];
127+
applyHeatmap(computeHandData(fingers));
128+
updateUI(hand + '-all', fingers);
129+
}
130+
131+
function handleBestFinger() {
132+
const preferenceAggregate = window.preferenceAggregate;
133+
if (!preferenceAggregate) return;
134+
const result = {};
135+
FACES.forEach(function (face) {
136+
result[face] = [];
137+
for (let i = 0; i < 16; i++) {
138+
let best = 0;
139+
const preferredFingers = preferenceAggregate.tiedFingers[face][i];
140+
preferredFingers.forEach(function (f) {
141+
const fData = window.perFingerReachability[f];
142+
if (fData) {
143+
let s = 0;
144+
fData.forEach(function (p) {
145+
s += p[face][i];
146+
});
147+
if (s > best) best = s;
148+
}
149+
});
150+
result[face].push(best);
151+
}
152+
});
153+
applyHeatmap(result, {min: 0, max: 66});
154+
updateUI('best', ALL_FINGERS);
155+
}
156+
157+
function handleSelect() {
158+
let selectValue = allFingers.value;
159+
if (selectValue === "total") {
160+
if (fingerSelect.classList.contains('active')) {
161+
fingerSelect.classList.remove('active');
162+
fingerSelect.value = "H"
163+
}
164+
updateSelection("total");
165+
} else if (selectValue === "preferred") {
166+
if (fingerSelect.classList.contains('active')) {
167+
fingerSelect.classList.remove('active');
168+
fingerSelect.value = "H"
169+
}
170+
handleBestFinger();
171+
} else {
172+
fingerSelect.classList.add('active')
173+
let selectFinger = fingerSelect.value;
174+
if (selectFinger === 'H') {
175+
if (selectValue === 'L') {
176+
handleHandAll('left')
177+
} else {
178+
handleHandAll('right')
179+
}
180+
} else {
181+
updateSelection(selectValue + selectFinger)
182+
}
183+
}
184+
}
185+
186+
allFingers.addEventListener('change', handleSelect)
187+
fingerSelect.addEventListener('change', handleSelect)
188+
189+
handleSelect();
190+
document.getElementById('model-container').classList.add('active');
191+
});
192+
</script>

assets/css/heatmap.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@
5252
background-color: #fff;
5353
border: 2px solid #e8e8e8;
5454
border-radius: 5px;
55+
56+
opacity: 50%;
57+
pointer-events: none;
58+
}
59+
60+
.finger-select.active {
61+
opacity: 100%;
62+
pointer-events: auto;
5563
}
5664

5765
.finger-radio {
@@ -61,6 +69,14 @@
6169
background-color: #fff;
6270
border: 2px solid #e8e8e8;
6371
border-radius: 5px;
72+
73+
opacity: 50%;
74+
pointer-events: none;
75+
}
76+
77+
.finger-radio.active {
78+
opacity: 100%;
79+
pointer-events: auto;
6480
}
6581

6682
.row {

heatmap-fr.markdown

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ extra_css:
1313

1414
Ce rendu 3D est une analyse visuelle interactive d'une des [études menée sur le keycube](/research/), avec l’accessibilité de chaques doigts directement projetés sur le cube 3D.
1515

16-
{% include heatmap.html %}
16+
{% include heatmap3.html %}

0 commit comments

Comments
 (0)