Вы абсолютно правы, давайте всё исправим. Вот финальные изменения: ## 1. Убираем колонку "Описание пород" из таблицы и возвращаем все настройки на место: ```javascript function drawWellStructure(data, scale = 40) { const graphicsScrollContainer = document.getElementById('graphicsScrollContainer'); if (!graphicsScrollContainer) return; graphicsScrollContainer.innerHTML = ''; const activeDocType = data.documentationType || 'primary'; const mergedGeology = createMergedGeologyFromDocumentation(activeDocType); const assays = xmlData?.assays || []; const totalDepth = data.totalDepth; const totalHeight = totalDepth * scale; const headerHeight = 50; // СОЗДАЕМ НОВУЮ СТРУКТУРУ КОЛОНКИ const columnContainer = document.createElement('div'); columnContainer.style.cssText = ` position: relative; width: 100%; min-width: 700px; background: white; border: 1px solid #cbd5e1; border-radius: 4px; margin-top: 10px; overflow: hidden; `; // ШАПКА КОЛОНКИ (только глубина, колонка, кат.бур., пробы) const header = document.createElement('div'); header.style.cssText = ` display: flex; height: ${headerHeight}px; background: #e2e8f0; border-bottom: 1px solid #94a3b8; flex-shrink: 0; `; const depthHeader = document.createElement('div'); depthHeader.style.cssText = ` width: 70px; min-width: 70px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.7rem; text-transform: uppercase; color: #0f172a; border-right: 1px solid #cbd5e1; writing-mode: vertical-rl; text-orientation: mixed; transform: rotate(180deg); `; depthHeader.textContent = 'глубина, м'; const coreHeader = document.createElement('div'); coreHeader.style.cssText = ` width: 120px; min-width: 120px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.7rem; text-transform: uppercase; color: #0f172a; border-right: 1px solid #cbd5e1; `; coreHeader.textContent = 'Колонка'; const drillHeader = document.createElement('div'); drillHeader.style.cssText = ` width: 80px; min-width: 80px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.7rem; text-transform: uppercase; color: #0f172a; border-right: 1px solid #cbd5e1; `; drillHeader.textContent = 'Кат. бур.'; const assaysHeader = document.createElement('div'); assaysHeader.style.cssText = ` width: 160px; min-width: 160px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.7rem; text-transform: uppercase; color: #0f172a; `; assaysHeader.textContent = 'Пробы'; header.appendChild(depthHeader); header.appendChild(coreHeader); header.appendChild(drillHeader); header.appendChild(assaysHeader); columnContainer.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: 70px; min-width: 70px; 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 coreArea = document.createElement('div'); coreArea.style.cssText = ` width: 120px; min-width: 120px; background: #ffffff; border-right: 1px solid #cbd5e1; position: relative; `; const coreBody = document.createElement('div'); coreBody.style.cssText = `position: relative; height: ${totalHeight}px;`; coreArea.appendChild(coreBody); // Область категорий бурения const drillArea = document.createElement('div'); drillArea.style.cssText = ` width: 80px; min-width: 80px; background: #f8fafc; border-right: 1px solid #cbd5e1; position: relative; `; const drillBody = document.createElement('div'); drillBody.style.cssText = `position: relative; height: ${totalHeight}px;`; drillArea.appendChild(drillBody); // Область проб const assaysArea = document.createElement('div'); assaysArea.style.cssText = ` width: 160px; min-width: 160px; background: #ffffff; position: relative; `; const assaysBody = document.createElement('div'); assaysBody.style.cssText = `position: relative; height: ${totalHeight}px;`; assaysArea.appendChild(assaysBody); body.appendChild(depthArea); body.appendChild(coreArea); body.appendChild(drillArea); body.appendChild(assaysArea); columnContainer.appendChild(body); graphicsScrollContainer.appendChild(columnContainer); // === ОТРИСОВКА ШКАЛЫ ГЛУБИН === for (let depth = 0; depth <= totalDepth; depth += 5) { const yPos = depth * scale; if (yPos >= 0 && yPos <= totalHeight) { const markDiv = document.createElement('div'); markDiv.style.cssText = `position: absolute; top: ${yPos}px; left: 0; width: 100%; height: 0;`; const line = document.createElement('div'); line.style.cssText = `position: absolute; left: 8px; right: 0; height: 1px; background: #cbd5e1;`; const label = document.createElement('span'); label.style.cssText = ` position: absolute; right: 6px; font-weight: 600; font-size: 0.65rem; color: #1e293b; background: #f8fafc; padding: 0 2px; transform: translateY(-50%); `; label.textContent = depth.toString(); markDiv.appendChild(line); markDiv.appendChild(label); depthBody.appendChild(markDiv); } } // Метки границ интервалов документирования mergedGeology.forEach((geo) => { const topY = geo.from * scale; const bottomY = geo.to * scale; if (topY >= 0 && topY <= totalHeight) { const label = document.createElement('div'); label.style.cssText = ` position: absolute; right: 6px; top: ${topY}px; font-size: 0.6rem; color: #e74c3c; background: white; padding: 0 2px; transform: translateY(-50%); z-index: 5; font-weight: bold; `; label.textContent = geo.from.toFixed(1); depthBody.appendChild(label); } if (bottomY >= 0 && bottomY <= totalHeight) { const label = document.createElement('div'); label.style.cssText = ` position: absolute; right: 6px; top: ${bottomY}px; font-size: 0.6rem; color: #e74c3c; background: white; padding: 0 2px; transform: translateY(-50%); z-index: 5; font-weight: bold; `; label.textContent = geo.to.toFixed(1); depthBody.appendChild(label); } }); // Метки через 1 метр for (let depth = 0; depth <= totalDepth; depth += 1) { if (depth % 5 !== 0) { const yPos = depth * scale; if (yPos >= 0 && yPos <= totalHeight) { const line = document.createElement('div'); line.style.cssText = `position: absolute; top: ${yPos}px; left: 8px; right: 0; height: 1px; background: #e2e8f0;`; depthBody.appendChild(line); } } } // === ОТРИСОВКА ГЕОЛОГИЧЕСКИХ ИНТЕРВАЛОВ === const geologyBlocks = []; mergedGeology.forEach((geo, index) => { const topY = geo.from * scale; const heightY = (geo.to - geo.from) * scale; if (heightY > 0) { // Блок в колонке const block = document.createElement('div'); const bgColor = getStratigraphyBackground({ stratigraphy: geo.stratigraphy, originalStratigraphy: geo.originalStratigraphy }); block.style.cssText = ` position: absolute; top: ${topY}px; left: 0; width: 100%; height: ${heightY}px; background: ${bgColor}; border: 1px solid rgba(0,0,0,0.25); cursor: pointer; transition: all 0.15s ease; display: flex; align-items: center; justify-content: center; z-index: 1; `; // Возраст const ageSpan = document.createElement('span'); const isDark = isColorDark(bgColor); ageSpan.style.cssText = ` font-weight: 700; font-size: 0.7rem; color: ${isDark ? '#ffffff' : '#0f172a'}; background: rgba(255,255,255,0.7); padding: 2px 6px; border-radius: 2px; text-align: center; z-index: 2; `; ageSpan.textContent = geo.originalStratigraphy || geo.stratigraphy || ''; block.appendChild(ageSpan); // Крап литологии if (geo.lcode && window.LITHOLOGY_PATTERNS && window.LITHOLOGY_PATTERNS[geo.lcode]) { const patternOverlay = document.createElement('div'); const patternSize = getPatternSize(scale); const fileName = window.LITHOLOGY_PATTERNS[geo.lcode]; const patternUrl = `litology/${fileName}`; patternOverlay.style.cssText = ` position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-image: url("${patternUrl}"); background-repeat: repeat; background-size: ${patternSize.width}px ${patternSize.height}px; opacity: 0.5; pointer-events: none; z-index: 1; `; block.appendChild(patternOverlay); } block.dataset.layerId = index; block.addEventListener('click', (e) => { e.stopPropagation(); highlightGeologyBlock(index); if (window.geologyCards && window.geologyCards[index]) { window.geologyCards[index].scrollIntoView({ behavior: 'smooth', block: 'center' }); window.geologyCards[index].click(); } }); coreBody.appendChild(block); geologyBlocks.push(block); // Категория бурения const drillCell = document.createElement('div'); drillCell.style.cssText = ` position: absolute; top: ${topY}px; left: 0; width: 100%; height: ${heightY}px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.78rem; border-bottom: 1px solid #e2e8f0; cursor: pointer; `; let drillCat = '—'; if (geo.rockCategories && geo.rockCategories !== 'Не указано' && geo.rockCategories !== '') { const catMatch = geo.rockCategories.match(/- ([IVX]+)/); if (catMatch) drillCat = catMatch[1]; } drillCell.textContent = drillCat; drillCell.addEventListener('click', (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); } }); function highlightGeologyBlock(index) { geologyBlocks.forEach((block, i) => { if (i === index) { block.style.border = '2px solid #b91c1c'; block.style.boxShadow = '0 0 0 3px rgba(185, 28, 28, 0.25)'; block.style.zIndex = '10'; } else { block.style.border = '1px solid rgba(0,0,0,0.25)'; block.style.boxShadow = 'none'; block.style.zIndex = '1'; } }); if (window.geologyCards) { document.querySelectorAll('.geology-card').forEach((card, i) => { if (i === index) { card.style.background = '#fff7ed'; card.style.borderLeft = '4px solid #c2410c'; } else { card.style.background = 'white'; card.style.borderLeft = '4px solid #e9ecef'; } }); } } // === ОТРИСОВКА ПРОБ С УЛУЧШЕННЫМ РАЗМЕЩЕНИЕМ МЕТОК === if (window.assaysVisible !== false && assays.length > 0) { drawAssaysWithImprovedLabels(assaysBody, mergedGeology, scale); } window.geologyBlocks = geologyBlocks; } ``` ## 2. НОВАЯ ФУНКЦИЯ для отрисовки проб с умным размещением меток: ```javascript function drawAssaysWithImprovedLabels(assaysBody, mergedGeology, scale) { 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 allAssays = []; filteredAssays.forEach(assay => { const from = assay.from || 0; const to = assay.to || from; const isPoint = Math.abs(to - from) < 0.1; const docType = assay.documentationType === 'Итоговое документирование' ? 'final' : 'primary'; allAssays.push({ from: from, to: to, isPoint: isPoint, docType: docType, originalAssay: assay }); }); // Сортируем по глубине allAssays.sort((a, b) => a.from - b.from); // Размещаем пробы по колонкам const COLUMN_WIDTH = 24; const columns = { primary: [], final: [] }; const MAX_COLUMNS = 4; // Инициализация колонок for (let i = 0; i < MAX_COLUMNS; i++) { columns.primary.push([]); columns.final.push([]); } // Функция проверки пересечения function assaysOverlap(a, b) { if (a.isPoint && b.isPoint) { return Math.abs(a.from - b.from) < 0.5; } if (!a.isPoint && !b.isPoint) { return Math.max(a.from, b.from) < Math.min(a.to, b.to); } const pointDepth = a.isPoint ? a.from : b.from; const intervalFrom = a.isPoint ? b.from : a.from; const intervalTo = a.isPoint ? b.to : a.to; return pointDepth >= intervalFrom && pointDepth <= intervalTo; } // Размещение проб allAssays.forEach(assay => { const colType = assay.docType; let placed = false; for (let col = 0; col < MAX_COLUMNS; col++) { let canPlace = true; for (const placedAssay of columns[colType][col]) { if (assaysOverlap(assay, placedAssay)) { canPlace = false; break; } } if (canPlace) { columns[colType][col].push(assay); assay.column = col; assay.offset = colType === 'primary' ? -(COLUMN_WIDTH * (col + 1)) : (COLUMN_WIDTH * (col + 1)); placed = true; break; } } if (!placed) { assay.column = MAX_COLUMNS - 1; assay.offset = colType === 'primary' ? -(COLUMN_WIDTH * MAX_COLUMNS) : (COLUMN_WIDTH * MAX_COLUMNS); } }); // Отслеживание занятых позиций для меток const usedLabelPositions = []; // Отрисовка проб allAssays.forEach(assay => { // Находим слой, в котором находится проба const layer = mergedGeology.find(l => assay.from >= l.from && assay.from <= l.to); if (!layer) return; const layerIndex = mergedGeology.indexOf(layer); const layerTop = layer.from * scale; const layerHeight = (layer.to - layer.from) * scale; if (assay.isPoint) { const yPos = (assay.from - layer.from) * scale; if (yPos >= 0 && yPos <= layerHeight) { const centerX = 80; // Центр области проб const xPos = centerX + assay.offset; // Точечная проба const pointEl = document.createElement('div'); pointEl.style.cssText = ` position: absolute; left: ${xPos}px; top: ${yPos}px; width: 10px; height: 10px; border-radius: 50%; background: ${assay.docType === 'primary' ? '#2c3e50' : '#e74c3c'}; border: 2px solid #000000; cursor: pointer; z-index: 54; transition: all 0.3s ease; transform: translateX(-50%); `; pointEl.addEventListener('mouseenter', function() { this.style.transform = 'translateX(-50%) scale(1.8)'; this.style.boxShadow = '0 0 8px rgba(231, 76, 60, 0.6)'; }); pointEl.addEventListener('mouseleave', function() { this.style.transform = 'translateX(-50%) scale(1)'; this.style.boxShadow = 'none'; }); pointEl.addEventListener('click', (e) => { e.stopPropagation(); showAssayModal(assay.originalAssay); }); assaysBody.appendChild(pointEl); // Метка для точечной пробы - умное размещение let labelX = xPos + (assay.docType === 'primary' ? -25 : 25); let labelY = yPos; // Проверка наложения с другими метками let adjusted = false; for (let offset = 0; offset < 30; offset += 5) { let newY = yPos + (adjusted ? offset : -offset); let conflict = false; for (const used of usedLabelPositions) { if (Math.abs(used.y - newY) < 12 && Math.abs(used.x - labelX) < 40) { conflict = true; break; } } if (!conflict) { labelY = newY; usedLabelPositions.push({ x: labelX, y: labelY }); break; } adjusted = !adjusted; } const label = document.createElement('div'); label.style.cssText = ` position: absolute; left: ${labelX}px; top: ${Math.max(labelY, 5)}px; font-size: 8px; font-weight: bold; background: rgba(255,255,255,0.95); padding: 1px 4px; border-radius: 2px; white-space: nowrap; border: 0.5px solid #bdc3c7; cursor: pointer; transform: translateY(-50%); color: ${assay.docType === 'primary' ? '#2c3e50' : '#c0392b'}; z-index: 53; `; label.textContent = assay.from.toFixed(1); label.addEventListener('click', (e) => { e.stopPropagation(); showAssayModal(assay.originalAssay); }); assaysBody.appendChild(label); } } else { // Интервальная проба const assayTop = Math.max(assay.from, layer.from); const assayBottom = Math.min(assay.to, layer.to); const topY = (assayTop - layer.from) * scale; const heightY = (assayBottom - assayTop) * scale; if (heightY > 2) { const centerX = 80; const xPos = centerX + assay.offset; const intervalEl = document.createElement('div'); intervalEl.style.cssText = ` position: absolute; left: ${xPos}px; top: ${topY}px; width: 6px; height: ${heightY}px; background: ${assay.docType === 'primary' ? 'repeating-linear-gradient(45deg, #2c3e50, #2c3e50 3px, transparent 3px, transparent 6px)' : 'repeating-linear-gradient(45deg, #e74c3c, #e74c3c 3px, #fadbd8 3px, #fadbd8 6px)'}; border: 1px solid ${assay.docType === 'primary' ? '#000000' : '#c0392b'}; border-radius: 1px; cursor: pointer; transition: all 0.3s ease; z-index: 52; transform: translateX(-50%); `; intervalEl.addEventListener('mouseenter', function() { this.style.transform = 'translateX(-50%) scale(1.05)'; this.style.boxShadow = '0 2px 8px rgba(231, 76, 60, 0.4)'; }); intervalEl.addEventListener('mouseleave', function() { this.style.transform = 'translateX(-50%) scale(1)'; this.style.boxShadow = 'none'; }); intervalEl.addEventListener('click', (e) => { e.stopPropagation(); showAssayModal(assay.originalAssay); }); assaysBody.appendChild(intervalEl); // Метка для верхней границы интервала if (assay.from >= layer.from && heightY > 15) { let labelX = xPos + (assay.docType === 'primary' ? -30 : 30); let labelY = topY; let adjusted = false; for (let offset = 0; offset < 20; offset += 5) { let newY = topY + (adjusted ? offset : -offset); let conflict = false; for (const used of usedLabelPositions) { if (Math.abs(used.y - newY) < 10 && Math.abs(used.x - labelX) < 35) { conflict = true; break; } } if (!conflict) { labelY = newY; usedLabelPositions.push({ x: labelX, y: labelY }); break; } adjusted = !adjusted; } const label = document.createElement('div'); label.style.cssText = ` position: absolute; left: ${labelX}px; top: ${labelY}px; font-size: 7px; font-weight: bold; background: rgba(255,255,255,0.95); padding: 1px 3px; border-radius: 2px; white-space: nowrap; border: 0.5px solid #bdc3c7; cursor: pointer; transform: translateY(-50%); color: ${assay.docType === 'primary' ? '#2c3e50' : '#c0392b'}; z-index: 53; `; label.textContent = assay.from.toFixed(1); label.addEventListener('click', (e) => { e.stopPropagation(); showAssayModal(assay.originalAssay); }); assaysBody.appendChild(label); } } } }); } ``` ## 3. ВОССТАНАВЛИВАЕМ функции масштаба и переключения режима в `createWellStructureDrawing`: ```javascript function createWellStructureDrawing(structureData) { const drawing = document.getElementById('wellStructureDrawing'); if (!drawing) return; drawing.innerHTML = ''; const wellStructureContainer = document.getElementById('wellStructureContainer'); if (!wellStructureContainer) { const container = document.createElement('div'); container.id = 'wellStructureContainer'; container.style.cssText = `height: 100%; display: flex; flex-direction: column;`; drawing.appendChild(container); } const container = document.getElementById('wellStructureContainer'); container.innerHTML = ''; container.style.cssText = ` display: flex; flex-direction: column; height: 100%; background: white; border-radius: 8px; border: 1px solid #ddd; overflow: hidden; `; // ЗАГОЛОВОК С ВСЕМИ НАСТРОЙКАМИ const mainHeader = document.createElement('div'); mainHeader.style.cssText = ` padding: 12px 20px; background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%); color: white; border-bottom: 1px solid #2c3e50; flex-shrink: 0; `; const titleRow = document.createElement('div'); titleRow.style.cssText = `display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;`; const title = document.createElement('h3'); const docTypeNames = { 'primary': 'Первичное', 'final': 'Итоговое', 'gis': 'ГИС' }; const activeDocType = structureData.documentationType; title.textContent = `Геологическая колонка • Глубина: ${structureData.totalDepth} м • ${docTypeNames[activeDocType] || activeDocType}`; title.style.cssText = `margin: 0; color: white; font-size: 18px; font-weight: 600;`; titleRow.appendChild(title); mainHeader.appendChild(titleRow); // Панель настроек const settingsRow = document.createElement('div'); settingsRow.style.cssText = ` display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 8px; `; // Левая группа - переключатель режима const leftControls = document.createElement('div'); leftControls.style.cssText = `display: flex; gap: 10px; align-items: center;`; // Кнопки режима const modeToggle = createColumnModeToggle(); leftControls.appendChild(modeToggle); // Переключатель проб const assaysToggle = createAssaysToggle(); leftControls.appendChild(assaysToggle); // Правая группа - масштаб const rightControls = document.createElement('div'); rightControls.style.cssText = `display: flex; gap: 5px; align-items: center;`; const scaleControls = createColumnScaleControls(); rightControls.appendChild(scaleControls); settingsRow.appendChild(leftControls); settingsRow.appendChild(rightControls); mainHeader.appendChild(settingsRow); container.appendChild(mainHeader); // Контент const contentContainer = document.createElement('div'); contentContainer.style.cssText = ` display: flex; flex: 1; min-height: 0; overflow: hidden; `; // Левая панель для колонки const visualPanel = document.createElement('div'); visualPanel.style.cssText = ` flex: 2; min-width: 500px; display: flex; flex-direction: column; border-right: 1px solid #e0e0e0; background: #fafafa; overflow: hidden; `; const graphicsScrollContainer = document.createElement('div'); graphicsScrollContainer.id = 'graphicsScrollContainer'; graphicsScrollContainer.style.cssText = ` flex: 1; overflow: auto; padding: 15px; `; visualPanel.appendChild(graphicsScrollContainer); // Правая панель с карточками const infoPanel = document.createElement('div'); infoPanel.style.cssText = ` flex: 1; min-width: 400px; display: flex; flex-direction: column; background: #f8f9fa; overflow: hidden; `; const infoHeader = document.createElement('div'); infoHeader.style.cssText = ` padding: 10px 20px; background: #2c3e50; color: white; font-size: 14px; font-weight: 600; flex-shrink: 0; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #34495e; `; const headerLeft = document.createElement('div'); headerLeft.style.cssText = 'display: flex; align-items: center; gap: 10px;'; const infoTitle = document.createElement('span'); infoTitle.textContent = 'Интервалы документирования'; const countBadge = document.createElement('span'); countBadge.id = 'intervalsCount'; countBadge.style.cssText = ` background: #e74c3c; color: white; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; `; headerLeft.appendChild(infoTitle); headerLeft.appendChild(countBadge); const headerRight = document.createElement('div'); headerRight.style.cssText = 'display: flex; align-items: center; gap: 5px;'; const docTypeSelector = createDocTypeSelector(activeDocType); headerRight.appendChild(docTypeSelector); infoHeader.appendChild(headerLeft); infoHeader.appendChild(headerRight); infoPanel.appendChild(infoHeader); const cardsContainer = document.createElement('div'); cardsContainer.id = 'cardsContainer'; cardsContainer.style.cssText = ` flex: 1; overflow-y: auto; padding: 15px; background: white; `; infoPanel.appendChild(cardsContainer); contentContainer.appendChild(visualPanel); contentContainer.appendChild(infoPanel); container.appendChild(contentContainer); // Инициализация window.currentScale = window.currentScale || 40; window.assaysVisible = window.assaysVisible !== undefined ? window.assaysVisible : true; window.assayFilter = window.assayFilter || 'all'; window.displayMode = window.displayMode || 'geology'; updateWellStructureByDocType(activeDocType); } ``` Теперь всё должно работать: - Убрана колонка с описанием пород - При клике на карточку интервала - выделяется соответствующий слой в колонке - Метки проб не накладываются друг на друга - На шкале отображаются границы интервалов документирования - Все настройки (масштаб, переключение режима, пробы) на своих местах