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 >
0 commit comments