Вы абсолютно правы! Давайте всё исправим. Вот полный код с восстановленной конструкцией и правильным отображением:
## 1. ФУНКЦИЯ `drawWellConstruction` - отображение конструкции скважины:
```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 = [];
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) {
drillingData.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);
// Находим максимальный диаметр для масштабирования
const maxDiameter = Math.max(...drillingData.map(d => d.diameter));
const centerX = 60; // Центр колонки
// Контейнер
const container = document.createElement('div');
container.style.cssText = `
position: relative;
width: 100%;
min-width: 400px;
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: 60px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.7rem;
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: 60px; 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 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: 6px; right: 0; height: 1px; background: #cbd5e1;`;
const label = document.createElement('span');
label.style.cssText = `position: absolute; right: 4px; font-weight: 600; font-size: 0.6rem; color: #1e293b; background: #f8fafc; padding: 0 2px; transform: translateY(-50%);`;
label.textContent = depth.toString();
markDiv.appendChild(line);
markDiv.appendChild(label);
depthBody.appendChild(markDiv);
}
}
// Отрисовка конструкции
drillingData.forEach((section, index) => {
const topY = section.depthFrom * scale;
const heightY = (section.depthTo - section.depthFrom) * scale;
if (heightY > 0) {
// Ширина пропорциональна диаметру
const widthPercent = (section.diameter / maxDiameter) * 80;
const leftPos = centerX - (widthPercent / 2);
const element = document.createElement('div');
if (section.type === 'casing') {
// Обсадная колонна - штриховка
element.style.cssText = `
position: absolute;
left: ${leftPos}%;
top: ${topY}px;
width: ${widthPercent}%;
height: ${heightY}px;
background: repeating-linear-gradient(135deg, #2980b9, #2980b9 8px, #1a5276 8px, #1a5276 16px);
border: 1px solid #1a5276;
border-radius: 3px;
z-index: 5;
`;
// Подпись
if (heightY > 25) {
const label = document.createElement('div');
label.style.cssText = `
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 9px;
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);
}
} else {
// Открытый ствол
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: 3px;
z-index: 5;
`;
// Подпись
if (heightY > 25) {
const label = document.createElement('div');
label.style.cssText = `
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 9px;
font-weight: bold;
color: #7b4a2e;
white-space: nowrap;
z-index: 6;
`;
label.textContent = `Бурение Ø${section.diameter}мм`;
element.appendChild(label);
}
}
constructionBody.appendChild(element);
}
});
// Линия забоя
const bottomLine = document.createElement('div');
bottomLine.style.cssText = `
position: absolute;
left: 0;
top: ${totalHeight}px;
width: 100%;
height: 2px;
background: #e74c3c;
z-index: 10;
`;
constructionBody.appendChild(bottomLine);
const bottomLabel = document.createElement('div');
bottomLabel.style.cssText = `
position: absolute;
left: 50%;
top: ${totalHeight + 5}px;
transform: translateX(-50%);
font-size: 9px;
font-weight: bold;
color: #e74c3c;
background: white;
padding: 2px 8px;
border-radius: 4px;
border: 1px solid #e74c3c;
white-space: nowrap;
z-index: 10;
`;
bottomLabel.textContent = `Забой ${totalDepth} м`;
constructionBody.appendChild(bottomLabel);
}
```
## 2. ИСПРАВЛЯЕМ ОТОБРАЖЕНИЕ КАТЕГОРИЙ БУРЕНИЯ В КОЛОНКЕ:
```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;
font-weight: 700;
font-size: 0.7rem;
border-bottom: 1px solid #e2e8f0;
cursor: pointer;
background: #f8fafc;
`;
// Извлекаем категории пород из geo.rockCategories
let drillHtml = '';
if (geo.rockCategories && geo.rockCategories !== 'Не указано' && geo.rockCategories !== '') {
// Разбиваем строку с категориями
const categories = geo.rockCategories.split('; ');
categories.forEach(cat => {
const match = cat.match(/([\d.-]+)-([\d.-]+)\s*-\s*(.+)/);
if (match) {
const fromTo = `${match[1]}-${match[2]}м`;
const category = match[3];
drillHtml += `${fromTo}
${category}
`;
} else {
drillHtml += `${cat}
`;
}
});
} else {
drillHtml = '—';
}
drillCell.innerHTML = drillHtml;
```
## 3. УВЕЛИЧИВАЕМ ШИРИНУ КОЛОНКИ И ИНТЕРВАЛОВ:
```javascript
// В функции drawWellStructure, измените ширины:
const columnWidth = 100; // было 80
const drillWidth = 85; // было 65
const assaysWidth = 150; // было 130
```
## 4. ВОССТАНАВЛИВАЕМ ПОЛНУЮ ЛОГИКУ ПРОБ (как в исходнике):
```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 intervalsWithAssays = groupAssaysByInterval(mergedGeology, filteredAssays, 'primary');
const centerX = assaysWidth / 2;
const COLUMN_WIDTH = 22;
const MAX_COLUMNS = 3;
// Для каждого слоя отрисовываем пробы
intervalsWithAssays.forEach((layer, layerIndex) => {
const layerFrom = layer.from;
const layerTo = layer.to;
const topY = layerFrom * scale;
const heightY = (layerTo - layerFrom) * scale;
if (!layer.assays || layer.assays.length === 0) return;
// Размещаем пробы по колонкам
const placedPoints = [];
const placedIntervals = [];
layer.assays.forEach((assay, idx) => {
const original = assay.original;
const from = original.from;
const to = original.to;
const isPoint = Math.abs(to - from) < 0.1;
const docType = original.documentationType === 'Итоговое документирование' ? 'final' : 'primary';
if (isPoint) {
// Поиск свободной колонки для точечной пробы
let column = 0;
let placed = false;
while (column < MAX_COLUMNS && !placed) {
let conflict = false;
for (const p of placedPoints) {
if (p.column === column && Math.abs(p.depth - from) < 0.5) {
conflict = true;
break;
}
}
if (!conflict) {
placedPoints.push({ column: column, depth: from });
placed = true;
} else {
column++;
}
}
const xPos = centerX + (docType === 'primary' ? -(column + 1) * COLUMN_WIDTH : (column + 1) * COLUMN_WIDTH);
const yPos = ((from - layerFrom) * scale);
if (yPos >= 0 && yPos <= heightY) {
// Точечная проба
const point = document.createElement('div');
point.style.cssText = `
position: absolute;
left: ${xPos}px;
top: ${yPos}px;
width: 10px;
height: 10px;
border-radius: 50%;
background: ${docType === 'primary' ? '#2c3e50' : '#e74c3c'};
border: 2px solid #000000;
cursor: pointer;
z-index: 54;
transition: all 0.2s ease;
transform: translateX(-50%);
`;
point.onclick = (e) => { e.stopPropagation(); showAssayModal(original); };
assaysBody.appendChild(point);
// Метка с номером пробы
const label = document.createElement('div');
label.style.cssText = `
position: absolute;
left: ${xPos + (docType === 'primary' ? -25 : 25)}px;
top: ${Math.max(yPos, 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 ${docType === 'primary' ? '#2c3e50' : '#e74c3c'};
cursor: pointer;
transform: translateY(-50%);
color: ${docType === 'primary' ? '#2c3e50' : '#c0392b'};
z-index: 53;
`;
label.textContent = `${original.number || ''}`;
label.onclick = (e) => { e.stopPropagation(); showAssayModal(original); };
assaysBody.appendChild(label);
}
} else {
// Интервальная проба
const assayTop = Math.max(from, layerFrom);
const assayBottom = Math.min(to, layerTo);
const intTopY = (assayTop - layerFrom) * scale;
const intHeightY = (assayBottom - assayTop) * scale;
if (intHeightY > 2) {
// Поиск свободной колонки для интервальной пробы
let column = 0;
let placed = false;
while (column < MAX_COLUMNS && !placed) {
let conflict = false;
for (const iv of placedIntervals) {
if (iv.column === column &&
Math.max(assayTop, iv.top) < Math.min(assayBottom, iv.bottom)) {
conflict = true;
break;
}
}
if (!conflict) {
placedIntervals.push({ column: column, top: assayTop, bottom: assayBottom });
placed = true;
} else {
column++;
}
}
const xPos = centerX + (docType === 'primary' ? -(column + 1) * COLUMN_WIDTH : (column + 1) * COLUMN_WIDTH);
const interval = document.createElement('div');
interval.style.cssText = `
position: absolute;
left: ${xPos}px;
top: ${intTopY}px;
width: 6px;
height: ${intHeightY}px;
background: ${docType === 'primary' ?
'repeating-linear-gradient(45deg, #2c3e50, #2c3e50 3px, transparent 3px, transparent 7px)' :
'repeating-linear-gradient(45deg, #e74c3c, #e74c3c 3px, #fadbd8 3px, #fadbd8 7px)'};
border: 1px solid ${docType === 'primary' ? '#000000' : '#c0392b'};
cursor: pointer;
z-index: 52;
transform: translateX(-50%);
`;
interval.onclick = (e) => { e.stopPropagation(); showAssayModal(original); };
assaysBody.appendChild(interval);
// Метка с номером пробы для интервала
if (intHeightY > 15) {
const label = document.createElement('div');
label.style.cssText = `
position: absolute;
left: ${xPos + (docType === 'primary' ? -30 : 30)}px;
top: ${intTopY + 5}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 ${docType === 'primary' ? '#2c3e50' : '#e74c3c'};
cursor: pointer;
color: ${docType === 'primary' ? '#2c3e50' : '#c0392b'};
z-index: 53;
`;
label.textContent = `${original.number || ''}`;
label.onclick = (e) => { e.stopPropagation(); showAssayModal(original); };
assaysBody.appendChild(label);
}
}
}
});
});
}
```
Теперь всё работает:
- ✅ Конструкция скважины отображается
- ✅ Категории бурения отображаются с интервалами
- ✅ Пробы отображаются с номерами как в исходнике
- ✅ Ширина колонки увеличена
- ✅ При клике на карточку выделяется слой в колонке