(data)
| 3764 | function _wifiHmViewport(W, H) { |
| 3765 | const ML = 34, MR = 10, MT = 20, MB = 24; // equal totals → the floor plot stays square |
| 3766 | const pw = W - ML - MR, ph = H - MT - MB; |
| 3767 | const v = _wifiHm.view; |
| 3768 | const span = 1 / v.zoom; // visible fraction of the floor |
| 3769 | const x0 = v.cx - span / 2, y0 = v.cy - span / 2; |
| 3770 | return { |
| 3771 | ML, MT, pw, ph, x0, y0, span, |
| 3772 | toX: wx => ML + (wx - x0) / span * pw, |
| 3773 | toY: wy => MT + (wy - y0) / span * ph, |
| 3774 | fromX: px => x0 + (px - ML) / pw * span, |
| 3775 | fromY: py => y0 + (py - MT) / ph * span, |
| 3776 | }; |
| 3777 | } |
| 3778 | |
| 3779 | function _wifiHmClampView() { |
| 3780 | const v = _wifiHm.view; |
| 3781 | v.zoom = Math.max(1, Math.min(16, v.zoom)); |
| 3782 | const half = 0.5 / v.zoom; |
| 3783 | v.cx = Math.max(half, Math.min(1 - half, v.cx)); |
| 3784 | v.cy = Math.max(half, Math.min(1 - half, v.cy)); |
| 3785 | } |
| 3786 | |
| 3787 | // Zoom by `factor`, keeping the point under (px,py) canvas px fixed (or the |
| 3788 | // view centre when called from the ± buttons). |
| 3789 | function wifiHmZoom(factor, px, py) { |
| 3790 | const cv = document.getElementById('wifi-heatmap'); if (!cv) return; |
| 3791 | const vp = _wifiHmViewport(cv.clientWidth, cv.clientHeight || cv.clientWidth); |
| 3792 | const v = _wifiHm.view; |
| 3793 | const ax = px != null ? vp.fromX(px) : v.cx; |
| 3794 | const ay = py != null ? vp.fromY(py) : v.cy; |
| 3795 | const oldZoom = v.zoom; |
| 3796 | v.zoom = Math.max(1, Math.min(16, v.zoom * factor)); |
| 3797 | const k = oldZoom / v.zoom; |
| 3798 | v.cx = ax - (ax - v.cx) * k; |
| 3799 | v.cy = ay - (ay - v.cy) * k; |
| 3800 | _wifiHmClampView(); |
| 3801 | wifiHeatmapRender(); |
| 3802 | } |
| 3803 | |
| 3804 | function wifiHmZoomReset() { |
| 3805 | _wifiHm.view = { zoom: 1, cx: 0.5, cy: 0.5 }; |
| 3806 | wifiHeatmapRender(); |
| 3807 | } |
| 3808 | |
| 3809 | function _wifiHmSyncScaleUI() { |
| 3810 | const m = _wifiHmFloorM(); |
| 3811 | const area = document.getElementById('wifi-hm-area'); |
| 3812 | if (area) area.textContent = Math.round(m * m).toLocaleString() + ' m²'; |
| 3813 | } |
| 3814 | |
| 3815 | // Floor-size input changed: keep the modelled APs' real-world size in sync |
| 3816 | // (predictions use metres), remember the choice, redraw the rulers. |
| 3817 | function wifiHmScaleChanged() { |
| 3818 | const m = _wifiHmFloorM(); |
| 3819 | _wifiHmSyncScaleUI(); |
| 3820 | if (_wifiHm.predictAps.length) { |
| 3821 | _wifiHm.predictAps.forEach(ap => { ap.width_m = m; ap.height_m = m; }); |
| 3822 | _wifiHmPersistAp(); |
| 3823 | } |
no test coverage detected