Хорошо, давайте окончательно исправим все проблемы. Вот полный исправленный код:
## 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