feat: implement binary search for optimal zoom level
Replace area-based formula with binary search to find zoom where regions occupy 10-20% of magnifier area. Binary search algorithm: 1. Start with minZoom=1, maxZoom=1000 2. Test midpoint zoom level 3. Calculate how much of magnified view each region occupies 4. If no regions fit → zoom out (maxZoom = mid) 5. If regions < 10% → zoom in (minZoom = mid) 6. If regions > 20% → zoom out (maxZoom = mid) 7. If 10-20% → perfect, done! 8. Iterate max 20 times or until range < 0.1 This handles all regions in the detection box, not just the one under cursor, giving better overall framing. For Gibraltar area: - Binary search will find zoom ~800-1000x where Gibraltar occupies 10-20% of magnifier - Converges in ~10-15 iterations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1a690e00b0
commit
1a54f09814
|
|
@ -870,43 +870,92 @@ export function MapRenderer({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldShow) {
|
if (shouldShow) {
|
||||||
// Calculate zoom to make the region under cursor occupy ~15% of magnifier area
|
// Binary search for optimal zoom level
|
||||||
// Magnifier area is 50% of container width × 25% height (aspect 2:1)
|
// Goal: Find zoom where regions fit nicely in magnifier (taking 10-20% of area)
|
||||||
// Target: region should occupy 15% of magnifier
|
const TARGET_AREA_MIN = 0.10 // 10% of magnifier
|
||||||
let adaptiveZoom = 10 // Default zoom
|
const TARGET_AREA_MAX = 0.20 // 20% of magnifier
|
||||||
|
|
||||||
if (regionUnderCursor && regionUnderCursorArea > 0) {
|
// Get magnifier dimensions
|
||||||
// Get magnifier dimensions in screen pixels
|
const magnifierWidth = containerRect.width * 0.5
|
||||||
const magnifierWidth = containerRect.width * 0.5
|
const magnifierHeight = magnifierWidth / 2
|
||||||
const magnifierHeight = magnifierWidth / 2
|
const magnifierArea = magnifierWidth * magnifierHeight
|
||||||
const magnifierArea = magnifierWidth * magnifierHeight
|
|
||||||
|
|
||||||
// Calculate zoom so region occupies 15% of magnifier area
|
// Get SVG viewBox for coordinate conversion
|
||||||
// regionArea * zoom^2 = magnifierArea * 0.15
|
const viewBoxParts = mapData.viewBox.split(' ').map(Number)
|
||||||
// zoom = sqrt((magnifierArea * 0.15) / regionArea)
|
const viewBoxWidth = viewBoxParts[2] || 1000
|
||||||
const targetAreaRatio = 0.15
|
const viewBoxHeight = viewBoxParts[3] || 1000
|
||||||
adaptiveZoom = Math.sqrt((magnifierArea * targetAreaRatio) / regionUnderCursorArea)
|
|
||||||
|
|
||||||
// Clamp zoom between reasonable bounds
|
// Binary search bounds
|
||||||
const preClampZoom = adaptiveZoom
|
let minZoom = 1
|
||||||
adaptiveZoom = Math.max(8, Math.min(MAX_ZOOM, adaptiveZoom))
|
let maxZoom = MAX_ZOOM
|
||||||
|
let adaptiveZoom = 10
|
||||||
|
let iterations = 0
|
||||||
|
const MAX_ITERATIONS = 20
|
||||||
|
|
||||||
// Debug logging for Gibraltar
|
while (iterations < MAX_ITERATIONS && maxZoom - minZoom > 0.1) {
|
||||||
const hasGibraltar = regionUnderCursor === 'gi'
|
iterations++
|
||||||
if (hasGibraltar) {
|
const testZoom = (minZoom + maxZoom) / 2
|
||||||
console.log(`[Zoom] 🎯 GIBRALTAR BREAKDOWN:`, {
|
|
||||||
regionArea: `${regionUnderCursorArea.toFixed(6)}px²`,
|
// Calculate magnified viewBox dimensions at this zoom
|
||||||
magnifierArea: `${magnifierArea.toFixed(0)}px²`,
|
const magnifiedViewBoxWidth = viewBoxWidth / testZoom
|
||||||
targetRatio: `${(targetAreaRatio * 100).toFixed(0)}%`,
|
const magnifiedViewBoxHeight = viewBoxHeight / testZoom
|
||||||
calculatedZoom: preClampZoom.toFixed(1),
|
const magnifiedViewBoxArea = magnifiedViewBoxWidth * magnifiedViewBoxHeight
|
||||||
finalZoom: adaptiveZoom.toFixed(1),
|
|
||||||
hitMaxZoom: preClampZoom > MAX_ZOOM,
|
// Check regions in detection box to see how they fit
|
||||||
})
|
let anyRegionFullyInside = false
|
||||||
|
let largestRegionRatio = 0
|
||||||
|
|
||||||
|
detectedRegions.forEach((regionId) => {
|
||||||
|
const region = mapData.regions.find((r) => r.id === regionId)
|
||||||
|
if (!region) return
|
||||||
|
|
||||||
|
const regionPath = svgRef.current?.querySelector(`path[data-region-id="${regionId}"]`)
|
||||||
|
if (!regionPath) return
|
||||||
|
|
||||||
|
const pathRect = regionPath.getBoundingClientRect()
|
||||||
|
const regionPixelArea = pathRect.width * pathRect.height
|
||||||
|
|
||||||
|
// Convert pixel area to viewBox area (approximate)
|
||||||
|
const scaleX = viewBoxWidth / svgRect.width
|
||||||
|
const scaleY = viewBoxHeight / svgRect.height
|
||||||
|
const regionViewBoxArea = regionPixelArea * scaleX * scaleY
|
||||||
|
|
||||||
|
// Check if region fits in magnified view
|
||||||
|
const regionRatioInMagnifier = regionViewBoxArea / magnifiedViewBoxArea
|
||||||
|
|
||||||
|
if (regionRatioInMagnifier < 1.0) {
|
||||||
|
anyRegionFullyInside = true
|
||||||
|
largestRegionRatio = Math.max(largestRegionRatio, regionRatioInMagnifier)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Binary search logic
|
||||||
|
if (!anyRegionFullyInside) {
|
||||||
|
// No regions fit - zoom out
|
||||||
|
maxZoom = testZoom
|
||||||
|
} else if (largestRegionRatio < TARGET_AREA_MIN) {
|
||||||
|
// Regions too small - zoom in
|
||||||
|
minZoom = testZoom
|
||||||
|
} else if (largestRegionRatio > TARGET_AREA_MAX) {
|
||||||
|
// Regions too large - zoom out
|
||||||
|
maxZoom = testZoom
|
||||||
|
} else {
|
||||||
|
// Just right!
|
||||||
|
adaptiveZoom = testZoom
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// No region under cursor - use density-based zoom
|
adaptiveZoom = testZoom
|
||||||
const countFactor = Math.min(regionsInBox / 10, 1)
|
}
|
||||||
adaptiveZoom = 10 + countFactor * 20
|
|
||||||
|
// Debug logging for Gibraltar
|
||||||
|
const hasGibraltar = detectedRegions.includes('gi')
|
||||||
|
if (hasGibraltar) {
|
||||||
|
console.log(`[Zoom] 🎯 BINARY SEARCH RESULT:`, {
|
||||||
|
iterations,
|
||||||
|
finalZoom: adaptiveZoom.toFixed(1),
|
||||||
|
detectedRegions,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate magnifier position (opposite corner from cursor)
|
// Calculate magnifier position (opposite corner from cursor)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue