Innendaemmung_calculator/wall_visualization.html
2025-04-01 08:40:08 +00:00

490 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wall Board Coverage Visualization</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f0f0f0;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.wall-section {
margin-bottom: 40px;
padding: 20px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.canvas-container {
position: relative;
margin-top: 20px;
}
canvas {
background-color: white;
border: 1px solid #ccc;
}
#boardInfo {
position: absolute;
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
display: none;
}
.legend {
margin-top: 20px;
display: flex;
gap: 20px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.legend-color {
width: 20px;
height: 20px;
border: 1px solid #000;
}
.dimensions {
margin-top: 20px;
font-size: 14px;
}
.stats {
margin-top: 20px;
padding: 10px;
background-color: #f8f8f8;
border: 1px solid #ccc;
border-radius: 4px;
}
.total-summary {
margin-top: 40px;
padding: 20px;
background-color: #e8f5e9;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h2 {
margin-top: 0;
color: #2e7d32;
}
</style>
</head>
<body>
<div class="container">
<h1>Wall Board Coverage Visualization</h1>
<div id="wallSections"></div>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #4CAF50;"></div>
<span>Full Boards</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #FFA726;"></div>
<span>Cut Boards</span>
</div>
</div>
<div class="total-summary" id="totalSummary"></div>
<div id="boardInfo"></div>
</div>
<script src="visualization_data.js"></script>
<script>
console.log('Starting visualization script...');
// Add error handling and debugging for data
if (typeof wallsData === 'undefined') {
console.error('Visualization data not loaded!');
document.getElementById('wallSections').innerHTML =
'<div style="color: red; padding: 20px;">Error: Visualization data not loaded. Please run the Python script first.</div>';
} else {
console.log('Loaded wall data:', wallsData);
console.log('Number of walls:', wallsData.length);
const PADDING = 40;
const SCALE_FACTOR = 100; // pixels per meter
function drawWall(ctx, wall) {
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
if (wall.shape === 'rectangular') {
ctx.strokeRect(
PADDING,
PADDING,
wall.width * SCALE_FACTOR,
wall.height * SCALE_FACTOR
);
} else if (wall.shape === 'triangular') {
const baseWidth = wall.width * SCALE_FACTOR;
const height = wall.height * SCALE_FACTOR;
ctx.beginPath();
ctx.moveTo(PADDING, PADDING + height); // Bottom left
ctx.lineTo(PADDING + baseWidth, PADDING + height); // Bottom right
ctx.lineTo(PADDING + (baseWidth / 2), PADDING); // Top middle
ctx.closePath();
ctx.stroke();
}
}
function drawWindows(ctx, wall) {
if (!wall.windows || wall.windows.length === 0) return;
// Save the current context state
ctx.save();
// Set window style
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.fillStyle = 'rgba(135, 206, 235, 0.5)'; // Light blue with 50% transparency
// Draw each window
wall.windows.forEach(window => {
const x = PADDING + (window.left * SCALE_FACTOR);
const y = PADDING + ((wall.height - window.bottom - window.height) * SCALE_FACTOR);
const width = window.width * SCALE_FACTOR;
const height = window.height * SCALE_FACTOR;
// Draw window with semi-transparent fill
ctx.fillRect(x, y, width, height);
ctx.strokeRect(x, y, width, height);
});
// Restore the context state
ctx.restore();
}
function isPointInTriangle(x, y, wall) {
if (wall.shape !== 'triangular') return true;
const baseWidth = wall.width * SCALE_FACTOR;
const height = wall.height * SCALE_FACTOR;
const topX = PADDING + (baseWidth / 2);
const topY = PADDING;
const bottomLeftX = PADDING;
const bottomLeftY = PADDING + height;
const bottomRightX = PADDING + baseWidth;
const bottomRightY = PADDING + height;
// Calculate barycentric coordinates
const denominator = (bottomLeftY - bottomRightY) * (topX - bottomRightX) +
(bottomRightX - bottomLeftX) * (topY - bottomRightY);
const a = ((bottomLeftY - bottomRightY) * (x - bottomRightX) +
(bottomRightX - bottomLeftX) * (y - bottomRightY)) / denominator;
const b = ((bottomRightY - topY) * (x - bottomRightX) +
(topX - bottomRightX) * (y - bottomRightY)) / denominator;
const c = 1 - a - b;
return a >= 0 && a <= 1 && b >= 0 && b <= 1 && c >= 0 && c <= 1;
}
function drawBoards(ctx, wallData) {
wallData.boardRects = [];
let cutBoardsInfo = []; // Store info about cut boards for later
// For triangular walls, create clipping path once
if (wallData.wall.shape === 'triangular') {
ctx.save();
ctx.beginPath();
const baseWidth = wallData.wall.width * SCALE_FACTOR;
const wallHeight = wallData.wall.height * SCALE_FACTOR;
ctx.moveTo(PADDING, PADDING + wallHeight);
ctx.lineTo(PADDING + baseWidth, PADDING + wallHeight);
ctx.lineTo(PADDING + (baseWidth / 2), PADDING);
ctx.closePath();
ctx.clip();
}
// Draw all boards
wallData.boards.forEach((board, index) => {
ctx.fillStyle = board.is_cut ? '#FFA726' : '#4CAF50';
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
const x = PADDING + (board.x * SCALE_FACTOR);
const y = PADDING + ((wallData.wall.height - board.y - board.height) * SCALE_FACTOR);
const width = board.width * SCALE_FACTOR;
const height = board.height * SCALE_FACTOR;
// Store board position and dimensions for hover detection
wallData.boardRects.push({
x, y, width, height,
boardData: {
width: board.width,
height: board.height,
is_cut: board.is_cut,
y_position: board.y,
x_position: board.x
}
});
// Draw board
ctx.fillRect(x, y, width, height);
ctx.strokeRect(x, y, width, height);
// Store cut board info for later drawing
if (board.is_cut) {
cutBoardsInfo.push({
x, y, width, height,
board: board,
wallData: wallData
});
}
// Draw dimensions if space allows
if (isPointInTriangle(x + width/2, y + height/2, wallData.wall)) {
ctx.fillStyle = '#000';
ctx.font = '10px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Format dimensions
const widthText = board.width.toFixed(2) + 'm';
const heightText = board.height.toFixed(2) + 'm';
// Draw width dimension if board is wide enough
if (width > 40) {
ctx.fillText(widthText, x + width/2, y + height/2);
}
// Draw height dimension if board is tall enough and board.x matches the calculated offset
if (height > 30 &&
(wallData.wall.shape === 'rectangular' ? board.x === 0 :
Math.abs(board.x - (board.y / wallData.wall.height) * (wallData.wall.width / 2)) < 0.01)) {
ctx.save();
ctx.translate(x - 5, y + height/2);
ctx.rotate(-Math.PI/2);
ctx.fillText(heightText, 0, 0);
ctx.restore();
}
}
});
// Restore context if we used clipping
if (wallData.wall.shape === 'triangular') {
ctx.restore();
}
// Draw dotted lines for cut boards after clipping is restored
cutBoardsInfo.forEach(info => {
ctx.save();
ctx.strokeStyle = '#666';
ctx.setLineDash([5, 5]); // Create dotted line pattern
ctx.beginPath();
// Draw vertical dotted line at the end of the cut board
if (info.board.width < info.wallData.board.width) {
const fullWidth = info.wallData.board.width * SCALE_FACTOR;
ctx.moveTo(info.x + info.width, info.y);
ctx.lineTo(info.x + fullWidth, info.y);
ctx.moveTo(info.x + info.width, info.y + info.height);
ctx.lineTo(info.x + fullWidth, info.y + info.height);
ctx.moveTo(info.x + fullWidth, info.y);
ctx.lineTo(info.x + fullWidth, info.y + info.height);
}
// Draw horizontal dotted line at the top of cut board for height cuts
if (info.board.height < info.wallData.board.height) {
const fullHeight = info.wallData.board.height * SCALE_FACTOR;
ctx.moveTo(info.x, info.y);
ctx.lineTo(info.x, info.y - (fullHeight - info.height));
ctx.moveTo(info.x + info.width, info.y);
ctx.lineTo(info.x + info.width, info.y - (fullHeight - info.height));
ctx.moveTo(info.x, info.y - (fullHeight - info.height));
ctx.lineTo(info.x + info.width, info.y - (fullHeight - info.height));
}
ctx.stroke();
ctx.restore();
});
}
function createWallSection(wallData, index) {
const section = document.createElement('div');
section.className = 'wall-section';
// Create wall title
const title = document.createElement('h2');
title.textContent = wallData.wall.name;
section.appendChild(title);
// Create dimensions div
const dimensions = document.createElement('div');
dimensions.className = 'dimensions';
dimensions.id = `dimensions-${index}`;
section.appendChild(dimensions);
// Create stats div
const stats = document.createElement('div');
stats.className = 'stats';
section.appendChild(stats);
// Create canvas container
const canvasContainer = document.createElement('div');
canvasContainer.className = 'canvas-container';
section.appendChild(canvasContainer);
// Create canvas element
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
canvasContainer.appendChild(canvas);
// Add mouse event listeners for board info
const boardInfo = document.getElementById('boardInfo');
canvas.addEventListener('mousemove', (event) => {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// Check if mouse is over any board
let found = false;
for (const boardRect of wallData.boardRects) {
if (x >= boardRect.x && x <= boardRect.x + boardRect.width &&
y >= boardRect.y && y <= boardRect.y + boardRect.height) {
const data = boardRect.boardData;
boardInfo.style.display = 'block';
boardInfo.style.left = (event.pageX + 10) + 'px';
boardInfo.style.top = (event.pageY + 10) + 'px';
boardInfo.innerHTML = `
Position: (${data.x_position.toFixed(2)}m, ${data.y_position.toFixed(2)}m)<br>
Size: ${data.width.toFixed(2)}m × ${data.height.toFixed(2)}m<br>
${data.is_cut ? 'Cut board' : 'Full board'}
`;
found = true;
break;
}
}
if (!found) {
boardInfo.style.display = 'none';
}
});
canvas.addEventListener('mouseleave', () => {
boardInfo.style.display = 'none';
});
// Get the canvas context
const ctx = canvas.getContext('2d');
// Draw wall
drawWall(ctx, wallData.wall);
// Draw boards
drawBoards(ctx, wallData);
// Draw windows (moved after boards)
drawWindows(ctx, wallData.wall);
// Add dimensions text first
let windowsInfo = '';
if (wallData.wall.windows && wallData.wall.windows.length > 0) {
windowsInfo = '<br>Windows:<br>' + wallData.wall.windows.map((w, i) =>
`Window ${i + 1}: Position (${w.left}m from left, ${w.bottom}m from bottom), Size: ${w.width}m × ${w.height}m`
).join('<br>');
}
dimensions.innerHTML = `
Wall dimensions: ${wallData.wall.width}m × ${wallData.wall.height}m<br>
Wall shape: ${wallData.wall.shape}<br>
Board dimensions: ${wallData.board.width}m × ${wallData.board.height}m
${windowsInfo}
`;
// Draw dimension lines and labels
ctx.strokeStyle = '#666';
ctx.fillStyle = '#666';
ctx.lineWidth = 1;
ctx.font = '12px Arial';
ctx.textAlign = 'center';
// Width dimension
const y = wallData.wall.height * SCALE_FACTOR + PADDING + 20;
ctx.beginPath();
ctx.moveTo(PADDING, y);
ctx.lineTo(PADDING + wallData.wall.width * SCALE_FACTOR, y);
ctx.stroke();
ctx.fillText(
`${wallData.wall.width}m`,
PADDING + (wallData.wall.width * SCALE_FACTOR) / 2,
y + 15
);
// Height dimension
const x = PADDING - 20;
ctx.textAlign = 'right';
ctx.save();
ctx.translate(x, PADDING + (wallData.wall.height * SCALE_FACTOR) / 2);
ctx.rotate(-Math.PI / 2);
ctx.fillText(`${wallData.wall.height}m`, 0, 0);
ctx.restore();
// Add stats to the stats div
stats.innerHTML = `
Total boards: ${wallData.boards.length}<br>
Full boards: ${wallData.boards.filter(b => !b.is_cut).length}<br>
Cut boards: ${wallData.boards.filter(b => b.is_cut).length}
`;
return section;
}
function renderWallSections() {
console.log('Starting renderWallSections...');
const wallSections = document.getElementById('wallSections');
if (!wallSections) {
console.error('Could not find wallSections element!');
return;
}
wallSections.innerHTML = '';
// Calculate totals across all walls
console.log('Calculating totals...');
const totalBoards = wallsData.reduce((sum, wall) => sum + wall.boards.length, 0);
const totalFullBoards = wallsData.reduce((sum, wall) => sum + wall.boards.filter(b => !b.is_cut).length, 0);
const totalCutBoards = wallsData.reduce((sum, wall) => sum + wall.boards.filter(b => b.is_cut).length, 0);
console.log('Totals calculated:', { totalBoards, totalFullBoards, totalCutBoards });
// Render each wall section
console.log('Rendering wall sections...');
wallsData.forEach((wallData, index) => {
console.log(`Creating wall section ${index}:`, wallData.wall.name);
const section = createWallSection(wallData, index);
wallSections.appendChild(section);
});
// Update total summary
console.log('Updating total summary...');
const totalSummary = document.getElementById('totalSummary');
if (!totalSummary) {
console.error('Could not find totalSummary element!');
return;
}
totalSummary.innerHTML = `
<h2>Total Summary</h2>
Total boards across all walls: ${totalBoards}<br>
Full boards: ${totalFullBoards}<br>
Cut boards: ${totalCutBoards}
`;
console.log('Rendering complete.');
}
// Only render if we have data
if (typeof wallsData !== 'undefined') {
console.log('Starting render with data...');
renderWallSections();
}
}
</script>
</body>
</html>