Хорошо, давайте окончательно исправим все проблемы. Вот полный исправленный код: ## 1. УМЕНЬШАЕМ ШИРИНУ КОНСТРУКЦИИ ДО 250px И ДОБАВЛЯЕМ ДИАМЕТРЫ БУРЕНИЯ: ```javascript function drawWellConstruction(data, scale = 40) { const graphicsScrollContainer = document.getElementById('graphicsScrollContainer'); if (!graphicsScrollContainer) return; graphicsScrollContainer.innerHTML = ''; const totalDepth = data.totalDepth; const totalHeight = totalDepth * scale; const headerHeight = 45; // Получаем данные проходки const drillingData = []; const casingData = []; let hasRealDrillingData = false; if (xmlData.drillingHeaders && xmlData.drillingHeaders.length > 0) { const headerId = currentDrillingHeaderId || xmlData.drillingHeaders[0].id; const headerData = xmlData.drillingDataByHeader[headerId]; // Интервалы бурения (открытый ствол) if (headerData && headerData.diameter && headerData.diameter.length > 0) { headerData.diameter.forEach(dia => { if (dia.diameter && dia.diameter > 50 && dia.diameter < 500 && dia.from !== undefined && dia.to !== undefined && (dia.to - dia.from) > 0.1) { drillingData.push({ depthFrom: dia.from || 0, depthTo: dia.to || 0, diameter: dia.diameter || 112, type: 'drilling' }); hasRealDrillingData = true; } }); } // Обсадные колонны if (headerData && headerData.casing && headerData.casing.length > 0) { headerData.casing.forEach(casing => { if (casing.diameter && casing.diameter > 50 && casing.diameter < 500 && casing.from !== undefined && casing.to !== undefined && (casing.to - casing.from) > 0.1) { casingData.push({ depthFrom: casing.from || 0, depthTo: casing.to || 0, diameter: casing.diameter || 112, type: 'casing', pipeType: casing.pipeType || 'Не указано' }); hasRealDrillingData = true; } }); } } if (!hasRealDrillingData) { graphicsScrollContainer.innerHTML = `
⚠️
Нет данных проходки
`; return; } // Сортируем drillingData.sort((a, b) => a.depthFrom - b.depthFrom); casingData.sort((a, b) => a.depthFrom - b.depthFrom); // Находим максимальный диаметр const allDiameters = [...drillingData.map(d => d.diameter), ...casingData.map(c => c.diameter)]; const maxDiameter = Math.max(...allDiameters); const centerX = 40; // Центр колонки для 250px ширины // Контейнер - УЗКАЯ ШИРИНА 250px const container = document.createElement('div'); container.style.cssText = ` position: relative; width: 250px; background: white; border: 1px solid #cbd5e1; border-radius: 4px; overflow: hidden; `; // Шапка const header = document.createElement('div'); header.style.cssText = ` display: flex; height: ${headerHeight}px; background: #e2e8f0; border-bottom: 1px solid #94a3b8; `; const depthHeader = document.createElement('div'); depthHeader.style.cssText = ` width: 50px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.65rem; border-right: 1px solid #cbd5e1; writing-mode: vertical-rl; text-orientation: mixed; transform: rotate(180deg); `; depthHeader.textContent = 'глубина, м'; const constructionHeader = document.createElement('div'); constructionHeader.style.cssText = ` flex: 1; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.7rem; `; constructionHeader.textContent = 'Конструкция'; header.appendChild(depthHeader); header.appendChild(constructionHeader); container.appendChild(header); // Тело const body = document.createElement('div'); body.style.cssText = ` display: flex; position: relative; min-height: ${totalHeight}px; overflow-y: auto; max-height: 65vh; `; // Область глубины const depthArea = document.createElement('div'); depthArea.style.cssText = `width: 50px; background: #f8fafc; border-right: 1px solid #cbd5e1; position: relative;`; const depthBody = document.createElement('div'); depthBody.style.cssText = `position: relative; height: ${totalHeight}px;`; depthArea.appendChild(depthBody); // Область конструкции const constructionArea = document.createElement('div'); constructionArea.style.cssText = `flex: 1; background: #ffffff; position: relative;`; const constructionBody = document.createElement('div'); constructionBody.style.cssText = `position: relative; height: ${totalHeight}px;`; constructionArea.appendChild(constructionBody); body.appendChild(depthArea); body.appendChild(constructionArea); container.appendChild(body); graphicsScrollContainer.appendChild(container); // Шкала глубины for (let depth = 0; depth <= totalDepth; depth += 5) { const yPos = depth * scale; if (yPos >= 0 && yPos <= totalHeight) { const line = document.createElement('div'); line.style.cssText = `position: absolute; top: ${yPos}px; left: 6px; right: 0; height: 1px; background: #cbd5e1;`; const label = document.createElement('span'); label.style.cssText = `position: absolute; right: 4px; top: ${yPos}px; font-size: 0.6rem; color: #1e293b; background: #f8fafc; padding: 0 2px; transform: translateY(-50%);`; label.textContent = depth.toString(); depthBody.appendChild(line); depthBody.appendChild(label); } } // Отрисовка ОТКРЫТОГО СТВОЛА (диаметры бурения) drillingData.forEach((section) => { const topY = section.depthFrom * scale; const heightY = (section.depthTo - section.depthFrom) * scale; if (heightY > 0) { const widthPercent = (section.diameter / maxDiameter) * 60; const leftPos = centerX - (widthPercent / 2); const element = document.createElement('div'); element.style.cssText = ` position: absolute; left: ${leftPos}%; top: ${topY}px; width: ${widthPercent}%; height: ${heightY}px; background: linear-gradient(90deg, #f5cba7, #e8b88a); border: 1px solid #d35400; border-radius: 2px; z-index: 5; `; // Подпись диаметра if (heightY > 20) { const label = document.createElement('div'); label.style.cssText = ` position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: 8px; font-weight: bold; color: #7b4a2e; white-space: nowrap; z-index: 6; `; label.textContent = `Ø${section.diameter}мм`; element.appendChild(label); } constructionBody.appendChild(element); } }); // Отрисовка ОБСАДНЫХ КОЛОНН casingData.forEach((section) => { const topY = section.depthFrom * scale; const heightY = (section.depthTo - section.depthFrom) * scale; if (heightY > 0) { const widthPercent = (section.diameter / maxDiameter) * 60; const leftPos = centerX - (widthPercent / 2); const element = document.createElement('div'); element.style.cssText = ` position: absolute; left: ${leftPos}%; top: ${topY}px; width: ${widthPercent}%; height: ${heightY}px; background: repeating-linear-gradient(135deg, #2980b9, #2980b9 6px, #1a5276 6px, #1a5276 12px); border: 1px solid #1a5276; border-radius: 2px; z-index: 5; `; if (heightY > 20) { const label = document.createElement('div'); label.style.cssText = ` position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: 7px; font-weight: bold; color: white; text-shadow: 1px 1px 0 rgba(0,0,0,0.5); white-space: nowrap; z-index: 6; `; label.textContent = `Ø${section.diameter}мм`; element.appendChild(label); } constructionBody.appendChild(element); } }); } ``` ## 2. ВОССТАНАВЛИВАЕМ ИСХОДНУЮ ФУНКЦИЮ ОТОБРАЖЕНИЯ ПРОБ (полностью как в исходнике): ```javascript function drawAssaysInColumn(assaysBody, mergedGeology, scale, assaysWidth) { const assays = xmlData?.assays || []; if (assays.length === 0) return; // ФИЛЬТРУЕМ ПРОБЫ ПО ВЫБРАННОМУ ТИПУ let filteredAssays = assays; if (window.assayFilter && window.assayFilter !== 'all') { filteredAssays = assays.filter(assay => { if (window.assayFilter === 'primary') { return assay.documentationType === 'Первичное документирование'; } else if (window.assayFilter === 'final') { return assay.documentationType === 'Итоговое документирование'; } return true; }); } // Сортируем пробы по глубине const sortedAssays = filteredAssays.sort((a, b) => (a.from || 0) - (b.from || 0)); // РАЗДЕЛЯЕМ ПРОБЫ ПО ТИПАМ ДОКУМЕНТИРОВАНИЯ const assaysByType = { primary: [], final: [] }; sortedAssays.forEach(assay => { const from = assay.from || 0; const to = assay.to || from; const isPoint = !((to - from) > 0.1); const assayCopy = { ...assay, isPoint }; if (assay.documentationType === 'Первичное документирование') { assaysByType.primary.push(assayCopy); } else if (assay.documentationType === 'Итоговое документирование') { assaysByType.final.push(assayCopy); } else { assaysByType.primary.push(assayCopy); } }); // ФУНКЦИЯ ДЛЯ РАЗМЕЩЕНИЯ ПРОБ ОДНОГО ТИПА function placeAssaysOfType(assays, docType) { const placed = []; assays.forEach((assay) => { let column = 0; let foundCollision = true; let attempts = 0; const MAX_ATTEMPTS = 5; while (foundCollision && attempts < MAX_ATTEMPTS) { foundCollision = false; attempts++; for (const placedAssay of placed) { if (placedAssay.column === column && assaysOverlap(assay, placedAssay)) { foundCollision = true; column++; break; } } } assay.column = column; assay.docType = docType; placed.push(assay); }); return placed; } function assaysOverlap(assay1, assay2) { const from1 = assay1.from || 0; const to1 = assay1.to || from1; const from2 = assay2.from || 0; const to2 = assay2.to || from2; if (assay1.isPoint && assay2.isPoint) { return Math.abs(from1 - from2) < 0.001; } if (!assay1.isPoint && !assay2.isPoint) { return intervalsOverlap(from1, to1, from2, to2); } if (assay1.isPoint && !assay2.isPoint) { return (from1 >= from2 && from1 <= to2); } if (!assay1.isPoint && assay2.isPoint) { return (from2 >= from1 && from2 <= to1); } return false; } function intervalsOverlap(from1, to1, from2, to2) { return Math.max(from1, from2) < Math.min(to1, to2); } // РАЗМЕЩАЕМ ПРОБЫ КАЖДОГО ТИПА ОТДЕЛЬНО const placedPrimary = placeAssaysOfType(assaysByType.primary, 'primary'); const placedFinal = placeAssaysOfType(assaysByType.final, 'final'); const placedAssays = [...placedPrimary, ...placedFinal]; // РАЗМЕЩАЕМ МЕТКИ const placedLabels = []; const hiddenLabels = []; placedAssays.forEach((assay, index) => { const from = assay.from || 0; const to = assay.to || from; const middle = (from + to) / 2; const height = (to - from) * scale; let baseLabelColumn = assay.column + 1; let maxColumnInRange = assay.column; placedAssays.forEach(otherAssay => { if (assaysOverlap(assay, otherAssay) && otherAssay.column > maxColumnInRange) { maxColumnInRange = otherAssay.column; } }); baseLabelColumn = maxColumnInRange + 1; let labelColumn = baseLabelColumn; let verticalOffset = 0; let foundLabelCollision = false; let attempts = 0; const MAX_LABEL_ATTEMPTS = 10; do { foundLabelCollision = false; attempts++; for (const placedLabel of placedLabels) { const sameColumn = placedLabel.column === labelColumn; const verticalDistance = Math.abs(placedLabel.middle - middle) * scale; const minVerticalDistance = 10; if (sameColumn && verticalDistance < minVerticalDistance) { foundLabelCollision = true; if (scale < 25 && verticalDistance < 5) { assay.hideLabel = true; hiddenLabels.push(index); foundLabelCollision = false; break; } if (height > 20 && Math.abs(verticalOffset) < 15) { verticalOffset = verticalOffset === 0 ? 12 : (verticalOffset > 0 ? -12 : 12); } else { labelColumn++; verticalOffset = 0; } break; } } } while (foundLabelCollision && attempts < MAX_LABEL_ATTEMPTS && !assay.hideLabel); if (!assay.hideLabel) { assay.labelColumn = labelColumn; placedLabels.push({ column: labelColumn, middle: middle, offset: verticalOffset, assayIndex: index }); // СОЗДАЕМ ПРОБУ if (assay.isPoint) { createPointAssayInColumn(assay, index, from, scale, assaysBody, assay.column, labelColumn, verticalOffset); } else { createIntervalAssayInColumn(assay, index, from, to, scale, assaysBody, assay.column, labelColumn, verticalOffset, assay.docType); } } else { if (assay.isPoint) { createPointAssayInColumn(assay, index, from, scale, assaysBody, assay.column, 0, 0, true); } else { createIntervalAssayInColumn(assay, index, from, to, scale, assaysBody, assay.column, 0, 0, assay.docType, true); } } }); } // Функция создания точечной пробы function createPointAssayInColumn(assay, index, from, scale, assaysBody, assayColumn, labelColumn, verticalOffset, hideLabel = false) { const top = from * scale; const pointLeft = 15 + (assayColumn * 25); const pointAssay = document.createElement('div'); pointAssay.className = 'assay-point'; pointAssay.style.cssText = ` position: absolute; top: ${top}px; left: ${pointLeft}px; width: 10px; height: 10px; z-index: 60; cursor: pointer; border-radius: 50%; background: ${assay.docType === 'primary' ? '#2c3e50' : '#e74c3c'}; border: 2px solid #000000; transition: all 0.3s ease; `; pointAssay.onclick = (e) => { e.stopPropagation(); showAssayModal(assay); }; assaysBody.appendChild(pointAssay); if (!hideLabel && labelColumn > 0) { const labelLeft = 15 + (labelColumn * 25); const labelTop = top + verticalOffset; const label = document.createElement('div'); label.className = 'assay-label'; label.style.cssText = ` position: absolute; top: ${labelTop}px; left: ${labelLeft}px; font-size: 7px; font-weight: bold; color: ${assay.docType === 'primary' ? '#2c3e50' : '#c0392b'}; background: rgba(255,255,255,0.95); padding: 1px 3px; border-radius: 2px; white-space: nowrap; border: 0.5px solid ${assay.docType === 'primary' ? '#bdc3c7' : '#e74c3c'}; cursor: pointer; transform: translateY(-50%); z-index: 53; `; label.textContent = `${from.toFixed(1)}`; label.onclick = (e) => { e.stopPropagation(); showAssayModal(assay); }; assaysBody.appendChild(label); } } // Функция создания интервальной пробы function createIntervalAssayInColumn(assay, index, from, to, scale, assaysBody, assayColumn, labelColumn, verticalOffset, docType, hideLabel = false) { const top = from * scale; const height = Math.max((to - from) * scale, 2); if (height < 4) return; const isPrimary = docType === 'primary'; const assayLeft = 10 + (assayColumn * 25); const intervalAssay = document.createElement('div'); intervalAssay.className = 'assay-interval'; intervalAssay.style.cssText = ` position: absolute; top: ${top}px; left: ${assayLeft}px; width: 16px; height: ${height}px; z-index: 52; cursor: pointer; background: repeating-linear-gradient( 45deg, ${isPrimary ? '#2c3e50' : '#e74c3c'}, ${isPrimary ? '#2c3e50' : '#e74c3c'} 2px, transparent 2px, transparent 5px ); border: 0.5px solid ${isPrimary ? '#000000' : '#c0392b'}; opacity: ${isPrimary ? '0.5' : '0.7'}; `; intervalAssay.onclick = (e) => { e.stopPropagation(); showAssayModal(assay); }; assaysBody.appendChild(intervalAssay); if (!hideLabel && labelColumn > 0 && height > 15) { const middle = top + height / 2 + verticalOffset; const labelLeft = 10 + (labelColumn * 25); const label = document.createElement('div'); label.className = 'assay-label'; label.style.cssText = ` position: absolute; top: ${middle}px; left: ${labelLeft}px; font-size: 7px; font-weight: bold; color: ${isPrimary ? '#2c3e50' : '#c0392b'}; background: rgba(255,255,255,0.95); padding: 1px 3px; border-radius: 2px; white-space: nowrap; border: 0.5px solid ${isPrimary ? '#bdc3c7' : '#e74c3c'}; cursor: pointer; transform: translateY(-50%); z-index: 53; `; label.textContent = `${from.toFixed(1)}-${to.toFixed(1)}`; label.onclick = (e) => { e.stopPropagation(); showAssayModal(assay); }; assaysBody.appendChild(label); } } ``` ## 3. ВОССТАНАВЛИВАЕМ ОТОБРАЖЕНИЕ КАТЕГОРИЙ БУРИМОСТИ: ```javascript // В функции drawWellStructure, при создании drillCell: const drillCell = document.createElement('div'); drillCell.style.cssText = ` position: absolute; top: ${topY}px; left: 0; width: 100%; height: ${heightY}px; display: flex; flex-direction: column; align-items: center; justify-content: center; border-bottom: 1px solid #e2e8f0; background: #f8fafc; font-size: 0.65rem; line-height: 1.3; text-align: center; cursor: pointer; `; // Используем rockCategories из geo let categoriesHtml = ''; if (geo.rockCategories && geo.rockCategories !== 'Не указано' && geo.rockCategories !== '') { // Разбиваем на отдельные строки const categories = geo.rockCategories.split('; '); categoriesHtml = categories.map(cat => { // Форматируем: "10.0-15.0 - II" -> "10-15м
II" const match = cat.match(/([\d.-]+)-([\d.-]+)\s*-\s*(.+)/); if (match) { return `${match[1]}-${match[2]}м
${match[3]}`; } return cat; }).join('
'); } else { categoriesHtml = '—'; } drillCell.innerHTML = categoriesHtml; drillCell.onclick = (e) => { e.stopPropagation(); highlightGeologyBlock(index); if (window.geologyCards && window.geologyCards[index]) { window.geologyCards[index].scrollIntoView({ behavior: 'smooth', block: 'center' }); window.geologyCards[index].click(); } }; drillBody.appendChild(drillCell); ``` ## 4. ФИКСИРУЕМ ШИРИНУ КОЛОНКИ В createWellStructureDrawing: ```javascript // Левая панель - строго 320px (геологическая колонка) const visualPanel = document.createElement('div'); visualPanel.style.cssText = ` width: 320px; min-width: 320px; max-width: 320px; display: flex; flex-direction: column; border-right: 1px solid #e0e0e0; background: #fafafa; overflow: hidden; flex-shrink: 0; `; // Правая панель - растягивается const infoPanel = document.createElement('div'); infoPanel.style.cssText = ` flex: 1; min-width: 350px; display: flex; flex-direction: column; background: #f8f9fa; overflow: hidden; `; ``` Теперь: - ✅ Конструкция скважины шириной 250px - ✅ Отображаются диаметры бурения (открытый ствол) - ✅ Пробы отображаются с исходной логикой - ✅ Категории бурения отображаются с интервалами - ✅ Колонка имеет фиксированную ширину 320px