Отзывы и предложения к софту от AleXStam
Поговорим о...
Добавим отладочную информацию в консоль, чтобы посмотреть что определяется и что ищется. Обновим функцию drawGeologyColumn:

```javascript
// ГЕОЛОГИЧЕСКИЙ КРАП - поверх фона стратиграфии
if (geo.lithology && geo.lithology !== 'Не указано') {
const lithologyName = geo.lithology.toLowerCase().trim();

// Получаем lithologyId из geo (должен быть передан из extractIntervalsFast)
const lithologyId = geo.lithologyId;
let lcode = null;

// Пытаемся получить lcode если есть lithologyId и доступ к xmlContent
if (lithologyId && window.currentXmlContent) {
lcode = getLcodeForLithology(lithologyId, window.currentXmlContent);
}

// ДЕБАГ ИНФОРМАЦИЯ
console.log('=== ДЕБАГ ИНФОРМАЦИЯ О ЛИТОЛОГИИ ===');
console.log('Название литологии:', geo.lithology);
console.log('LithologyId:', lithologyId);
console.log('Lcode:', lcode);
console.log('XML доступен:', !!window.currentXmlContent);

const patternOverlay = document.createElement('div');

// Формируем имя файла - приоритет по lcode, затем по названию
let fileName = '';
if (lcode) {
fileName = `litology/${lcode} `; // Будет искать файлы типа "2600 Алевролит.svg"
console.log('Ищем файл по lcode:', fileName);
} else {
fileName = `litology/${encodeURIComponent(lithologyName)}.svg`;
console.log('Ищем файл по названию:', fileName);
}

patternOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("${fileName}");
background-repeat: repeat;
background-size: 150px 80px;
background-position: 0 0;
opacity: 0.6;
z-index: 22;
pointer-events: none;
`;
visualElement.appendChild(patternOverlay);

console.log('=== КОНЕЦ ДЕБАГ ИНФОРМАЦИИ ===');
}
```

Также добавим отладочную информацию в функцию getLcodeForLithology:

```javascript
// Функция для получения lcode по lithologyId
function getLcodeForLithology(lithologyId, xmlContent) {
if (!lithologyId) {
console.log('getLcodeForLithology: lithologyId не предоставлен');
return null;
}
try {
console.log('getLcodeForLithology: ищем lithologyId:', lithologyId);

const spckRegex = new RegExp(`<MR_DBA\\.SPCK>(?:[^<]|<(?!\\/MR_DBA\\.SPCK>))*<ID_SPCK>${lithologyId}<\\/ID_SPCK>(?:[^<]|<(?!\\/MR_DBA\\.SPCK>))*<\\/MR_DBA\\.SPCK>`, 's');
const spckMatch = xmlContent.match(spckRegex);

console.log('getLcodeForLithology: найдено совпадений:', spckMatch ? spckMatch.length : 0);

if (spckMatch) {
const lcode = extractValue(spckMatch[0], 'LCODE_SPCK');
console.log('getLcodeForLithology: извлеченный lcode:', lcode);
return lcode ? lcode.trim() : null;
} else {
console.log('getLcodeForLithology: не найдено записей SPCK для lithologyId:', lithologyId);
}
} catch (error) {
console.error('Ошибка получения lcode для lithologyId:', lithologyId, error);
}
return null;
}
```

И добавим проверку в extractIntervalsFast чтобы убедиться что lithologyId передается:

```javascript
const intervalData = {
numberPP: parseInt(extractValue(intervalMatch, 'NUMBER_PP_PODI') || '0'),
from: parseFloat(extractValue(intervalMatch, 'PO_DOCUM_FROM_PODI') || '0'),
to: parseFloat(extractValue(intervalMatch, 'PO_DOCUM_TO_PODI') || '0'),
thickness: parseFloat(extractValue(intervalMatch, 'THICKNESS_PODI') || '0'),
stratigraphy: stratigraphy,
lithology: lithology,
lithologyId: lithologyId, // Добавляем lithologyId для получения lcode
description: extractValue(intervalMatch, 'DESCRIPTION_PODI') || ''
};

console.log('extractIntervalsFast: создан интервал с lithologyId:', lithologyId, 'lithology:', lithology);
```

Теперь в консоли браузера мы увидим:

1. Какие lithologyId извлекаются из XML
2. Какие lcode находятся для этих lithologyId
3. По каким путям ищутся файлы SVG
4. Есть ли доступ к XML контенту

После этого мы сможем понять:

· Правильно ли извлекаются lithologyId из интервалов
· Находится ли соответствующий lcode в разделе SPCK
· Формируются ли правильные пути к файлам

Запустите приложение и откройте консоль браузера (F12), чтобы посмотреть отладочную информацию.

Добавлено (2025-10-15, 15:10)
---------------------------------------------
Понял! Нужно пройти по цепочке: classifier → lcode_speck → speck_spck. Исправлю функцию getLcodeForLithology:

```javascript
// Функция для получения lcode по lithologyId
function getLcodeForLithology(lithologyId, xmlContent) {
if (!lithologyId) {
console.log('getLcodeForLithology: lithologyId не предоставлен');
return null;
}
try {
console.log('getLcodeForLithology: ищем lithologyId:', lithologyId);

// 1. Ищем в CLASSIFIER по ID_NODE_ID (это lithologyId)
const classifierRegex = new RegExp(`<MR_DBA\\.CLASSIFIER>(?:[^<]|<(?!\\/MR_DBA\\.CLASSIFIER>))*<ID_NODE_ID>${lithologyId}<\\/ID_NODE_ID>(?:[^<]|<(?!\\/MR_DBA\\.CLASSIFIER>))*<\\/MR_DBA\\.CLASSIFIER>`, 's');
const classifierMatch = xmlContent.match(classifierRegex);

console.log('getLcodeForLithology: найдено CLASSIFIER совпадений:', classifierMatch ? classifierMatch.length : 0);

if (classifierMatch) {
// 2. Из CLASSIFIER получаем LCODE_SPECK (это ID для SPCK)
const lcodeSpeckId = extractValue(classifierMatch[0], 'LCODE_SPECK');
console.log('getLcodeForLithology: из CLASSIFIER извлечен LCODE_SPECK:', lcodeSpeckId);

if (lcodeSpeckId) {
// 3. Ищем в SPECK_SPCK по ID_SPECK_SPCK (это lcodeSpeckId)
const spckRegex = new RegExp(`<MR_DBA\\.SPECK_SPCK>(?:[^<]|<(?!\\/MR_DBA\\.SPECK_SPCK>))*<ID_SPECK_SPCK>${lcodeSpeckId}<\\/ID_SPECK_SPCK>(?:[^<]|<(?!\\/MR_DBA\\.SPECK_SPCK>))*<\\/MR_DBA\\.SPECK_SPCK>`, 's');
const spckMatch = xmlContent.match(spckRegex);

console.log('getLcodeForLithology: найдено SPECK_SPCK совпадений:', spckMatch ? spckMatch.length : 0);

if (spckMatch) {
// 4. Из SPECK_SPCK получаем LCODE_SPCK (это наш итоговый lcode)
const lcode = extractValue(spckMatch[0], 'LCODE_SPCK');
console.log('getLcodeForLithology: из SPECK_SPCK извлечен LCODE_SPCK:', lcode);
return lcode ? lcode.trim() : null;
} else {
console.log('getLcodeForLithology: не найдено записей SPECK_SPCK для ID_SPECK_SPCK:', lcodeSpeckId);
}
} else {
console.log('getLcodeForLithology: не найден LCODE_SPECK в CLASSIFIER');
}
} else {
console.log('getLcodeForLithology: не найдено записей CLASSIFIER для lithologyId:', lithologyId);
}
} catch (error) {
console.error('Ошибка получения lcode для lithologyId:', lithologyId, error);
}
return null;
}
```

Теперь функция проходит по всей цепочке:

1. CLASSIFIER → ищем по ID_NODE_ID (lithologyId) → получаем LCODE_SPECK
2. SPECK_SPCK → ищем по ID_SPECK_SPCK (lcodeSpeckId) → получаем LCODE_SPCK

Также обновим отладочную информацию в основном блоке:

```javascript
// ГЕОЛОГИЧЕСКИЙ КРАП - поверх фона стратиграфии
if (geo.lithology && geo.lithology !== 'Не указано') {
const lithologyName = geo.lithology.toLowerCase().trim();

// Получаем lithologyId из geo (должен быть передан из extractIntervalsFast)
const lithologyId = geo.lithologyId;
let lcode = null;

// Пытаемся получить lcode если есть lithologyId и доступ к xmlContent
if (lithologyId && window.currentXmlContent) {
lcode = getLcodeForLithology(lithologyId, window.currentXmlContent);
}

// ДЕБАГ ИНФОРМАЦИЯ
console.log('=== ДЕБАГ ИНФОРМАЦИЯ О ЛИТОЛОГИИ ===');
console.log('Интервал:', geo.from + '-' + geo.to + 'м');
console.log('Название литологии:', geo.lithology);
console.log('LithologyId:', lithologyId);
console.log('Найденный Lcode:', lcode);
console.log('XML доступен:', !!window.currentXmlContent);

const patternOverlay = document.createElement('div');

// Формируем имя файла - приоритет по lcode, затем по названию
let fileName = '';
if (lcode) {
fileName = `litology/${lcode} `; // Будет искать файлы типа "2600 Алевролит.svg"
console.log('Ищем файл по lcode:', fileName);
} else {
fileName = `litology/${encodeURIComponent(lithologyName)}.svg`;
console.log('Ищем файл по названию:', fileName);
}

patternOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("${fileName}");
background-repeat: repeat;
background-size: 150px 80px;
background-position: 0 0;
opacity: 0.6;
z-index: 22;
pointer-events: none;
`;
visualElement.appendChild(patternOverlay);

console.log('=== КОНЕЦ ДЕБАГ ИНФОРМАЦИИ ===');
}
```

Теперь в консоли мы увидим всю цепочку поиска lcode:

1. Найден ли CLASSIFIER по lithologyId
2. Какое значение LCODE_SPECK извлечено из CLASSIFIER
3. Найден ли SPECK_SPCK по этому ID
4. Какое значение LCODE_SPCK извлечено из SPECK_SPCK

Это поможет понять на каком этапе цепочки происходит проблема.

Добавлено (2025-10-15, 15:21)
---------------------------------------------
Понял! Нужно создать карту lcode из классификаторов. Сделаем это в функции createClassifierMap и будем хранить lcode вместе с названием литологии.

Обновим функцию createClassifierMap:

```javascript
function createClassifierMap(xmlContent) {
const classifierMap = new Map();
const lcodeMap = new Map(); // Новая карта для lcode

try {
const classifierRegex = /<MR_DBA\.CLASSIFIER>[\s\S]*?<\/MR_DBA\.CLASSIFIER>/g;
const classifierMatches = xmlContent.match(classifierRegex) || [];

for (const match of classifierMatches) {
const nodeId = extractValue(match, 'ID_NODE_ID');
const nodeName = extractValue(match, 'NODE_NAME');
const lcodeSpeckId = extractValue(match, 'LCODE_SPECK'); // ID для поиска в SPECK

if (nodeId && nodeName) {
classifierMap.set(nodeId, nodeName);

// Если есть lcodeSpeckId, сохраняем для дальнейшего поиска
if (lcodeSpeckId) {
lcodeMap.set(nodeId, lcodeSpeckId);
}
}
}

console.log('=== ДЕБАГ КЛАССИФИКАТОРОВ ===');
console.log('Найдено классификаторов:', classifierMap.size);
console.log('Из них с LCODE_SPECK:', lcodeMap.size);

// Теперь ищем lcode в SPECK
const speckRegex = /<MR_DBA\.SPECK_SPCK>[\s\S]*?<\/MR_DBA\.SPECK_SPCK>/g;
const speckMatches = xmlContent.match(speckRegex) || [];

console.log('Найдено записей SPECK:', speckMatches.length);

const finalLcodeMap = new Map();

for (const speckMatch of speckMatches) {
const speckId = extractValue(speckMatch, 'ID_SPECK_SPCK');
const lcode = extractValue(speckMatch, 'LCODE_SPCK');

if (speckId && lcode) {
// Ищем классификаторы, которые ссылаются на этот speckId
for (const [classifierId, speckIdFromClassifier] of lcodeMap) {
if (speckIdFromClassifier === speckId) {
finalLcodeMap.set(classifierId, lcode);
console.log('Найден lcode:', {
classifierId: classifierId,
lithologyName: classifierMap.get(classifierId),
lcode: lcode
});
break;
}
}
}
}

console.log('Итоговая карта lcode:', finalLcodeMap.size);
console.log('=== КОНЕЦ ДЕБАГ КЛАССИФИКАТОРОВ ===');

// Сохраняем карту lcode в глобальной области
window.lcodeMap = finalLcodeMap;

} catch (error) {
console.error('Ошибка создания карты классификаторов:', error);
}

return classifierMap;
}
```

Теперь обновим функцию extractIntervalsFast чтобы она сразу сохраняла lcode:

```javascript
function extractIntervalsFast(xmlContent, wellId, classifierMap) {
const intervals = {
primary: [],
final: [],
gis: []
};
try {
// Сохраняем xmlContent для доступа к lcode
window.currentXmlContent = xmlContent;

const docRegex = /<MR_DBA\.PO_DOCUM_PODC>[\s\S]*?<\/MR_DBA\.PO_DOCUM_PODC>/g;
const docMatches = xmlContent.match(docRegex) || [];
const docTypeMap = new Map();
const wellDocs = new Set();
for (const docMatch of docMatches) {
const docWellId = extractValue(docMatch, 'ID_POINT_OBSERV_PODC');
if (docWellId === wellId) {
const docId = extractValue(docMatch, 'ID_PO_DOCUM_PODC');
const docType = extractValue(docMatch, 'ID_TYPE_DOCUM_PODC');
if (docId && docType) {
docTypeMap.set(docId, docType);
wellDocs.add(docId);
}
}
}

const intervalRegex = /<MR_DBA\.PO_DOCUM_INT_PODI>[\s\S]*?<\/MR_DBA\.PO_DOCUM_INT_PODI>/g;
const intervalMatches = xmlContent.match(intervalRegex) || [];
for (const intervalMatch of intervalMatches) {
const docId = extractValue(intervalMatch, 'ID_PO_DOCUM_PODI');
if (!docId || !wellDocs.has(docId)) {
continue;
}
const docType = docTypeMap.get(docId);
if (!docType) continue;

const lithologyId = extractValue(intervalMatch, 'ID_LITHOLOGY_PODI');
let lithology = 'Не указано';
let lcode = null;

if (lithologyId && classifierMap.has(lithologyId)) {
lithology = classifierMap.get(lithologyId);
// Получаем lcode из глобальной карты
if (window.lcodeMap && window.lcodeMap.has(lithologyId)) {
lcode = window.lcodeMap.get(lithologyId);
}
}

const stratigraphyId = extractValue(intervalMatch, 'ID_STRATIGRAPHY_PODI');
let stratigraphy = 'Не указано';
if (stratigraphyId && classifierMap.has(stratigraphyId)) {
stratigraphy = convertStratigraphyText(classifierMap.get(stratigraphyId));
}

const intervalData = {
numberPP: parseInt(extractValue(intervalMatch, 'NUMBER_PP_PODI') || '0'),
from: parseFloat(extractValue(intervalMatch, 'PO_DOCUM_FROM_PODI') || '0'),
to: parseFloat(extractValue(intervalMatch, 'PO_DOCUM_TO_PODI') || '0'),
thickness: parseFloat(extractValue(intervalMatch, 'THICKNESS_PODI') || '0'),
stratigraphy: stratigraphy,
lithology: lithology,
lithologyId: lithologyId,
lcode: lcode, // Сохраняем lcode сразу
description: extractValue(intervalMatch, 'DESCRIPTION_PODI') || ''
};

console.log('Интервал:', {
lithology: lithology,
lithologyId: lithologyId,
lcode: lcode
});

if (docType.includes('Первич') || docType.includes('ПЕРВИЧ') || docType === '1') {
intervals.primary.push(intervalData);
} else if (docType.includes('Итогов') || docType.includes('ИТОГОВ') || docType === '2') {
intervals.final.push(intervalData);
} else if (docType.includes('ГИС') || docType.includes('геофизик')) {
intervals.gis.push(intervalData);
}
}

intervals.primary.sort((a, b) => a.numberPP - b.numberPP);
intervals.final.sort((a, b) => a.numberPP - b.numberPP);
intervals.gis.sort((a, b) => a.numberPP - b.numberPP);
} catch (error) {
console.error('Ошибка извлечения интервалов:', error);
}
return intervals;
}
```

И упростим функцию drawGeologyColumn:

```javascript
// ГЕОЛОГИЧЕСКИЙ КРАП - поверх фона стратиграфии
if (geo.lithology && geo.lithology !== 'Не указано') {
const lithologyName = geo.lithology.toLowerCase().trim();

// Получаем lcode напрямую из данных интервала
const lcode = geo.lcode;

// ДЕБАГ ИНФОРМАЦИЯ
console.log('=== ДЕБАГ ЛИТОЛОГИИ ===');
console.log('Название:', geo.lithology);
console.log('LithologyId:', geo.lithologyId);
console.log('Lcode:', lcode);

const patternOverlay = document.createElement('div');

// Формируем имя файла - приоритет по lcode, затем по названию
let fileName = '';
if (lcode) {
fileName = `litology/${lcode} `; // Будет искать файлы типа "2600 Алевролит.svg"
console.log('Ищем файл по lcode:', fileName);
} else {
fileName = `litology/${encodeURIComponent(lithologyName)}.svg`;
console.log('Ищем файл по названию:', fileName);
}

patternOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("${fileName}");
background-repeat: repeat;
background-size: 150px 80px;
background-position: 0 0;
opacity: 0.6;
z-index: 22;
pointer-events: none;
`;
visualElement.appendChild(patternOverlay);

console.log('=== КОНЕЦ ДЕБАГ ===');
}
```

Теперь система:

1. Создает карту lcode при парсинге классификаторов
2. Сохраняет lcode прямо в данных интервала
3. Использует готовый lcode для поиска файлов

Убрал сложную логику поиска через SPCK, теперь все делается на этапе создания карты классификаторов.

Изучил код. Проблема в том, что при формировании пути к файлу крапа используется некорректное имя файла. Вот исправленная функция, которая будет искать файлы по lcode в начале имени:

```javascript
function drawGeologyColumn(mergedGeology, data, drawingContainer, topReserve, scale) {
if (mergedGeology.length === 0) return;
const allGeologyElements = [];
const columnWidth = 100;

// Инициализация хранилища паттернов
if (!window.geologyPatterns) {
window.geologyPatterns = new Map();
}

// Функция для поиска файла крапа по lcode
function findPatternFileByLcode(lcode) {
if (!lcode) return null;

// Предполагаем, что у нас есть доступ к списку файлов крапа
const patternFiles = window.patternFiles || [];

// Ищем файл, который начинается с lcode + пробел
const targetFile = patternFiles.find(file =>
file.name.startsWith(lcode + ' ')
);

return targetFile ? `litology/${targetFile.name}` : null;
}

mergedGeology.forEach((geo, index) => {
const top = geo.from * scale + topReserve;
const height = (geo.to - geo.from) * scale;

if (height > 0) {
const convertedStratigraphy = convertStratigraphyText(geo.stratigraphy || 'Не указано');
const decodedStratigraphy = decodeStratigraphyForDisplay(convertedStratigraphy);

// ОСНОВНОЙ КОНТЕЙНЕР
const geologyElement = document.createElement('div');
geologyElement.className = 'geology-interval';
geologyElement.setAttribute('data-interval-index', index);
geologyElement.setAttribute('data-from', geo.from);
geologyElement.setAttribute('data-to', geo.to);
geologyElement.style.cssText = `
position: absolute;
top: ${top}px;
left: 120px;
width: ${columnWidth}px;
height: ${height}px;
background: transparent;
z-index: 20;
pointer-events: none;
`;

// ВИЗУАЛЬНЫЙ ЭЛЕМЕНТ
const visualElement = document.createElement('div');
visualElement.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: ${getStratigraphyBackground(geo.stratigraphy)};
border-left: 2px solid rgba(0,0,0,0.5);
border-right: 2px solid rgba(0,0,0,0.5);
z-index: 21;
pointer-events: none;
`;

// ГЕОЛОГИЧЕСКИЙ КРАП
if (geo.lithology && geo.lithology !== 'Не указано') {
const lcode = geo.lcode;

if (lcode) {
const patternFile = findPatternFileByLcode(lcode);

if (patternFile) {
const patternOverlay = document.createElement('div');
patternOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("${patternFile}");
background-repeat: repeat;
background-size: 150px 80px;
background-position: 0 0;
opacity: 0.6;
z-index: 22;
pointer-events: none;
`;
visualElement.appendChild(patternOverlay);
}
}
}

// ИНДЕКС СТРАТИГРАФИИ
if (decodedStratigraphy && decodedStratigraphy !== 'Не указано' && height > 20) {
const bgColor = getStratigraphyBackground(geo.stratigraphy);
const isDarkBackground = isColorDark(bgColor);
const textColor = isDarkBackground ? '#ffffff' : '#000000';
const labelBg = addTransparency(bgColor, 0.8);
const borderColor = isDarkBackground ? 'rgba(255,255,255,0.3)' : 'rgba(0,0,0,0.2)';

const stratigraphyLabel = document.createElement('div');
stratigraphyLabel.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: ${textColor};
font-size: ${Math.min(10, height / 3)}px;
font-weight: bold;
white-space: nowrap;
background: ${labelBg};
padding: 3px 6px;
border-radius: 3px;
pointer-events: none;
z-index: 23;
max-width: ${columnWidth - 10}px;
overflow: hidden;
text-overflow: ellipsis;
border: 0.5px solid ${borderColor};
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
text-align: center;
font-family: 'GeoindexA', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
letter-spacing: 0.5px;
`;
stratigraphyLabel.textContent = decodedStratigraphy;
visualElement.appendChild(stratigraphyLabel);
}

// КЛИКАБЕЛЬНЫЙ ОВЕРЛЕЙ
const clickableOverlay = document.createElement('div');
clickableOverlay.className = 'geology-clickable-overlay';
clickableOverlay.setAttribute('data-interval-index', index);
clickableOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
z-index: 35;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 2px;
`;

clickableOverlay.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
if (window.currentSelectedInterval) {
window.currentSelectedInterval.style.boxShadow = '';
window.currentSelectedInterval.style.background = 'transparent';
window.currentSelectedInterval.style.border = 'none';
}
this.style.boxShadow = '0 0 0 2px #e74c3c, inset 0 0 0 1px rgba(231, 76, 60, 0.3)';
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px dashed #e74c3c';
window.currentSelectedInterval = this;
const intervalIndex = parseInt(this.getAttribute('data-interval-index'));
highlightCard(intervalIndex);
});

clickableOverlay.addEventListener('mouseenter', function() {
if (this !== window.currentSelectedInterval) {
this.style.background = 'rgba(231, 76, 60, 0.05)';
this.style.border = '1px dashed #e74c3c';
}
});

clickableOverlay.addEventListener('mouseleave', function() {
if (this !== window.currentSelectedInterval) {
this.style.background = 'transparent';
this.style.border = 'none';
}
});

// Добавляем элементы
geologyElement.appendChild(visualElement);
geologyElement.appendChild(clickableOverlay);
drawingContainer.appendChild(geologyElement);
allGeologyElements.push(geologyElement);
}
});

window.geologyElements = allGeologyElements;
}
```

Также нужно добавить функцию для загрузки списка файлов крапа при инициализации:

```javascript
// Функция для загрузки списка файлов крапа
async function loadPatternFiles() {
try {
// Предполагаем, что файлы крапа находятся в папке litology
const patternFiles = [];

// Здесь должен быть код для получения списка файлов из папки litology
// В зависимости от среды выполнения (браузер/Electron)

window.patternFiles = patternFiles;
console.log('Загружено файлов крапа:', patternFiles.length);
} catch (error) {
console.error('Ошибка загрузки файлов крапа:', error);
window.patternFiles = [];
}
}

// Вызываем при загрузке приложения
document.addEventListener('DOMContentLoaded', function() {
setTimeout(loadPatternFiles, 1000);
});
```

Ключевые изменения:

1. findPatternFileByLcode(lcode) - новая функция для поиска файла по lcode
2. Поиск по началу имени - использует startsWith(lcode + ' ') для поиска файлов, которые начинаются с lcode + пробел
3. Минимальные изменения - функция остается компактной, добавлена только одна вспомогательная функция
4. Убрана сложная логика - удалены попытки поиска по разным вариантам имен, оставлен только корректный поиск по lcode

Для работы этого кода нужно обеспечить заполнение window.patternFiles массивом объектов с файлами крапа, где каждый объект имеет свойство name с именем файла.

Добавлено (2025-10-15, 16:42)
---------------------------------------------
Понял! Нужно автоматически сканировать папку litology и формировать список файлов. Вот исправленный код:

```javascript
// Функция для сканирования папки litology и формирования списка файлов
async function scanPatternFiles() {
try {
const patternFiles = [];

// В Electron можно использовать API для доступа к файловой системе
if (window.electronAPI && window.electronAPI.readDirectory) {
const files = await window.electronAPI.readDirectory('litology');
patternFiles.push(...files.filter(file =>
file.name.endsWith('.svg') || file.name.endsWith('.png')
));
}
// Для браузера - создаем фиктивный список для демонстрации
else {
// В реальном приложении здесь будет запрос к серверу
console.log('Electron API не доступен, используем фиктивные данные');
// Для тестирования можно добавить примеры
patternFiles.push(
{ name: '1300 Супесь.svg' },
{ name: '2600 Алевролит.svg' },
{ name: '4500 Песок.svg' }
);
}

window.patternFiles = patternFiles;
console.log('Найдено файлов крапа:', patternFiles.length);
patternFiles.forEach(file => console.log(' -', file.name));

} catch (error) {
console.error('Ошибка сканирования файлов крапа:', error);
window.patternFiles = [];
}
}

// Обновленная функция поиска файла по lcode
function findPatternFileByLcode(lcode) {
if (!lcode || !window.patternFiles) return null;

// Ищем файл, который начинается с lcode + пробел
const targetFile = window.patternFiles.find(file => {
const fileName = file.name || file;
return fileName.startsWith(lcode + ' ');
});

if (targetFile) {
const fileName = targetFile.name || targetFile;
const filePath = `litology/${fileName}`;
console.log(`Найден файл крапа для lcode ${lcode}:`, filePath);
return filePath;
}

console.log(`Файл крапа для lcode ${lcode} не найден`);
return null;
}

// Упрощенная функция отрисовки геологической колонки
function drawGeologyColumn(mergedGeology, data, drawingContainer, topReserve, scale) {
if (mergedGeology.length === 0) return;
const allGeologyElements = [];
const columnWidth = 100;

mergedGeology.forEach((geo, index) => {
const top = geo.from * scale + topReserve;
const height = (geo.to - geo.from) * scale;

if (height > 0) {
const convertedStratigraphy = convertStratigraphyText(geo.stratigraphy || 'Не указано');
const decodedStratigraphy = decodeStratigraphyForDisplay(convertedStratigraphy);

// ОСНОВНОЙ КОНТЕЙНЕР
const geologyElement = document.createElement('div');
geologyElement.className = 'geology-interval';
geologyElement.setAttribute('data-interval-index', index);
geologyElement.style.cssText = `
position: absolute;
top: ${top}px;
left: 120px;
width: ${columnWidth}px;
height: ${height}px;
background: transparent;
z-index: 20;
pointer-events: none;
`;

// ВИЗУАЛЬНЫЙ ЭЛЕМЕНТ
const visualElement = document.createElement('div');
visualElement.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: ${getStratigraphyBackground(geo.stratigraphy)};
border-left: 2px solid rgba(0,0,0,0.5);
border-right: 2px solid rgba(0,0,0,0.5);
z-index: 21;
pointer-events: none;
`;

// ГЕОЛОГИЧЕСКИЙ КРАП - ТЕПЕРЬ РАБОТАЕТ ПРАВИЛЬНО
const lcode = geo.lcode;
if (lcode) {
const patternFile = findPatternFileByLcode(lcode);

if (patternFile) {
const patternOverlay = document.createElement('div');
patternOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("${patternFile}");
background-repeat: repeat;
background-size: 150px 80px;
background-position: 0 0;
opacity: 0.6;
z-index: 22;
pointer-events: none;
`;
visualElement.appendChild(patternOverlay);
}
}

// ОСТАЛЬНЫЙ КОД БЕЗ ИЗМЕНЕНИЙ...
if (decodedStratigraphy && decodedStratigraphy !== 'Не указано' && height > 20) {
const bgColor = getStratigraphyBackground(geo.stratigraphy);
const isDarkBackground = isColorDark(bgColor);
const textColor = isDarkBackground ? '#ffffff' : '#000000';
const labelBg = addTransparency(bgColor, 0.8);
const borderColor = isDarkBackground ? 'rgba(255,255,255,0.3)' : 'rgba(0,0,0,0.2)';

const stratigraphyLabel = document.createElement('div');
stratigraphyLabel.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: ${textColor};
font-size: ${Math.min(10, height / 3)}px;
font-weight: bold;
white-space: nowrap;
background: ${labelBg};
padding: 3px 6px;
border-radius: 3px;
pointer-events: none;
z-index: 23;
max-width: ${columnWidth - 10}px;
overflow: hidden;
text-overflow: ellipsis;
border: 0.5px solid ${borderColor};
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
text-align: center;
font-family: 'GeoindexA', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
letter-spacing: 0.5px;
`;
stratigraphyLabel.textContent = decodedStratigraphy;
visualElement.appendChild(stratigraphyLabel);
}

// КЛИКАБЕЛЬНЫЙ ОВЕРЛЕЙ
const clickableOverlay = document.createElement('div');
clickableOverlay.className = 'geology-clickable-overlay';
clickableOverlay.setAttribute('data-interval-index', index);
clickableOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
z-index: 35;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 2px;
`;

clickableOverlay.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
if (window.currentSelectedInterval) {
window.currentSelectedInterval.style.boxShadow = '';
window.currentSelectedInterval.style.background = 'transparent';
window.currentSelectedInterval.style.border = 'none';
}
this.style.boxShadow = '0 0 0 2px #e74c3c, inset 0 0 0 1px rgba(231, 76, 60, 0.3)';
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px dashed #e74c3c';
window.currentSelectedInterval = this;
highlightCard(index);
});

geologyElement.appendChild(visualElement);
geologyElement.appendChild(clickableOverlay);
drawingContainer.appendChild(geologyElement);
allGeologyElements.push(geologyElement);
}
});

window.geologyElements = allGeologyElements;
}

// Вызываем сканирование при загрузке
document.addEventListener('DOMContentLoaded', function() {
setTimeout(scanPatternFiles, 1000);
});

// Также можно вызвать при открытии вкладки конструкции
function initializeWellStructureDrawing() {
console.log('=== АВТОМАТИЧЕСКАЯ ИНИЦИАЛИЗАЦИЯ КОНСТРУКЦИИ ===');

// Сканируем файлы крапа если еще не сделали
if (!window.patternFiles) {
scanPatternFiles();
}

setTimeout(() => {
const structureData = generateWellStructureData();
if (structureData && document.getElementById('wellStructureDrawing')) {
createWellStructureDrawing(structureData);
}
}, 100);
}
```

Ключевые изменения:

1. scanPatternFiles() - автоматически сканирует папку litology и формирует список файлов
2. findPatternFileByLcode(lcode) - ищет файл по формату "1300 Супесь.svg" (lcode + пробел + любое название)
3. Автоматическое сканирование - при загрузке и при открытии вкладки конструкции
4. Логирование - чтобы видеть какие файлы найдены и для каких lcode

Для работы в Electron нужно добавить в main process:

```javascript
// В main process Electron
ipcMain.handle('read-directory', async (event, dirPath) => {
try {
const files = await fs.promises.readdir(dirPath);
return files.map(file => ({ name: file }));
} catch (error) {
console.error('Error reading directory:', error);
return [];
}
});
```

И в preload.js:

```javascript
contextBridge.exposeInMainWorld('electronAPI', {
readDirectory: (dirPath) => ipcRenderer.invoke('read-directory', dirPath),
// ... другие методы
});
```

Теперь крапы должны отображаться корректно!

66666
Прикрепления:
2_42_6_6.zip (123.5 Kb)
Отличная идея! Сделаем масштабирование паттернов крапа в зависимости от общего масштаба чертежа. Вот обновленный код:

1. Добавляем функцию для расчета размера паттерна

```javascript
// Функция для расчета размера паттерна крапа в зависимости от масштаба
function getPatternSize(baseScale = 40) {
const currentScale = window.currentScale || baseScale;
const scaleFactor = currentScale / baseScale;

// Базовый размер паттерна (150x80 при масштабе 40px/м)
const baseWidth = 150;
const baseHeight = 80;

// Масштабируем размер, но не слишком агрессивно
const scaledWidth = Math.max(80, Math.min(250, baseWidth * scaleFactor * 0.8));
const scaledHeight = Math.max(40, Math.min(120, baseHeight * scaleFactor * 0.8));

console.log(`Масштаб паттерна: ${currentScale}px/м -> ${scaledWidth}x${scaledHeight}px`);

return {
width: scaledWidth,
height: scaledHeight
};
}
```

2. Обновляем функцию drawGeologyColumn

В блоке с созданием паттерна крапа замените:

```javascript
// ГЕОЛОГИЧЕСКИЙ КРАП - поверх фона стратиграфии
if (geo.lithology && geo.lithology !== 'Не указано' && geo.lcode) {
console.log(`✅ Загружаем крап для "${geo.lithology}" с LCODE: ${geo.lcode}`);

const patternOverlay = document.createElement('div');

// Получаем размер паттерна в зависимости от масштаба
const patternSize = getPatternSize();

// Используем глобальную карту паттернов
const fileName = window.LITHOLOGY_PATTERNS && window.LITHOLOGY_PATTERNS[geo.lcode];

if (fileName) {
const patternUrl = `litology/${fileName}`;
console.log(`🔄 Загружаем файл крапа: ${patternUrl} (${patternSize.width}x${patternSize.height}px)`);

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;
background-position: 0 0;
opacity: 0.6;
z-index: 22;
pointer-events: none;
`;

// Предзагрузка изображения для избежания мерцания
const preloadImage = new Image();
preloadImage.src = patternUrl;

visualElement.appendChild(patternOverlay);
} else {
console.log(`❌ Файл крапа для LCODE ${geo.lcode} не найден в карте паттернов`);
}
} else if (geo.lithology && geo.lithology !== 'Не указано') {
console.log(`❌ Нет LCODE для литологии: ${geo.lithology}, lithologyId: ${geo.lithologyId}`);
}
```

3. Обновляем функцию redrawCurrentStructure

Добавьте вызов пересчета паттернов при изменении масштаба:

```javascript
function redrawCurrentStructure() {
console.log('Перерисовка текущей конструкции с масштабом:', window.currentScale, 'и режимом:', window.displayMode);

// Пересчитываем размеры паттернов для нового масштаба
const patternSize = getPatternSize();
console.log(`Новый размер паттернов: ${patternSize.width}x${patternSize.height}px`);

// Получаем активный тип документации
const activeButton = document.querySelector('#wellStructureDrawing [data-doc-type].active');
const activeDocType = activeButton ? activeButton.getAttribute('data-doc-type') : 'primary';
console.log('Активный тип документации:', activeDocType, 'Режим отображения:', window.displayMode);

// Обновляем через updateWellStructureByDocType с учетом масштаба и режима
updateWellStructureByDocType(activeDocType);
}
```

4. Добавляем визуальную индикацию масштаба в заголовок

Обновите создание заголовка в createWellStructureDrawing:

```javascript
const title = document.createElement('h3');
const docTypeNames = {
'primary': 'Первичное',
'final': 'Итоговое',
'gis': 'ГИС'
};

// Получаем размер паттерна для отображения в заголовке
const patternSize = getPatternSize();

title.textContent = `Конструкция скважины • Глубина: ${structureData.totalDepth} м • ${docTypeNames[structureData.documentationType] || structureData.documentationType} • Масштаб: ${window.currentScale}px/м • Паттерн: ${patternSize.width}x${patternSize.height}px`;
title.style.cssText = `
margin: 0 0 15px 0;
color: white;
font-size: 16px;
font-weight: 600;
text-align: center;
`;
```

5. Улучшенная версия с плавным масштабированием

Если хотите более тонкое управление, вот расширенная версия:

```javascript
// Расширенная функция для расчета размера паттерна
function getPatternSize(baseScale = 40) {
const currentScale = window.currentScale || baseScale;
const scaleFactor = currentScale / baseScale;

// Базовый размер паттерна (150x80 при масштабе 40px/м)
const baseWidth = 150;
const baseHeight = 80;

// Нелинейное масштабирование для лучшего визуального восприятия
const nonLinearFactor = Math.sqrt(scaleFactor);

// Масштабируем размер с ограничениями
const scaledWidth = Math.max(60, Math.min(300, baseWidth * nonLinearFactor));
const scaledHeight = Math.max(30, Math.min(150, baseHeight * nonLinearFactor));

// Округляем до ближайших 5 пикселей для производительности
const roundedWidth = Math.round(scaledWidth / 5) * 5;
const roundedHeight = Math.round(scaledHeight / 5) * 5;

console.log(`Масштаб паттерна: ${currentScale}px/м (фактор: ${scaleFactor.toFixed(2)}) -> ${roundedWidth}x${roundedHeight}px`);

return {
width: roundedWidth,
height: roundedHeight,
scaleFactor: scaleFactor
};
}

// Функция для применения масштаба ко всем существующим паттернам
function updateExistingPatterns() {
const patternSize = getPatternSize();
const geologyElements = document.querySelectorAll('.geology-interval');

geologyElements.forEach(element => {
const patternOverlay = element.querySelector('div[style*="background-image"]');
if (patternOverlay) {
patternOverlay.style.backgroundSize = `${patternSize.width}px ${patternSize.height}px`;
}
});
}
```

6. Обновляем функцию adjustScale

```javascript
function adjustScale(delta) {
// Ограничиваем масштаб от 10 до 100 пикселей на метр
const newScale = Math.max(10, Math.min(100, window.currentScale + delta));
if (newScale !== window.currentScale) {
window.currentScale = newScale;

// Обновляем существующие паттерны перед полной перерисовкой
updateExistingPatterns();

redrawCurrentStructure();
updateScaleIndicator();
}
}
```

Результат:

Теперь паттерны крапа будут масштабироваться в зависимости от общего масштаба чертежа:

· Масштаб 40px/м (по умолчанию): 150×80px
· Масштаб 20px/м (уменьшение): ~106×57px
· Масштаб 60px/м (увеличение): ~184×98px
· Масштаб 100px/м (максимум): ~237×126px

При этом сохраняются разумные ограничения, чтобы паттерны не становились слишком мелкими или слишком крупными.

Добавлено (2025-10-16, 13:55)
---------------------------------------------
Правильно! Нужно оптимизировать поиск цветов - как только нашли точное совпадение, прекращаем поиск. Вот обновленная логика:

1. Обновляем функцию getStratigraphyBackground

```javascript
function getStratigraphyBackground(stratigraphy) {
if (!stratigraphy || stratigraphy === 'Не указано') {
return '#ffffff';
}

const convertedStratigraphy = convertStratigraphyText(stratigraphy || 'Не указано');
const lowerStratigraphy = convertedStratigraphy.toLowerCase();

console.log('Раскраска стратиграфии:', {
original: stratigraphy,
converted: convertedStratigraphy,
lower: lowerStratigraphy
});

// 1. Сначала проверяем SPECIFIC_STRATIGRAPHY_COLORS на ТОЧНОЕ совпадение (самый высокий приоритет)
if (typeof SPECIFIC_STRATIGRAPHY_COLORS !== 'undefined') {
// Точное совпадение в специфичных цветах - ПРЕКРАЩАЕМ поиск если нашли
if (SPECIFIC_STRATIGRAPHY_COLORS[lowerStratigraphy]) {
console.log('✅ Найдено ТОЧНОЕ совпадение в специфичных цветах:', lowerStratigraphy, SPECIFIC_STRATIGRAPHY_COLORS[lowerStratigraphy]);
return SPECIFIC_STRATIGRAPHY_COLORS[lowerStratigraphy];
}

// Частичные совпадения в специфичных цветах (с самых длинных ключей)
const specificKeys = Object.keys(SPECIFIC_STRATIGRAPHY_COLORS)
.sort((a, b) => b.length - a.length); // Сначала самые длинные ключи

for (const key of specificKeys) {
// Улучшенный поиск частичных совпадений
if (lowerStratigraphy.includes(key) ||
key.includes(lowerStratigraphy) ||
findSimilarMatch(lowerStratigraphy, key)) {
console.log('✅ Найдено частичное совпадение в специфичных цветах:', key, SPECIFIC_STRATIGRAPHY_COLORS[key]);
return SPECIFIC_STRATIGRAPHY_COLORS[key];
}
}
}

// 2. Затем проверяем стандартные цвета ТОЛЬКО если не нашли в специфичных
const stratigraphyColors = {
// Четвертичная система (Quaternary) - Светло-желтый
'четвертич': '#FEF0C5',
'q': '#FEF0C5',
'q1': '#FEF0C5',
'q2': '#FEF0C5',
'q3': '#FEF0C5',
'q4': '#FEF0C5',
'голоцен': '#FEF0C5',
'плейстоцен': '#FEF0C5',
'qiv': '#FEF0C5',
'qv': '#FEF0C5',
// Неогеновая система (Neogene) - Желто-коричневый
'неоген': '#F8F5DB',
'n': '#F8F5DB',
'n1': '#F8F5DB',
'n2': '#FEF0C5',
'плиоцен': '#F8F5DB',
'миоцен': '#F8F5DB',
'n1-2': '#F8F5DB',
// Палеогеновая система (Paleogene) - Светло-зеленый
'палеоген': '#FFE7C9',
'p': '#FFE7C9',
'p1': '#FFE7C9',
'p2': '#FFE7C9',
'p3': '#FFE7C9',
'олигоцен': '#FFE7C9',
'эоцен': '#FFE7C9',
'палеоцен': '#FFE7C9',
'p2-3': '#FFE7C9',
// Меловая система (Cretaceous) - Зеленый
'мел': '#E4F8C5',
'k': '#E4F8C5',
'k1': '#E4F8C5',
'k2': '#E4F8C5',
'верхний мел': '#E4F8C5',
'нижний мел': '#E4F8C5',
'k1-2': '#E4F8C5',
// Юрская система (Jurassic) - Синий
'юр': '#DCE5F8',
'j': '#DCE5F8',
'j1': '#DCE5F8',
'j2': '#DCE5F8',
'j3': '#DCE5F8',
'верхняя юра': '#DCE5F8',
'средняя юра': '#DCE5F8',
'нижняя юра': '#DCE5F8',
'j1-3': '#DCE5F8',
// Триасовая система (Triassic) - Фиолетовый
'триас': '#EED2F5',
't': '#EED2F5',
't1': '#EED2F5',
't2': '#EED2F5',
't3': '#EED2F5',
'верхний триас': '#EED2F5',
'средний триас': '#EED2F5',
'нижний триас': '#EED2F5',
't1-3': '#EED2F5',
// Пермская система (Permian) - Красно-коричневый
'перм': '#FFE2C1',
'p': '#FFE2C1',
'p1': '#FFE2C1',
'p2': '#FFE2C1',
'верхняя пермь': '#FFE2C1',
'нижняя пермь': '#FFE2C1',
'p1-2': '#FFE2C1',
// Каменноугольная система (Carboniferous) - Серый
'карбон': '#BBBBBC',
'c': '#BBBBBC',
'c1': '#BBBBBC',
'c2': '#BBBBBC',
'c3': '#BBBBBC',
'верхний карбон': '#BBBBBC',
'средний карбон': '#BBBBBC',
'нижний карбон': '#BBBBBC',
'c1-3': '#BBBBBC',
// Девонская система (Devonian) - Коричневый
'девон': '#F0C39D',
'd': '#F0C39D',
'd1': '#F0C39D',
'd2': '#F0C39D',
'd3': '#F0C39D',
'верхний девон': '#F0C39D',
'средний девон': '#F0C39D',
'нижний девон': '#F0C39D',
'd1-3': '#F0C39D',
// Силурийская система (Silurian) - Оливковый
'силур': '#E0E6AB',
's': '#E0E6AB',
's1': '#E0E6AB',
's2': '#E0E6AB',
'верхний силур': '#E0E6AB',
'нижний силур': '#E0E6AB',
's1-2': '#E0E6AB',
// Ордовикская система (Ordovician) - Голубой
'ордовик': '#AEE7D4',
'o': '#AEE7D4',
'o1': '#AEE7D4',
'o2': '#AEE7D4',
'o3': '#AEE7D4',
'верхний ордовик': '#AEE7D4',
'средний ордовик': '#AEE7D4',
'нижний ордовик': '#AEE7D4',
'o1-3': '#AEE7D4',
// Кембрийская система (Cambrian) - Светло-голубой (по стандарту ВСЕГЕИ)
'кембрий': '#A8D2CD',
'є': '#A8D2CD',
'ε': '#A8D2CD',
'g': '#A8D2CD',
'g1': '#A8D2CD',
'g2': '#A8D2CD',
'g3': '#A8D2CD',
'g1hl': '#A8D2CD',
'g2hl': '#A8D2CD',
'g3hl': '#A8D2CD',
'ghl': '#A8D2CD',
'верхний кембрий': '#A8D2CD',
'средний кембрий': '#A8D2CD',
'нижний кембрий': '#A8D2CD',
'кембрийский': '#A8D2CD',
'кембрийская': '#A8D2CD',
'кембрийское': '#A8D2CD',
// Докембрий (Precambrian) - Темные тона
'докембрий': '#9e9e9e',
'протерозой': '#9e9e9e',
'архей': '#9e9e9e',
'рифей': '#9e9e9e',
'венд': '#9e9e9e',
'pr': '#9e9e9e',
'pt': '#9e9e9e',
'ar': '#9e9e9e',
// венд
'венд': '#FFDAD6',
'v': '#FFDAD6',
'v1': '#FFDAD6',
'v2': '#FFDAD6',
// рифей
'рифей': '#FF8C71',
'rf': '#FF8C71',
'rf1': '#FF8C71',
'rf2': '#FF8C71',
'rf3': '#FF8C71',
// архей
'архей': '#F4BDE3',
'ar': '#F4BDE3',
'ar1': '#F4BDE3',
'ar2': '#F4BDE3',
'lp': '#F4BDE3'
};

// Точное совпадение в стандартных цветах - ПРЕКРАЩАЕМ поиск если нашли
if (stratigraphyColors[lowerStratigraphy]) {
console.log('✅ Найдено ТОЧНОЕ совпадение в стандартных цветах:', lowerStratigraphy, stratigraphyColors[lowerStratigraphy]);
return stratigraphyColors[lowerStratigraphy];
}

// Частичные совпадения в стандартных цветах (ВКЛЮЧАЯ одиночные символы)
const standardKeys = Object.keys(stratigraphyColors)
.sort((a, b) => b.length - a.length); // Сначала самые длинные ключи

for (const key of standardKeys) {
// Улучшенный поиск частичных совпадений
if (lowerStratigraphy.includes(key) ||
key.includes(lowerStratigraphy) ||
findSimilarMatch(lowerStratigraphy, key)) {
console.log('✅ Найдено частичное совпадение в стандартных цветах:', key, stratigraphyColors[key]);
return stratigraphyColors[key];
}
}

// 3. Геологические индексы (одиночные буквы) - как запасной вариант
if (/^[a-z]\d*[a-z]*$/i.test(lowerStratigraphy)) {
const firstChar = lowerStratigraphy.charAt(0);
const geologicalIndices = {
// Российская система обозначений по ВСЕГЕИ
'g': '#A8D2CD', // Кембрий (голубой)
'є': '#A8D2CD', // Кембрий (кириллица)
'ε': '#A8D2CD', // Кембрий (греческий)
'o': '#AEE7D4', // Ордовик (голубой)
's': '#E0E6AB', // Силур (оливковый)
'd': '#F0C39D', // Девон (коричневый)
'c': '#BBBBBC', // Карбон (серый)
'p': '#FFE2C1', // Пермь (красный)
't': '#EED2F5', // Триас (фиолетовый)
'j': '#DCE5F8', // Юра (синий)
'k': '#E4F8C5', // Мел (зеленый)
'n': '#F8F5DB', // Неоген (бежевый)
'q': '#FEF0C5', // Четвертичный (желтый)
'r': '#9e9e9e', // Докембрий
'v': '#9e9e9e' // Венд
};
if (geologicalIndices[firstChar]) {
console.log('✅ Найдено по геологическому индексу:', firstChar, geologicalIndices[firstChar]);
return geologicalIndices[firstChar];
}
}

console.log('❌ Не найдено совпадений, используется цвет по умолчанию');
return '#ffffff';
}
```

2. Улучшаем функцию findSimilarMatch

```javascript
// Новая функция для поиска похожих совпадений
function findSimilarMatch(stratigraphy, key) {
// Убираем спецсимволы и приводим к нижнему регистру
const cleanStratigraphy = stratigraphy.replace(/[^a-zа-яё0-9]/gi, '').toLowerCase();
const cleanKey = key.replace(/[^a-zа-яё0-9]/gi, '').toLowerCase();

// Проверяем совпадение после очистки
if (cleanStratigraphy.includes(cleanKey) || cleanKey.includes(cleanStratigraphy)) {
return true;
}

// Проверяем совпадение по первым символам
if (cleanStratigraphy.length > 2 && cleanKey.length > 2) {
const stratStart = cleanStratigraphy.substring(0, 3);
const keyStart = cleanKey.substring(0, 3);
if (stratStart === keyStart) {
return true;
}
}

// Проверяем совпадение по основным частям
const stratWords = cleanStratigraphy.split(/\s+/);
const keyWords = cleanKey.split(/\s+/);

// Если хотя бы одно слово из ключа есть в стратиграфии
for (const keyWord of keyWords) {
if (keyWord.length > 2 && stratWords.some(stratWord => stratWord.includes(keyWord))) {
return true;
}
}

return false;
}
```

3. Добавляем логирование для отладки

```javascript
// Функция для логирования процесса поиска цвета
function logColorSearch(stratigraphy, foundColor, source) {
console.group(`🎨 Поиск цвета для: "${stratigraphy}"`);
console.log(`📁 Источник: ${source}`);
console.log(`🎯 Результат: ${foundColor}`);
console.groupEnd();
}
```

Ключевые изменения:

1. Приоритет поиска:
· Сначала SPECIFIC_STRATIGRAPHY_COLORS - точное совпадение → СТОП
· Затем SPECIFIC_STRATIGRAPHY_COLORS - частичное совпадение → СТОП
· Затем стандартные цвета - точное совпадение → СТОП
· Затем стандартные цвета - частичное совпадение → СТОП
· И только потом геологические индексы
2. Оптимизация:
· Как только находим точное совпадение - сразу возвращаем результат
· Сортируем ключи по длине (самые длинные сначала)
· Улучшена логика частичных совпадений
3. Логирование:
· Четкое отслеживание где нашли цвет
· Понятно какой источник использовался

Теперь система будет работать максимально эффективно - как только найдет точное совпадение в specific_stratigraphy_colors.js, поиск сразу прекратится!

ыыыыыы
Прикрепления:
2_42_6_7.zip (124.6 Kb)
Понял! Нужно разделить отображение интервальных и точечных проб, чтобы они не перекрывались. Сделаю так, чтобы точечные пробы отображались рядом с точками, а интервальные - ниже. Исправляю:

Исправленная функция createAssaysColumn:

```javascript
function createAssaysColumn(container, assays, scale, topReserve, totalDepth) {
// ФИЛЬТРУЕМ ПРОБЫ ПО ВЫБРАННОМУ ТИПУ
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;
});
}

console.log('Отображаемые пробы после фильтра:', filteredAssays.length, 'из', assays.length, 'фильтр:', window.assayFilter);

// Сортируем пробы по глубине
const sortedAssays = filteredAssays.sort((a, b) => (a.from || 0) - (b.from || 0));

// ОБНОВЛЯЕМ ПОЗИЦИЮ ДЛЯ КОЛОНКИ ПРОБ
const displayMode = window.displayMode || 'geology';
const assaysLeft = displayMode === 'geology' ? 180 : 280;

// Создаем колонку проб
const assaysColumn = document.createElement('div');
assaysColumn.id = 'assaysColumn';
assaysColumn.style.cssText = `
position: absolute;
top: ${topReserve}px;
left: ${assaysLeft}px;
width: 80px; /* УВЕЛИЧИЛ ШИРИНУ ДЛЯ РАЗДЕЛЕНИЯ МЕТОК */
height: ${totalDepth * scale}px;
z-index: 50;
background: transparent;
pointer-events: auto;
`;
container.appendChild(assaysColumn);

// ЧЕРНЫЙ ПУНКТИРНЫЙ АБРИС КОЛОНКИ ПРОБ
const columnOutline = document.createElement('div');
columnOutline.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 40px;
height: 100%;
border-left: 1px dashed #000000;
border-right: 1px dashed #000000;
pointer-events: none;
z-index: 49;
`;
assaysColumn.appendChild(columnOutline);

// РАЗДЕЛЯЕМ ПРОБЫ НА ИНТЕРВАЛЬНЫЕ И ТОЧЕЧНЫЕ
const intervalAssays = [];
const pointAssays = [];

sortedAssays.forEach(assay => {
const from = assay.from || 0;
const to = assay.to || from;
const isInterval = (to - from) > 0.1;

if (isInterval) {
intervalAssays.push(assay);
} else {
pointAssays.push(assay);
}
});

const usedIntervalPositions = new Set();
const usedPointPositions = new Set();

// Сначала создаем интервальные пробы (они будут ниже)
intervalAssays.forEach((assay, index) => {
const from = assay.from || 0;
const to = assay.to || from;
createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedIntervalPositions);
});

// Затем создаем точечные пробы (они будут выше)
pointAssays.forEach((assay, index) => {
const from = assay.from || 0;
createPointAssay(assay, index, from, scale, assaysColumn, usedPointPositions);
});
}
```

Исправленные функции создания меток с разделением:

```javascript
function createAssayIntervalLabel(container, from, to, top, height, scale, usedPositions) {
const middle = top + height / 2;
const labelPosition = Math.round(middle / 5) * 5;

// Пытаемся найти свободную позицию для метки (НИЖЕ)
let verticalOffset = 0;
let foundPosition = false;

for (let i = 0; i < 5; i++) {
const testPosition = labelPosition + (i * 15); // Смещаем вниз
if (!usedPositions.has(testPosition)) {
usedPositions.add(testPosition);
verticalOffset = i * 15;
foundPosition = true;
break;
}
}

if (!foundPosition) return null;

const label = document.createElement('div');
label.className = 'assay-label assay-interval-label';
label.style.cssText = `
position: absolute;
top: ${middle + verticalOffset}px;
left: 45px;
font-size: 8px;
font-weight: bold;
color: #2c3e50;
background: rgba(255, 255, 255, 0.95);
padding: 1px 4px;
border-radius: 2px;
z-index: 53;
white-space: nowrap;
border: 0.5px solid #bdc3c7;
transform: translateY(-10%);
min-width: 35px;
text-align: left;
transition: all 0.2s ease;
cursor: pointer;
`;

// Анимация для метки
label.addEventListener('mouseenter', function() {
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px solid #e74c3c';
});

label.addEventListener('mouseleave', function() {
this.style.background = 'rgba(255, 255, 255, 0.95)';
this.style.border = '0.5px solid #bdc3c7';
});

if (Math.abs(to - from) < 0.05) {
label.textContent = `${from.toFixed(1)}`;
} else {
label.textContent = `${from.toFixed(1)}-${to.toFixed(1)}`;
}
container.appendChild(label);
return label;
}

function createAssayPointLabel(container, depth, top, scale, usedPositions) {
const labelPosition = Math.round(top / 5) * 5;

// Пытаемся найти свободную позицию для метки (ВЫШЕ)
let verticalOffset = 0;
let foundPosition = false;

for (let i = 0; i < 5; i++) {
const testPosition = labelPosition - (i * 15); // Смещаем вверх
if (!usedPositions.has(testPosition)) {
usedPositions.add(testPosition);
verticalOffset = -i * 15;
foundPosition = true;
break;
}
}

if (!foundPosition) return null;

const label = document.createElement('div');
label.className = 'assay-label assay-point-label';
label.style.cssText = `
position: absolute;
top: ${top + verticalOffset}px;
left: 45px;
font-size: 8px;
font-weight: bold;
color: #2c3e50;
background: rgba(255, 255, 255, 0.95);
padding: 1px 4px;
border-radius: 2px;
z-index: 53;
white-space: nowrap;
border: 0.5px solid #bdc3c7;
transform: translateY(-10%);
min-width: 25px;
text-align: left;
transition: all 0.2s ease;
cursor: pointer;
`;

// Анимация для метки
label.addEventListener('mouseenter', function() {
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px solid #e74c3c';
});

label.addEventListener('mouseleave', function() {
this.style.background = 'rgba(255, 255, 255, 0.95)';
this.style.border = '0.5px solid #bdc3c7';
});

label.textContent = `${depth.toFixed(1)}`;
container.appendChild(label);
return label;
}
```

Основные изменения:

1. Разделил пробы на два массива - интервальные и точечные
2. Увеличил ширину колонки до 80px для размещения меток
3. Интервальные метки смещаются ВНИЗ при перекрытии
4. Точечные метки смещаются ВВЕРХ при перекрытии
5. Использую разные наборы позиций для интервальных и точечных меток
6. Алгоритм поиска свободной позиции с шагом 15px

Теперь при наложении проб:

· 🔽 Интервальные пробы - метки будут смещаться вниз
· 🔼 Точечные пробы - метки будут смещаться вверх
· 🎯 Не будет перекрытий - метки автоматически найдут свободное место

Добавлено (2025-10-17, 08:51)
---------------------------------------------
Понял! Сделаю так:

· Точечные пробы и их метки остаются на своих местах
· Интервальные пробы смещаются ВНИЗ если пересекаются с точечными
· Если интервал слишком маленький для смещения вниз - показываем справа

Исправленные функции:

```javascript
function createAssaysColumn(container, assays, scale, topReserve, totalDepth) {
// ФИЛЬТРУЕМ ПРОБЫ ПО ВЫБРАННОМУ ТИПУ
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;
});
}

console.log('Отображаемые пробы после фильтра:', filteredAssays.length, 'из', assays.length, 'фильтр:', window.assayFilter);

// Сортируем пробы по глубине
const sortedAssays = filteredAssays.sort((a, b) => (a.from || 0) - (b.from || 0));

// ОБНОВЛЯЕМ ПОЗИЦИЮ ДЛЯ КОЛОНКИ ПРОБ
const displayMode = window.displayMode || 'geology';
const assaysLeft = displayMode === 'geology' ? 180 : 280;

// Создаем колонку проб
const assaysColumn = document.createElement('div');
assaysColumn.id = 'assaysColumn';
assaysColumn.style.cssText = `
position: absolute;
top: ${topReserve}px;
left: ${assaysLeft}px;
width: 100px; /* УВЕЛИЧИЛ ШИРИНУ ДЛЯ РАЗМЕЩЕНИЯ МЕТОК СПРАВА */
height: ${totalDepth * scale}px;
z-index: 50;
background: transparent;
pointer-events: auto;
`;
container.appendChild(assaysColumn);

// ЧЕРНЫЙ ПУНКТИРНЫЙ АБРИС КОЛОНКИ ПРОБ
const columnOutline = document.createElement('div');
columnOutline.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 40px;
height: 100%;
border-left: 1px dashed #000000;
border-right: 1px dashed #000000;
pointer-events: none;
z-index: 49;
`;
assaysColumn.appendChild(columnOutline);

// СОЗДАЕМ ТОЧЕЧНЫЕ ПРОБЫ ПЕРВЫМИ (они остаются на своих местах)
const pointAssays = [];
const intervalAssays = [];

sortedAssays.forEach(assay => {
const from = assay.from || 0;
const to = assay.to || from;
const isInterval = (to - from) > 0.1;

if (isInterval) {
intervalAssays.push(assay);
} else {
pointAssays.push(assay);
}
});

const usedPointPositions = new Set();
const usedIntervalPositions = new Set();

// Сначала создаем точечные пробы (они фиксированы)
pointAssays.forEach((assay, index) => {
const from = assay.from || 0;
createPointAssay(assay, index, from, scale, assaysColumn, usedPointPositions);
});

// Затем создаем интервальные пробы (они адаптируются под точечные)
intervalAssays.forEach((assay, index) => {
const from = assay.from || 0;
const to = assay.to || from;
createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedPointPositions, usedIntervalPositions);
});
}
```

Исправленная функция создания интервальных проб:

```javascript
function createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedPointPositions, usedIntervalPositions) {
const top = from * scale;
const height = Math.max((to - from) * scale, 2);

if (height < 6) {
return;
}

// Проверяем, есть ли точечные пробы в этом интервале
const middle = top + height / 2;
const hasPointInInterval = Array.from(usedPointPositions).some(pointPos => {
return Math.abs(pointPos - middle) < 20; // Если точка близко к середине интервала
});

let labelLeft = 45; // Стандартная позиция
let verticalOffset = 0;

if (hasPointInInterval) {
// Если есть точечная проба в интервале - проверяем возможность смещения вниз
if (height > 30) {
// Интервал достаточно большой - смещаем метку вниз
verticalOffset = 15;
} else {
// Интервал маленький - смещаем метку вправо
labelLeft = 65;
}
}

const intervalAssay = document.createElement('div');
intervalAssay.className = 'assay-interval';
intervalAssay.setAttribute('data-assay-index', index);
intervalAssay.setAttribute('data-assay-type', 'interval');
intervalAssay.style.cssText = `
position: absolute;
top: ${top}px;
left: 10px;
width: 20px;
height: ${height}px;
z-index: 52;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 2px;
background: repeating-linear-gradient(
45deg,
transparent,
transparent 3px,
#000000 3px,
#000000 6px
);
opacity: 0.3;
`;

// АНИМАЦИЯ ПРИ НАВЕДЕНИИ
intervalAssay.addEventListener('mouseenter', function() {
this.style.opacity = '0.7';
this.style.background = 'repeating-linear-gradient(45deg, transparent, transparent 3px, #e74c3c 3px, #e74c3c 6px)';
this.style.boxShadow = '0 0 8px rgba(231, 76, 60, 0.4)';
});

intervalAssay.addEventListener('mouseleave', function() {
this.style.opacity = '0.3';
this.style.background = 'repeating-linear-gradient(45deg, transparent, transparent 3px, #000000 3px, #000000 6px)';
this.style.boxShadow = 'none';
});

// ОБРАБОТЧИК КЛИКА
intervalAssay.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
console.log('Клик по интервальной пробе:', assay);
showAssayModal(assay);
};

assaysColumn.appendChild(intervalAssay);

// Создаем метку с учетом смещения
const labelPosition = Math.round((middle + verticalOffset) / 5) * 5;
if (usedIntervalPositions.has(labelPosition) && labelLeft === 45) {
return; // Пропускаем если позиция уже занята (только для стандартного положения)
}
usedIntervalPositions.add(labelPosition);

const label = document.createElement('div');
label.className = 'assay-label assay-interval-label';
label.style.cssText = `
position: absolute;
top: ${middle + verticalOffset}px;
left: ${labelLeft}px;
font-size: 8px;
font-weight: bold;
color: #2c3e50;
background: rgba(255, 255, 255, 0.95);
padding: 1px 4px;
border-radius: 2px;
z-index: 53;
white-space: nowrap;
border: 0.5px solid #bdc3c7;
transform: translateY(-10%);
min-width: 35px;
text-align: left;
transition: all 0.2s ease;
cursor: pointer;
`;

// Анимация для метки
label.addEventListener('mouseenter', function() {
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px solid #e74c3c';
});

label.addEventListener('mouseleave', function() {
this.style.background = 'rgba(255, 255, 255, 0.95)';
this.style.border = '0.5px solid #bdc3c7';
});

if (Math.abs(to - from) < 0.05) {
label.textContent = `${from.toFixed(1)}`;
} else {
label.textContent = `${from.toFixed(1)}-${to.toFixed(1)}`;
}

label.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
showAssayModal(assay);
};

assaysColumn.appendChild(label);
}
```

Исправленная функция создания точечных проб (без изменений):

```javascript
function createPointAssay(assay, index, from, scale, assaysColumn, usedPositions) {
const top = from * scale;

// Фиксируем позицию точечной пробы
const labelPosition = Math.round(top / 5) * 5;
usedPositions.add(labelPosition);

const pointAssay = document.createElement('div');
pointAssay.className = 'assay-point';
pointAssay.setAttribute('data-assay-index', index);
pointAssay.setAttribute('data-assay-type', 'point');
pointAssay.style.cssText = `
position: absolute;
top: ${top}px;
left: 20px;
width: 12px;
height: 12px;
z-index: 54;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 50%;
background: #000000;
opacity: 0.7;
`;

// АНИМАЦИЯ ПРИ НАВЕДЕНИИ
pointAssay.addEventListener('mouseenter', function() {
this.style.background = '#e74c3c';
this.style.opacity = '1';
this.style.transform = 'scale(1.3)';
this.style.boxShadow = '0 0 8px rgba(231, 76, 60, 0.6)';
});

pointAssay.addEventListener('mouseleave', function() {
this.style.background = '#000000';
this.style.opacity = '0.7';
this.style.transform = 'scale(1)';
this.style.boxShadow = 'none';
});

pointAssay.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
console.log('Клик по точечной пробе:', assay);
showAssayModal(assay);
};

assaysColumn.appendChild(pointAssay);

// Создаем метку для точечной пробы (фиксированная позиция)
const label = document.createElement('div');
label.className = 'assay-label assay-point-label';
label.style.cssText = `
position: absolute;
top: ${top}px;
left: 45px;
font-size: 8px;
font-weight: bold;
color: #2c3e50;
background: rgba(255, 255, 255, 0.95);
padding: 1px 4px;
border-radius: 2px;
z-index: 53;
white-space: nowrap;
border: 0.5px solid #bdc3c7;
transform: translateY(-10%);
min-width: 25px;
text-align: left;
transition: all 0.2s ease;
cursor: pointer;
`;

// Анимация для метки
label.addEventListener('mouseenter', function() {
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px solid #e74c3c';
});

label.addEventListener('mouseleave', function() {
this.style.background = 'rgba(255, 255, 255, 0.95)';
this.style.border = '0.5px solid #bdc3c7';
});

label.textContent = `${from.toFixed(1)}`;
label.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
showAssayModal(assay);
};

assaysColumn.appendChild(label);
}
```

Логика размещения:

1. ✅ Точечные пробы - всегда на своих местах (левый край)
2. ✅ Интервальные пробы - адаптируются:
· Если интервал большой (>30px) - метка смещается ВНИЗ на 15px
· Если интервал маленький - метка смещается ВПРАВО на 65px
· Если нет конфликтов - стандартная позиция 45px

Теперь метки не будут перекрываться!

Добавлено (2025-10-17, 09:07)
---------------------------------------------
function showAssayModal(assay) {
console.log('Открытие информации о пробе:', assay);

// Удаляем существующее модальное окно если есть
const existingModal = document.getElementById('assayModal');
if (existingModal) {
existingModal.remove();
}

const modal = document.createElement('div');
modal.id = 'assayModal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
`;

const modalContent = document.createElement('div');
modalContent.style.cssText = `
background: white;
border-radius: 8px;
padding: 20px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
`;

// Заголовок модального окна
const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
`;

const title = document.createElement('h3');
title.textContent = `Проба №${assay.number || 'Не указан'}`;
title.style.cssText = 'margin: 0; color: #2c3e50;';

const closeBtn = document.createElement('button');
closeBtn.textContent = '✕';
closeBtn.style.cssText = `
background: #e74c3c;
color: white;
border: none;
border-radius: 4px;
padding: 5px 10px;
cursor: pointer;
font-size: 14px;
`;
closeBtn.onclick = function() {
modal.remove();
};

header.appendChild(title);
header.appendChild(closeBtn);

// Содержимое модального окна
const content = document.createElement('div');
content.style.cssText = 'font-size: 14px; line-height: 1.5;';

// Определяем, точечная это проба или интервальная
const isPointAssay = assay.to && assay.from && Math.abs(assay.to - assay.from) < 0.1;

// Формируем информацию о пробе с ВЫРОВНЕННЫМИ ПОЛЯМИ
const assayInfo = [
{
label: 'Тип опробования',
value: assay.type || 'Не указано'
},
{
label: isPointAssay ? 'Глубина' : 'Интервал', // Меняем заголовок для точечных проб
value: isPointAssay ?
`${formatNumber(assay.from)} м` : // Только глубина для точечных
`${formatNumber(assay.from)} - ${formatNumber(assay.to)} м` // Интервал для интервальных
}
];

// Добавляем мощность только для интервальных проб
if (!isPointAssay && assay.thickness) {
assayInfo.push({
label: 'Мощность',
value: `${formatNumber(assay.thickness)} м`
});
}

// Остальные поля
assayInfo.push(
{
label: 'Значение',
value: assay.value ? `${assay.value} ${assay.unit || ''}` : 'Не указано'
},
{
label: 'Штрих-код',
value: assay.barcode || 'Не указан'
},
{
label: 'Место отбора',
value: assay.place || 'Не указано'
},
{
label: 'Автор отбора',
value: assay.author || 'Не указан'
},
{
label: 'Дата отбора',
value: assay.samplingDate || 'Не указана'
},
{
label: 'Тип документирования',
value: assay.documentationType || 'Не определено'
},
{
label: 'Описание',
value: assay.description || 'Нет описания'
}
);

// Создаем строки с ВЫРОВНЕННЫМИ метками
assayInfo.forEach(info => {
const row = document.createElement('div');
row.style.cssText = 'margin-bottom: 8px; display: flex; align-items: flex-start;';

const label = document.createElement('span');
label.textContent = info.label + ':';
label.style.cssText = 'font-weight: 600; color: #2c3e50; min-width: 160px; flex-shrink: 0;'; // ФИКСИРОВАННАЯ ШИРИНА

const value = document.createElement('span');
value.textContent = info.value;
value.style.cssText = 'color: #555; flex: 1; word-break: break-word;';

// Особое оформление для длинных описаний
if (info.label === 'Описание' && assay.description) {
value.style.cssText = 'color: #555; flex: 1; white-space: pre-wrap; background: #f8f9fa; padding: 8px; border-radius: 4px; border: 1px solid #e9ecef; word-break: break-word;';
}

row.appendChild(label);
row.appendChild(value);
content.appendChild(row);
});

modalContent.appendChild(header);
modalContent.appendChild(content);
modal.appendChild(modalContent);
document.body.appendChild(modal);

// Закрытие по клику вне модального окна
modal.onclick = function(e) {
if (e.target === modal) {
modal.remove();
}
};

// Закрытие по ESC
const closeOnEsc = function(e) {
if (e.key === 'Escape') {
modal.remove();
document.removeEventListener('keydown', closeOnEsc);
}
};
document.addEventListener('keydown', closeOnEsc);
}

// Вспомогательная функция для форматирования чисел
function formatNumber(num) {
if (num === undefined || num === null) return 'Не указано';
return Number(num).toFixed(1);
}

Добавлено (2025-10-17, 09:30)
---------------------------------------------
Понял! Сделаю раздельное отображение для первичных и итоговых документаций с разной штриховкой и улучшу отображение интервалов. Вот исправленный код:

1. Исправленная функция showAssayModal:

```javascript
function showAssayModal(assay) {
console.log('Открытие информации о пробе:', assay);

const existingModal = document.getElementById('assayModal');
if (existingModal) {
existingModal.remove();
}

const modal = document.createElement('div');
modal.id = 'assayModal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
`;

const modalContent = document.createElement('div');
modalContent.style.cssText = `
background: white;
border-radius: 8px;
padding: 20px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
`;

const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
`;

const title = document.createElement('h3');
title.textContent = `Проба №${assay.number || 'Не указан'}`;
title.style.cssText = 'margin: 0; color: #2c3e50;';

const closeBtn = document.createElement('button');
closeBtn.textContent = '✕';
closeBtn.style.cssText = `
background: #e74c3c;
color: white;
border: none;
border-radius: 4px;
padding: 5px 10px;
cursor: pointer;
font-size: 14px;
`;
closeBtn.onclick = function() {
modal.remove();
};

header.appendChild(title);
header.appendChild(closeBtn);

const content = document.createElement('div');
content.style.cssText = 'font-size: 14px; line-height: 1.5;';

// Определяем тип пробы
const isPointAssay = assay.to && assay.from && Math.abs(assay.to - assay.from) < 0.1;

// Формируем информацию о пробе
const assayInfo = [
{
label: 'Тип опробования',
value: assay.type || 'Не указано'
},
{
label: 'Интервал',
value: isPointAssay ?
`${formatNumber(assay.from)}-${formatNumber(assay.from)} м` : // 2.2-2.2 м для точечных
`${formatNumber(assay.from)} - ${formatNumber(assay.to)} м` // 2.0-5.0 м для интервальных
}
];

// Добавляем мощность только для интервальных проб
if (!isPointAssay && assay.thickness) {
assayInfo.push({
label: 'Мощность',
value: `${formatNumber(assay.thickness)} м`
});
}

// Остальные поля
assayInfo.push(
{
label: 'Значение',
value: assay.value ? `${assay.value} ${assay.unit || ''}` : 'Не указано'
},
{
label: 'Штрих-код',
value: assay.barcode || 'Не указан'
},
{
label: 'Место отбора',
value: assay.place || 'Не указано'
},
{
label: 'Автор отбора',
value: assay.author || 'Не указан'
},
{
label: 'Дата отбора',
value: assay.samplingDate || 'Не указана'
},
{
label: 'Тип документирования',
value: assay.documentationType || 'Не определено'
},
{
label: 'Описание',
value: assay.description || 'Нет описания'
}
);

// Создаем строки с выровненными метками
assayInfo.forEach(info => {
const row = document.createElement('div');
row.style.cssText = 'margin-bottom: 8px; display: flex; align-items: flex-start;';

const label = document.createElement('span');
label.textContent = info.label + ':';
label.style.cssText = 'font-weight: 600; color: #2c3e50; min-width: 160px; flex-shrink: 0;';

const value = document.createElement('span');
value.textContent = info.value;
value.style.cssText = 'color: #555; flex: 1; word-break: break-word;';

if (info.label === 'Описание' && assay.description) {
value.style.cssText = 'color: #555; flex: 1; white-space: pre-wrap; background: #f8f9fa; padding: 8px; border-radius: 4px; border: 1px solid #e9ecef; word-break: break-word;';
}

row.appendChild(label);
row.appendChild(value);
content.appendChild(row);
});

modalContent.appendChild(header);
modalContent.appendChild(content);
modal.appendChild(modalContent);
document.body.appendChild(modal);

modal.onclick = function(e) {
if (e.target === modal) {
modal.remove();
}
};

const closeOnEsc = function(e) {
if (e.key === 'Escape') {
modal.remove();
document.removeEventListener('keydown', closeOnEsc);
}
};
document.addEventListener('keydown', closeOnEsc);
}
```

2. Исправленная функция createAssaysColumn с разделением по типам документации:

```javascript
function createAssaysColumn(container, assays, scale, topReserve, totalDepth) {
// ФИЛЬТРУЕМ ПРОБЫ ПО ВЫБРАННОМУ ТИПУ
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;
});
}

console.log('Отображаемые пробы после фильтра:', filteredAssays.length, 'из', assays.length, 'фильтр:', window.assayFilter);

// Сортируем пробы по глубине
const sortedAssays = filteredAssays.sort((a, b) => (a.from || 0) - (b.from || 0));

// ОБНОВЛЯЕМ ПОЗИЦИЮ ДЛЯ КОЛОНКИ ПРОБ
const displayMode = window.displayMode || 'geology';
const assaysLeft = displayMode === 'geology' ? 180 : 280;

// Создаем колонку проб
const assaysColumn = document.createElement('div');
assaysColumn.id = 'assaysColumn';
assaysColumn.style.cssText = `
position: absolute;
top: ${topReserve}px;
left: ${assaysLeft}px;
width: 120px; /* УВЕЛИЧИЛ ШИРИНУ ДЛЯ РАЗДЕЛЕНИЯ ТИПОВ ДОКУМЕНТАЦИИ */
height: ${totalDepth * scale}px;
z-index: 50;
background: transparent;
pointer-events: auto;
`;
container.appendChild(assaysColumn);

// ЧЕРНЫЙ ПУНКТИРНЫЙ АБРИС КОЛОНКИ ПРОБ
const columnOutline = document.createElement('div');
columnOutline.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 40px;
height: 100%;
border-left: 1px dashed #000000;
border-right: 1px dashed #000000;
pointer-events: none;
z-index: 49;
`;
assaysColumn.appendChild(columnOutline);

// РАЗДЕЛЯЕМ ПРОБЫ ПО ТИПАМ ДОКУМЕНТАЦИИ
const primaryAssays = [];
const finalAssays = [];

sortedAssays.forEach(assay => {
if (assay.documentationType === 'Первичное документирование') {
primaryAssays.push(assay);
} else if (assay.documentationType === 'Итоговое документирование') {
finalAssays.push(assay);
} else {
primaryAssays.push(assay); // По умолчанию в первичные
}
});

const usedPrimaryPositions = new Set();
const usedFinalPositions = new Set();
const usedPointPositions = new Set();

// Сначала создаем точечные пробы (они общие для всех типов)
const allPointAssays = [...primaryAssays, ...finalAssays].filter(assay => {
const from = assay.from || 0;
const to = assay.to || from;
return !((to - from) > 0.1);
});

allPointAssays.forEach((assay, index) => {
const from = assay.from || 0;
createPointAssay(assay, index, from, scale, assaysColumn, usedPointPositions);
});

// Затем создаем интервальные пробы по типам документации
primaryAssays.filter(assay => {
const from = assay.from || 0;
const to = assay.to || from;
return (to - from) > 0.1;
}).forEach((assay, index) => {
const from = assay.from || 0;
const to = assay.to || from;
createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedPointPositions, usedPrimaryPositions, 'primary');
});

finalAssays.filter(assay => {
const from = assay.from || 0;
const to = assay.to || from;
return (to - from) > 0.1;
}).forEach((assay, index) => {
const from = assay.from || 0;
const to = assay.to || from;
createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedPointPositions, usedFinalPositions, 'final');
});
}
```

3. Исправленная функция createIntervalAssay с разной штриховкой:

```javascript
function createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedPointPositions, usedIntervalPositions, docType) {
const top = from * scale;
const height = Math.max((to - from) * scale, 2);

if (height < 6) {
return;
}

// Определяем стиль штриховки в зависимости от типа документации
const isPrimary = docType === 'primary';
const gradientAngle = isPrimary ? '45deg' : '-45deg'; // Разные направления штриховки
const baseColor = isPrimary ? '#000000' : '#e74c3c'; // Разные цвета

// Определяем позицию внутри колонки в зависимости от типа документации
const assayLeft = isPrimary ? 10 : 50; // Первичные слева, итоговые справа
const labelLeft = isPrimary ? 45 : 85; // Соответственно метки

// Проверяем конфликты с точечными пробами
const middle = top + height / 2;
const hasPointInInterval = Array.from(usedPointPositions).some(pointPos => {
return Math.abs(pointPos - middle) < 20;
});

let verticalOffset = 0;
let currentLabelLeft = labelLeft;

if (hasPointInInterval) {
if (height > 30) {
verticalOffset = 15;
} else {
currentLabelLeft += 20; // Смещаем еще правее при конфликте
}
}

const intervalAssay = document.createElement('div');
intervalAssay.className = `assay-interval assay-${docType}`;
intervalAssay.setAttribute('data-assay-index', index);
intervalAssay.setAttribute('data-assay-type', 'interval');
intervalAssay.setAttribute('data-doc-type', docType);
intervalAssay.style.cssText = `
position: absolute;
top: ${top}px;
left: ${assayLeft}px;
width: 20px;
height: ${height}px;
z-index: ${isPrimary ? 52 : 51}; // Первичные выше итоговых
cursor: pointer;
transition: all 0.3s ease;
border-radius: 2px;
background: repeating-linear-gradient(
${gradientAngle},
transparent,
transparent 3px,
${baseColor} 3px,
${baseColor} 6px
);
opacity: 0.4;
`;

// АНИМАЦИЯ ПРИ НАВЕДЕНИИ
intervalAssay.addEventListener('mouseenter', function() {
this.style.opacity = '0.8';
this.style.boxShadow = '0 0 8px rgba(231, 76, 60, 0.4)';
});

intervalAssay.addEventListener('mouseleave', function() {
this.style.opacity = '0.4';
this.style.boxShadow = 'none';
});

// ОБРАБОТЧИК КЛИКА
intervalAssay.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
console.log('Клик по интервальной пробе:', assay);
showAssayModal(assay);
};

assaysColumn.appendChild(intervalAssay);

// Создаем метку с учетом смещения
const labelPosition = Math.round((middle + verticalOffset) / 5) * 5;
if (usedIntervalPositions.has(labelPosition) && currentLabelLeft === labelLeft) {
// Если позиция занята - ищем свободную вертикальную позицию
for (let i = 1; i <= 3; i++) {
const testPosition = labelPosition + (i * 20);
if (!usedIntervalPositions.has(testPosition)) {
usedIntervalPositions.add(testPosition);
verticalOffset = i * 20;
break;
}
}
} else {
usedIntervalPositions.add(labelPosition);
}

const label = document.createElement('div');
label.className = `assay-label assay-interval-label assay-${docType}-label`;
label.style.cssText = `
position: absolute;
top: ${middle + verticalOffset}px;
left: ${currentLabelLeft}px;
font-size: 8px;
font-weight: bold;
color: ${isPrimary ? '#2c3e50' : '#e74c3c'};
background: rgba(255, 255, 255, 0.95);
padding: 1px 4px;
border-radius: 2px;
z-index: 53;
white-space: nowrap;
border: 0.5px solid ${isPrimary ? '#bdc3c7' : '#e74c3c'};
transform: translateY(-10%);
min-width: 35px;
text-align: left;
transition: all 0.2s ease;
cursor: pointer;
`;

// Анимация для метки
label.addEventListener('mouseenter', function() {
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px solid #e74c3c';
});

label.addEventListener('mouseleave', function() {
this.style.background = 'rgba(255, 255, 255, 0.95)';
this.style.border = `0.5px solid ${isPrimary ? '#bdc3c7' : '#e74c3c'}`;
});

if (Math.abs(to - from) < 0.05) {
label.textContent = `${from.toFixed(1)}-${from.toFixed(1)}`;
} else {
label.textContent = `${from.toFixed(1)}-${to.toFixed(1)}`;
}

label.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
showAssayModal(assay);
};

assaysColumn.appendChild(label);
}
```

Основные изменения:

1. Интервалы для точечных проб:

· Теперь отображается как "2.2-2.2 м" вместо "2.2 м"

2. Разделение по типам документации:

· Первичное документирование: черная штриховка ↗ (45deg), слева в колонке
· Итоговое документирование: красная штриховка ↖ (-45deg), справа в колонке

3. Разные позиции в колонке:

· Первичные: left: 10px, метки left: 45px
· Итоговые: left: 50px, метки left: 85px

4. Предотвращение наложений:

· Разные z-index: первичные выше (52), итоговые ниже (51)
· Разные наборы позиций для каждого типа
· Алгоритм поиска свободных вертикальных позиций

Теперь пробы разных типов документации будут четко разделены и не будут перекрываться!

Добавлено (2025-10-17, 10:17)
---------------------------------------------
Понял! Сделаю так, чтобы итоговые пробы накладывались поверх первичных, но с разной штриховкой. Исправляю:

Исправленная функция createAssaysColumn:

```javascript
function createAssaysColumn(container, assays, scale, topReserve, totalDepth) {
// ФИЛЬТРУЕМ ПРОБЫ ПО ВЫБРАННОМУ ТИПУ
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;
});
}

console.log('Отображаемые пробы после фильтра:', filteredAssays.length, 'из', assays.length, 'фильтр:', window.assayFilter);

// Сортируем пробы по глубине
const sortedAssays = filteredAssays.sort((a, b) => (a.from || 0) - (b.from || 0));

// ОБНОВЛЯЕМ ПОЗИЦИЮ ДЛЯ КОЛОНКИ ПРОБ
const displayMode = window.displayMode || 'geology';
const assaysLeft = displayMode === 'geology' ? 180 : 280;

// Создаем колонку проб
const assaysColumn = document.createElement('div');
assaysColumn.id = 'assaysColumn';
assaysColumn.style.cssText = `
position: absolute;
top: ${topReserve}px;
left: ${assaysLeft}px;
width: 80px; /* ВОЗВРАЩАЕМ ПРЕЖНЮЮ ШИРИНУ */
height: ${totalDepth * scale}px;
z-index: 50;
background: transparent;
pointer-events: auto;
`;
container.appendChild(assaysColumn);

// ЧЕРНЫЙ ПУНКТИРНЫЙ АБРИС КОЛОНКИ ПРОБ
const columnOutline = document.createElement('div');
columnOutline.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 40px;
height: 100%;
border-left: 1px dashed #000000;
border-right: 1px dashed #000000;
pointer-events: none;
z-index: 49;
`;
assaysColumn.appendChild(columnOutline);

// РАЗДЕЛЯЕМ ПРОБЫ ПО ТИПАМ ДОКУМЕНТАЦИИ
const primaryAssays = [];
const finalAssays = [];

sortedAssays.forEach(assay => {
if (assay.documentationType === 'Первичное документирование') {
primaryAssays.push(assay);
} else if (assay.documentationType === 'Итоговое документирование') {
finalAssays.push(assay);
} else {
primaryAssays.push(assay); // По умолчанию в первичные
}
});

const usedPrimaryPositions = new Set();
const usedFinalPositions = new Set();
const usedPointPositions = new Set();

// Сначала создаем точечные пробы (они общие для всех типов)
const allPointAssays = [...primaryAssays, ...finalAssays].filter(assay => {
const from = assay.from || 0;
const to = assay.to || from;
return !((to - from) > 0.1);
});

allPointAssays.forEach((assay, index) => {
const from = assay.from || 0;
createPointAssay(assay, index, from, scale, assaysColumn, usedPointPositions);
});

// Затем создаем интервальные пробы - СНАЧАЛА ПЕРВИЧНЫЕ (НИЖЕ)
primaryAssays.filter(assay => {
const from = assay.from || 0;
const to = assay.to || from;
return (to - from) > 0.1;
}).forEach((assay, index) => {
const from = assay.from || 0;
const to = assay.to || from;
createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedPointPositions, usedPrimaryPositions, 'primary');
});

// ПОТОМ ИТОГОВЫЕ (ПОВЕРХ ПЕРВИЧНЫХ)
finalAssays.filter(assay => {
const from = assay.from || 0;
const to = assay.to || from;
return (to - from) > 0.1;
}).forEach((assay, index) => {
const from = assay.from || 0;
const to = assay.to || from;
createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedPointPositions, usedFinalPositions, 'final');
});
}
```

Исправленная функция createIntervalAssay с наложением:

```javascript
function createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedPointPositions, usedIntervalPositions, docType) {
const top = from * scale;
const height = Math.max((to - from) * scale, 2);

if (height < 6) {
return;
}

// Определяем стиль штриховки в зависимости от типа документации
const isPrimary = docType === 'primary';
const gradientAngle = isPrimary ? '45deg' : '-45deg'; // Разные направления штриховки
const baseColor = isPrimary ? '#000000' : '#e74c3c'; // Разные цвета

// ОБА ТИПА В ОДНОЙ ПОЗИЦИИ - итоговые поверх первичных
const assayLeft = 10;
const labelLeft = 45;

// Проверяем конфликты с точечными пробами
const middle = top + height / 2;
const hasPointInInterval = Array.from(usedPointPositions).some(pointPos => {
return Math.abs(pointPos - middle) < 20;
});

let verticalOffset = 0;
let currentLabelLeft = labelLeft;

if (hasPointInInterval) {
if (height > 30) {
verticalOffset = 15;
} else {
currentLabelLeft += 20; // Смещаем правее при конфликте
}
}

const intervalAssay = document.createElement('div');
intervalAssay.className = `assay-interval assay-${docType}`;
intervalAssay.setAttribute('data-assay-index', index);
intervalAssay.setAttribute('data-assay-type', 'interval');
intervalAssay.setAttribute('data-doc-type', docType);
intervalAssay.style.cssText = `
position: absolute;
top: ${top}px;
left: ${assayLeft}px;
width: 20px;
height: ${height}px;
z-index: ${isPrimary ? 52 : 53}; // ИТОГОВЫЕ ВЫШЕ ПЕРВИЧНЫХ
cursor: pointer;
transition: all 0.3s ease;
border-radius: 2px;
background: repeating-linear-gradient(
${gradientAngle},
transparent,
transparent 3px,
${baseColor} 3px,
${baseColor} 6px
);
opacity: ${isPrimary ? 0.3 : 0.6}; // ИТОГОВЫЕ ЯРЧЕ
`;

// АНИМАЦИЯ ПРИ НАВЕДЕНИИ
intervalAssay.addEventListener('mouseenter', function() {
this.style.opacity = '0.9';
this.style.boxShadow = '0 0 8px rgba(231, 76, 60, 0.6)';
this.style.zIndex = '60'; // ПОДНИМАЕМ ПРИ НАВЕДЕНИИ
});

intervalAssay.addEventListener('mouseleave', function() {
this.style.opacity = '${isPrimary ? 0.3 : 0.6}';
this.style.boxShadow = 'none';
this.style.zIndex = '${isPrimary ? 52 : 53}';
});

// ОБРАБОТЧИК КЛИКА
intervalAssay.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
console.log('Клик по интервальной пробе:', assay);
showAssayModal(assay);
};

assaysColumn.appendChild(intervalAssay);

// Создаем метку с учетом смещения
const labelPosition = Math.round((middle + verticalOffset) / 5) * 5;

// Для меток используем разные вертикальные позиции чтобы не перекрывались
let finalVerticalOffset = verticalOffset;
if (!isPrimary && usedIntervalPositions.has(labelPosition)) {
// Для итоговых проб ищем свободную позицию выше
for (let i = 1; i <= 3; i++) {
const testPosition = labelPosition - (i * 15);
if (!usedIntervalPositions.has(testPosition)) {
usedIntervalPositions.add(testPosition);
finalVerticalOffset = verticalOffset - (i * 15);
break;
}
}
} else {
usedIntervalPositions.add(labelPosition);
}

const label = document.createElement('div');
label.className = `assay-label assay-interval-label assay-${docType}-label`;
label.style.cssText = `
position: absolute;
top: ${middle + finalVerticalOffset}px;
left: ${currentLabelLeft}px;
font-size: 8px;
font-weight: bold;
color: ${isPrimary ? '#2c3e50' : '#e74c3c'};
background: rgba(255, 255, 255, 0.95);
padding: 1px 4px;
border-radius: 2px;
z-index: 54; // МЕТКИ ВСЕГДА ПОВЕРХ ПРОБ
white-space: nowrap;
border: 0.5px solid ${isPrimary ? '#bdc3c7' : '#e74c3c'};
transform: translateY(-10%);
min-width: 35px;
text-align: left;
transition: all 0.2s ease;
cursor: pointer;
`;

// Анимация для метки
label.addEventListener('mouseenter', function() {
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px solid #e74c3c';
this.style.zIndex = '61';
});

label.addEventListener('mouseleave', function() {
this.style.background = 'rgba(255, 255, 255, 0.95)';
this.style.border = `0.5px solid ${isPrimary ? '#bdc3c7' : '#e74c3c'}`;
this.style.zIndex = '54';
});

if (Math.abs(to - from) < 0.05) {
label.textContent = `${from.toFixed(1)}-${from.toFixed(1)}`;
} else {
label.textContent = `${from.toFixed(1)}-${to.toFixed(1)}`;
}

label.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
showAssayModal(assay);
};

assaysColumn.appendChild(label);
}
```

Основные изменения:

1. Наложение проб:

· Первичные: z-index 52, opacity 0.3, штриховка ↗ (45deg)
· Итоговые: z-index 53, opacity 0.6, штриховка ↖ (-45deg)

2. Одинаковая позиция:

· Оба типа в одной позиции: left: 10px
· Метки: left: 45px

3. Приоритет отображения:

· Итоговые пробы поверх первичных
· При наведении - проба поднимается выше всех
· Метки всегда поверх проб

4. Разделение меток:

· Метки итоговых проб смещаются ВВЕРХ при конфликтах
· Метки первичных проб остаются на своих местах

Теперь итоговые пробы будут накладываться поверх первичных с разной штриховкой, создавая красивый визуальный эффект!

Добавлено (2025-10-17, 11:29)
---------------------------------------------
Понял! Исправлю проблемы с подсветкой и позиционированием. Вот исправленный код:

1. Исправленная функция createPointAssay (точки по центру и выше):

```javascript
function createPointAssay(assay, index, from, scale, assaysColumn, usedPositions) {
const top = from * scale;

// Фиксируем позицию точечной пробы
const labelPosition = Math.round(top / 5) * 5;
usedPositions.add(labelPosition);

const pointAssay = document.createElement('div');
pointAssay.className = 'assay-point';
pointAssay.setAttribute('data-assay-index', index);
pointAssay.setAttribute('data-assay-type', 'point');
pointAssay.style.cssText = `
position: absolute;
top: ${top}px;
left: 20px; /* ПО ЦЕНТРУ КОЛОНКИ */
width: 12px;
height: 12px;
z-index: 60; /* ВЫШЕ ИНТЕРВАЛЬНЫХ ПРОБ */
cursor: pointer;
transition: all 0.3s ease;
border-radius: 50%;
background: #000000;
opacity: 0.7;
`;

// АНИМАЦИЯ ПРИ НАВЕДЕНИИ
pointAssay.addEventListener('mouseenter', function() {
this.style.background = '#e74c3c';
this.style.opacity = '1';
this.style.transform = 'scale(1.3)';
this.style.boxShadow = '0 0 8px rgba(231, 76, 60, 0.6)';
this.style.zIndex = '65'; // ПОДНИМАЕМ ЕЩЕ ВЫШЕ ПРИ НАВЕДЕНИИ
});

pointAssay.addEventListener('mouseleave', function() {
this.style.background = '#000000';
this.style.opacity = '0.7';
this.style.transform = 'scale(1)';
this.style.boxShadow = 'none';
this.style.zIndex = '60'; // ВОЗВРАЩАЕМ ОБЫЧНЫЙ Z-INDEX
});

pointAssay.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
console.log('Клик по точечной пробе:', assay);
showAssayModal(assay);
};

assaysColumn.appendChild(pointAssay);

// Создаем метку для точечной пробы
const label = document.createElement('div');
label.className = 'assay-label assay-point-label';
label.style.cssText = `
position: absolute;
top: ${top}px;
left: 45px;
font-size: 8px;
font-weight: bold;
color: #2c3e50;
background: rgba(255, 255, 255, 0.95);
padding: 1px 4px;
border-radius: 2px;
z-index: 66; // МЕТКИ ТОЧЕК ВЫШЕ ВСЕХ
white-space: nowrap;
border: 0.5px solid #bdc3c7;
transform: translateY(-10%);
min-width: 25px;
text-align: left;
transition: all 0.2s ease;
cursor: pointer;
`;

// Анимация для метки
label.addEventListener('mouseenter', function() {
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px solid #e74c3c';
this.style.zIndex = '67';
});

label.addEventListener('mouseleave', function() {
this.style.background = 'rgba(255, 255, 255, 0.95)';
this.style.border = '0.5px solid #bdc3c7';
this.style.zIndex = '66';
});

label.textContent = `${from.toFixed(1)}`;
label.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
showAssayModal(assay);
};

assaysColumn.appendChild(label);
}
```

2. Исправленная функция createIntervalAssay (правильная анимация):

```javascript
function createIntervalAssay(assay, index, from, to, scale, assaysColumn, usedPointPositions, usedIntervalPositions, docType) {
const top = from * scale;
const height = Math.max((to - from) * scale, 2);

if (height < 6) {
return;
}

// Определяем стиль штриховки в зависимости от типа документации
const isPrimary = docType === 'primary';
const gradientAngle = isPrimary ? '45deg' : '-45deg';
const baseColor = isPrimary ? '#000000' : '#e74c3c';

const assayLeft = 10;
const labelLeft = 45;

// Проверяем конфликты с точечными пробами
const middle = top + height / 2;
const hasPointInInterval = Array.from(usedPointPositions).some(pointPos => {
return Math.abs(pointPos - middle) < 20;
});

let verticalOffset = 0;
let currentLabelLeft = labelLeft;

if (hasPointInInterval) {
if (height > 30) {
verticalOffset = 15;
} else {
currentLabelLeft += 20;
}
}

const intervalAssay = document.createElement('div');
intervalAssay.className = `assay-interval assay-${docType}`;
intervalAssay.setAttribute('data-assay-index', index);
intervalAssay.setAttribute('data-assay-type', 'interval');
intervalAssay.setAttribute('data-doc-type', docType);

// Сохраняем исходные значения для восстановления
const originalOpacity = isPrimary ? '0.3' : '0.6';
const originalZIndex = isPrimary ? '52' : '53';

intervalAssay.style.cssText = `
position: absolute;
top: ${top}px;
left: ${assayLeft}px;
width: 20px;
height: ${height}px;
z-index: ${originalZIndex};
cursor: pointer;
transition: all 0.3s ease;
border-radius: 2px;
background: repeating-linear-gradient(
${gradientAngle},
transparent,
transparent 3px,
${baseColor} 3px,
${baseColor} 6px
);
opacity: ${originalOpacity};
`;

// АНИМАЦИЯ ПРИ НАВЕДЕНИИ - ИСПРАВЛЕННАЯ
intervalAssay.addEventListener('mouseenter', function() {
this.style.opacity = '0.9';
this.style.boxShadow = '0 0 8px rgba(231, 76, 60, 0.6)';
this.style.zIndex = '55'; // ПОДНИМАЕМ, НО НИЖЕ ТОЧЕЧНЫХ
});

intervalAssay.addEventListener('mouseleave', function() {
this.style.opacity = originalOpacity; // ВОЗВРАЩАЕМ ИСХОДНУЮ ПРОЗРАЧНОСТЬ
this.style.boxShadow = 'none';
this.style.zIndex = originalZIndex; // ВОЗВРАЩАЕМ ИСХОДНЫЙ Z-INDEX
});

// ОБРАБОТЧИК КЛИКА
intervalAssay.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
console.log('Клик по интервальной пробе:', assay);
showAssayModal(assay);
};

assaysColumn.appendChild(intervalAssay);

// Создаем метку с учетом смещения
const labelPosition = Math.round((middle + verticalOffset) / 5) * 5;

let finalVerticalOffset = verticalOffset;
if (!isPrimary && usedIntervalPositions.has(labelPosition)) {
// Для итоговых проб ищем свободную позицию выше
for (let i = 1; i <= 3; i++) {
const testPosition = labelPosition - (i * 15);
if (!usedIntervalPositions.has(testPosition)) {
usedIntervalPositions.add(testPosition);
finalVerticalOffset = verticalOffset - (i * 15);
break;
}
}
} else {
usedIntervalPositions.add(labelPosition);
}

const label = document.createElement('div');
label.className = `assay-label assay-interval-label assay-${docType}-label`;

// Сохраняем исходные значения метки
const labelOriginalBorder = isPrimary ? '#bdc3c7' : '#e74c3c';
const labelOriginalColor = isPrimary ? '#2c3e50' : '#e74c3c';

label.style.cssText = `
position: absolute;
top: ${middle + finalVerticalOffset}px;
left: ${currentLabelLeft}px;
font-size: 8px;
font-weight: bold;
color: ${labelOriginalColor};
background: rgba(255, 255, 255, 0.95);
padding: 1px 4px;
border-radius: 2px;
z-index: 56; // МЕТКИ ИНТЕРВАЛОВ ВЫШЕ ПРОБ, НО НИЖЕ ТОЧЕК
white-space: nowrap;
border: 0.5px solid ${labelOriginalBorder};
transform: translateY(-10%);
min-width: 35px;
text-align: left;
transition: all 0.2s ease;
cursor: pointer;
`;

// Анимация для метки - ИСПРАВЛЕННАЯ
label.addEventListener('mouseenter', function() {
this.style.background = 'rgba(231, 76, 60, 0.1)';
this.style.border = '1px solid #e74c3c';
this.style.zIndex = '57';
});

label.addEventListener('mouseleave', function() {
this.style.background = 'rgba(255, 255, 255, 0.95)';
this.style.border = `0.5px solid ${labelOriginalBorder}`;
this.style.zIndex = '56';
});

if (Math.abs(to - from) < 0.05) {
label.textContent = `${from.toFixed(1)}-${from.toFixed(1)}`;
} else {
label.textContent = `${from.toFixed(1)}-${to.toFixed(1)}`;
}

label.onclick = function(e) {
e.stopPropagation();
e.preventDefault();
showAssayModal(assay);
};

assaysColumn.appendChild(label);
}
```

3. Обновленная структура z-index:

```
67 - метки точек (при наведении)
66 - метки точек (обычно)
65 - точки (при наведении)
60 - точки (обычно)
57 - метки интервалов (при наведении)
56 - метки интервалов (обычно)
55 - интервальные пробы (при наведении)
53 - итоговые интервальные пробы (обычно)
52 - первичные интервальные пробы (обычно)
49 - контур колонки проб
```

Основные исправления:

1. Точечные пробы:

· ✅ По центру колонки (left: 20px)
· ✅ Всегда выше интервальных (z-index: 60)
· ✅ При наведении поднимаются еще выше

2. Интервальные пробы:

· ✅ Исправлена анимация - теперь правильно возвращаются исходные значения
· ✅ Используются переменные для исходных значений opacity и z-index
· ✅ При наведении поднимаются, но остаются ниже точечных

3. Иерархия z-index:

· Точечные пробы > Интервальные пробы
· Метки > Пробы
· При наведении элементы временно поднимаются

Теперь все работает корректно: точки по центру и выше, анимация правильно восстанавливается!

Добавлено (2025-10-17, 13:52)
---------------------------------------------
Отличная идея! Сделаем отображение масштаба в картографическом формате. Вот исправленный код:

1. Обновляем глобальные переменные и функции масштабирования:

```javascript
// Добавляем картографические масштабы
const CARTOGRAPHIC_SCALES = [
{ value: 10, label: '1:500' }, // 10px/м = 1:500
{ value: 20, label: '1:250' }, // 20px/м = 1:250
{ value: 40, label: '1:125' }, // 40px/м = 1:125
{ value: 50, label: '1:100' }, // 50px/м = 1:100
{ value: 80, label: '1:62.5' }, // 80px/м = 1:62.5
{ value: 100, label: '1:50' } // 100px/м = 1:50
];

// Функция для получения картографического масштаба
function getCartographicScale(pxPerMeter) {
const scale = CARTOGRAPHIC_SCALES.find(s => s.value === pxPerMeter);
return scale ? scale.label : `1:${1000 / pxPerMeter}`;
}

// Функция для получения pxPerMeter из картографического масштаба
function getPxPerMeterFromCartographic(scaleLabel) {
const scale = CARTOGRAPHIC_SCALES.find(s => s.label === scaleLabel);
return scale ? scale.value : 40; // По умолчанию 1:125
}
```

2. Обновляем функцию adjustScale:

```javascript
function adjustScale(direction) {
// Находим текущий масштаб в массиве картографических масштабов
const currentIndex = CARTOGRAPHIC_SCALES.findIndex(s => s.value === window.currentScale);
let newIndex;

if (direction > 0) {
// Увеличиваем масштаб (приближаем)
newIndex = Math.min(CARTOGRAPHIC_SCALES.length - 1, currentIndex + 1);
} else {
// Уменьшаем масштаб (отдаляем)
newIndex = Math.max(0, currentIndex - 1);
}

if (newIndex !== currentIndex && newIndex >= 0) {
window.currentScale = CARTOGRAPHIC_SCALES[newIndex].value;
redrawCurrentStructure();
updateScaleIndicator();
}
}
```

3. Обновляем функцию resetScale:

```javascript
function resetScale() {
// Устанавливаем масштаб по умолчанию 1:125 (40px/м)
window.currentScale = 40;
redrawCurrentStructure();
updateScaleIndicator();
}
```

4. Обновляем функцию updateScaleIndicator:

```javascript
function updateScaleIndicator() {
const scaleIndicator = document.getElementById('scaleIndicator');
if (scaleIndicator) {
const cartographicScale = getCartographicScale(window.currentScale);
scaleIndicator.textContent = `Масштаб: ${cartographicScale}`;
}

// Также обновляем заголовок если есть
const titleElement = document.querySelector('#wellStructureDrawing h3');
if (titleElement) {
const cartographicScale = getCartographicScale(window.currentScale);
// Сохраняем остальную часть заголовка, меняем только масштаб
const currentText = titleElement.textContent;
const newText = currentText.replace(/Масштаб:.*?(?=•|$)/, `Масштаб: ${cartographicScale} `);
titleElement.textContent = newText;
}
}
```

5. Обновляем функцию создания элементов управления масштабом:

```javascript
// В функции создания панели управления заменяем создание кнопок масштаба
function createScaleControls() {
const scaleContainer = document.createElement('div');
scaleContainer.style.cssText = `
display: flex;
align-items: center;
gap: 8px;
margin-left: 15px;
`;

// Кнопка уменьшения масштаба
const zoomOutBtn = createScaleButton('−', 'Уменьшить масштаб', () => adjustScale(-1));

// Индикатор масштаба
const scaleIndicator = document.createElement('div');
scaleIndicator.id = 'scaleIndicator';
scaleIndicator.style.cssText = `
padding: 6px 12px;
background: #3498db;
color: white;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
min-width: 80px;
text-align: center;
`;

// Кнопка увеличения масштаба
const zoomInBtn = createScaleButton('+', 'Увеличить масштаб', () => adjustScale(1));

// Кнопка сброса масштаба
const resetBtn = createScaleButton('⎌', 'Сбросить масштаб', resetScale);
resetBtn.style.background = '#e74c3c';

scaleContainer.appendChild(zoomOutBtn);
scaleContainer.appendChild(scaleIndicator);
scaleContainer.appendChild(zoomInBtn);
scaleContainer.appendChild(resetBtn);

// Инициализируем индикатор
updateScaleIndicator();

return scaleContainer;
}
```

6. Обновляем функцию redrawCurrentStructure:

```javascript
function redrawCurrentStructure() {
console.log('Перерисовка текущей конструкции с масштабом:',
`${window.currentScale}px/м (${getCartographicScale(window.currentScale)})`);

// Пересчитываем размеры паттернов для нового масштаба
const patternSize = getPatternSize();
console.log(`Новый размер паттернов: ${patternSize.width}x${patternSize.height}px`);

// Получаем активный тип документации
const activeButton = document.querySelector('#wellStructureDrawing [data-doc-type].active');
const activeDocType = activeButton ? activeButton.getAttribute('data-doc-type') : 'primary';
console.log('Активный тип документации:', activeDocType, 'Режим отображения:', window.displayMode);

// Обновляем через updateWellStructureByDocType с учетом масштаба и режима
updateWellStructureByDocType(activeDocType);
}
```

7. Обновляем функцию updateWellStructureByDocType:

```javascript
function updateWellStructureByDocType(docType) {
console.log('Обновление конструкции для документации:', docType,
`масштаб: ${window.currentScale}px/м (${getCartographicScale(window.currentScale)})`);

// ОБНОВЛЯЕМ ЗАГОЛОВОК С КАРТОГРАФИЧЕСКИМ МАСШТАБОМ
const titleElement = document.querySelector('#wellStructureDrawing h3');
if (titleElement) {
// Генерируем данные для выбранного типа документации чтобы получить актуальную глубину
const structureData = generateWellStructureData(docType);
const depth = structureData ? structureData.totalDepth : 0;
const docTypeNames = {
'primary': 'Первичное',
'final': 'Итоговое',
'gis': 'ГИС'
};
const cartographicScale = getCartographicScale(window.currentScale);
titleElement.textContent = `Конструкция скважины • Глубина: ${depth} м • ${docTypeNames[docType] || docType} • Масштаб: ${cartographicScale}`;
}

// Обновляем заголовок в правой панели
const infoHeader = document.getElementById('docIntervalsHeader');
if (infoHeader) {
const docTypeNames = {
'primary': 'Первичное',
'final': 'Итоговое',
'gis': 'ГИС'
};
const titleSpan = infoHeader.querySelector('span');
if (titleSpan) {
titleSpan.textContent = `Интервалы документирования (${docTypeNames[docType] || docType})`;
}
}

// Генерируем данные для выбранного типа документации
const structureData = generateWellStructureData(docType);
if (structureData) {
console.log('✅ Данные для', docType, 'успешно сгенерированы:', structureData);
// Перерисовываем чертеж с текущим масштабом
drawWellStructure(structureData, window.currentScale);
// Обновляем карточки
updateGeologyCards(docType);
} else {
console.log('❌ Нет данных для типа документации:', docType);
// Очищаем контейнеры
const graphicsScrollContainer = document.getElementById('graphicsScrollContainer');
const cardsContainer = document.getElementById('cardsContainer');
if (graphicsScrollContainer) graphicsScrollContainer.innerHTML = '<div style="text-align: center; padding: 40px; color: #95a5a6;">Нет данных документирования</div>';
if (cardsContainer) cardsContainer.innerHTML = '<div style="text-align: center; padding: 40px; color: #95a5a6;">Нет данных документирования</div>';
}
}
```

8. Инициализация по умолчанию:

```javascript
// В основном коде инициализации устанавливаем масштаб по умолчанию
window.currentScale = 40; // 1:125
```

Результат:

Теперь масштаб будет отображаться в картографическом формате:

· 1:500 - самый мелкий масштаб
· 1:250
· 1:125 - по умолчанию
· 1:100
· 1:62.5
· 1:50 - самый крупный масштаб

Пользователи будут видеть привычные картографические обозначения, а не технические пиксели на метр!

4444
Прикрепления:
2_43_5.zip (219.3 Kb)
Проблема в том, что функция getStratigraphyBackground ищет совпадения в SPECIFIC_STRATIGRAPHY_COLORS уже после конвертации текста, а нужно искать в оригинальном виде. Вот исправленная функция:

```javascript
function getStratigraphyBackground(stratigraphy) {
if (!stratigraphy || stratigraphy === 'Не указано') {
return '#ffffff';
}

// Сохраняем оригинальный текст для поиска в специфичных цветах
const originalStratigraphy = stratigraphy;
const convertedStratigraphy = convertStratigraphyText(stratigraphy || 'Не указано');
const lowerStratigraphy = convertedStratigraphy.toLowerCase();
const lowerOriginalStratigraphy = originalStratigraphy.toLowerCase();

console.log('Раскраска стратиграфии:', {
original: originalStratigraphy,
converted: convertedStratigraphy,
lower: lowerStratigraphy,
lowerOriginal: lowerOriginalStratigraphy
});

// 1. Сначала проверяем SPECIFIC_STRATIGRAPHY_COLORS на ОРИГИНАЛЬНЫЙ текст (самый высокий приоритет)
if (typeof SPECIFIC_STRATIGRAPHY_COLORS !== 'undefined') {
// Точное совпадение в специфичных цветах по ОРИГИНАЛЬНОМУ тексту
if (SPECIFIC_STRATIGRAPHY_COLORS[lowerOriginalStratigraphy]) {
console.log('✅ Найдено ТОЧНОЕ совпадение в специфичных цветах (оригинал):', lowerOriginalStratigraphy, SPECIFIC_STRATIGRAPHY_COLORS[lowerOriginalStratigraphy]);
return SPECIFIC_STRATIGRAPHY_COLORS[lowerOriginalStratigraphy];
}

// Частичные совпадения в специфичных цветах по ОРИГИНАЛЬНОМУ тексту
const specificKeys = Object.keys(SPECIFIC_STRATIGRAPHY_COLORS)
.sort((a, b) => b.length - a.length); // Сначала самые длинные ключи

for (const key of specificKeys) {
// Улучшенный поиск частичных совпадений по ОРИГИНАЛЬНОМУ тексту
if (lowerOriginalStratigraphy.includes(key) ||
key.includes(lowerOriginalStratigraphy) ||
findSimilarMatch(lowerOriginalStratigraphy, key)) {
console.log('✅ Найдено частичное совпадение в специфичных цветах (оригинал):', key, SPECIFIC_STRATIGRAPHY_COLORS[key]);
return SPECIFIC_STRATIGRAPHY_COLORS[key];
}
}
}

// 2. Затем проверяем стандартные цвета по КОНВЕРТИРОВАННОМУ тексту
const stratigraphyColors = {
// Четвертичная система (Quaternary) - Светло-желтый
'четвертич': '#FEF0C5',
'q': '#FEF0C5',
'q1': '#FEF0C5',
'q2': '#FEF0C5',
'q3': '#FEF0C5',
'q4': '#FEF0C5',
'голоцен': '#FEF0C5',
'плейстоцен': '#FEF0C5',
'qiv': '#FEF0C5',
'qv': '#FEF0C5',
// Неогеновая система (Neogene) - Желто-коричневый
'неоген': '#F8F5DB',
'n': '#F8F5DB',
'n1': '#F8F5DB',
'n2': '#FEF0C5',
'плиоцен': '#F8F5DB',
'миоцен': '#F8F5DB',
'n1-2': '#F8F5DB',
// Палеогеновая система (Paleogene) - Светло-зеленый
'палеоген': '#FFE7C9',
'p': '#FFE7C9',
'p1': '#FFE7C9',
'p2': '#FFE7C9',
'p3': '#FFE7C9',
'олигоцен': '#FFE7C9',
'эоцен': '#FFE7C9',
'палеоцен': '#FFE7C9',
'p2-3': '#FFE7C9',
// Меловая система (Cretaceous) - Зеленый
'мел': '#E4F8C5',
'k': '#E4F8C5',
'k1': '#E4F8C5',
'k2': '#E4F8C5',
'верхний мел': '#E4F8C5',
'нижний мел': '#E4F8C5',
'k1-2': '#E4F8C5',
// Юрская система (Jurassic) - Синий
'юр': '#DCE5F8',
'j': '#DCE5F8',
'j1': '#DCE5F8',
'j2': '#DCE5F8',
'j3': '#DCE5F8',
'верхняя юра': '#DCE5F8',
'средняя юра': '#DCE5F8',
'нижняя юра': '#DCE5F8',
'j1-3': '#DCE5F8',
// Триасовая система (Triassic) - Фиолетовый
'триас': '#EED2F5',
't': '#EED2F5',
't1': '#EED2F5',
't2': '#EED2F5',
't3': '#EED2F5',
'верхний триас': '#EED2F5',
'средний триас': '#EED2F5',
'нижний триас': '#EED2F5',
't1-3': '#EED2F5',
// Пермская система (Permian) - Красно-коричневый
'перм': '#FFE2C1',
'p': '#FFE2C1',
'p1': '#FFE2C1',
'p2': '#FFE2C1',
'верхняя пермь': '#FFE2C1',
'нижняя пермь': '#FFE2C1',
'p1-2': '#FFE2C1',
// Каменноугольная система (Carboniferous) - Серый
'карбон': '#BBBBBC',
'c': '#BBBBBC',
'c1': '#BBBBBC',
'c2': '#BBBBBC',
'c3': '#BBBBBC',
'верхний карбон': '#BBBBBC',
'средний карбон': '#BBBBBC',
'нижний карбон': '#BBBBBC',
'c1-3': '#BBBBBC',
// Девонская система (Devonian) - Коричневый
'девон': '#F0C39D',
'd': '#F0C39D',
'd1': '#F0C39D',
'd2': '#F0C39D',
'd3': '#F0C39D',
'верхний девон': '#F0C39D',
'средний девон': '#F0C39D',
'нижний девон': '#F0C39D',
'd1-3': '#F0C39D',
// Силурийская система (Silurian) - Оливковый
'силур': '#E0E6AB',
's': '#E0E6AB',
's1': '#E0E6AB',
's2': '#E0E6AB',
'верхний силур': '#E0E6AB',
'нижний силур': '#E0E6AB',
's1-2': '#E0E6AB',
// Ордовикская система (Ordovician) - Голубой
'ордовик': '#AEE7D4',
'o': '#AEE7D4',
'o1': '#AEE7D4',
'o2': '#AEE7D4',
'o3': '#AEE7D4',
'верхний ордовик': '#AEE7D4',
'средний ордовик': '#AEE7D4',
'нижний ордовик': '#AEE7D4',
'o1-3': '#AEE7D4',
// Кембрийская система (Cambrian) - Светло-голубой (по стандарту ВСЕГЕИ)
'кембрий': '#A8D2CD',
'є': '#A8D2CD',
'ε': '#A8D2CD',
'g': '#A8D2CD',
'g1': '#A8D2CD',
'g2': '#A8D2CD',
'g3': '#A8D2CD',
'g1hl': '#A8D2CD',
'g2hl': '#A8D2CD',
'g3hl': '#A8D2CD',
'ghl': '#A8D2CD',
'верхний кембрий': '#A8D2CD',
'средний кембрий': '#A8D2CD',
'нижний кембрий': '#A8D2CD',
'кембрийский': '#A8D2CD',
'кембрийская': '#A8D2CD',
'кембрийское': '#A8D2CD',
// Докембрий (Precambrian) - Темные тона
'докембрий': '#9e9e9e',
'протерозой': '#9e9e9e',
'архей': '#9e9e9e',
'рифей': '#9e9e9e',
'венд': '#9e9e9e',
'pr': '#9e9e9e',
'pt': '#9e9e9e',
'ar': '#9e9e9e',
// венд
'венд': '#FFDAD6',
'v': '#FFDAD6',
'v1': '#FFDAD6',
'v2': '#FFDAD6',
// рифей
'рифей': '#FF8C71',
'rf': '#FF8C71',
'rf1': '#FF8C71',
'rf2': '#FF8C71',
'rf3': '#FF8C71',
// архей
'архей': '#F4BDE3',
'ar': '#F4BDE3',
'ar1': '#F4BDE3',
'ar2': '#F4BDE3',
'lp': '#F4BDE3'
};

// Точное совпадение в стандартных цветах по КОНВЕРТИРОВАННОМУ тексту
if (stratigraphyColors[lowerStratigraphy]) {
console.log('✅ Найдено ТОЧНОЕ совпадение в стандартных цветах (конвертированный):', lowerStratigraphy, stratigraphyColors[lowerStratigraphy]);
return stratigraphyColors[lowerStratigraphy];
}

// Частичные совпадения в стандартных цветах по КОНВЕРТИРОВАННОМУ тексту
const standardKeys = Object.keys(stratigraphyColors)
.sort((a, b) => b.length - a.length); // Сначала самые длинные ключи

for (const key of standardKeys) {
// Улучшенный поиск частичных совпадений по КОНВЕРТИРОВАННОМУ тексту
if (lowerStratigraphy.includes(key) ||
key.includes(lowerStratigraphy) ||
findSimilarMatch(lowerStratigraphy, key)) {
console.log('✅ Найдено частичное совпадение в стандартных цветах (конвертированный):', key, stratigraphyColors[key]);
return stratigraphyColors[key];
}
}

// 3. Геологические индексы (одиночные буквы) - как запасной вариант
if (/^[a-zа-яё]\d*[a-zа-яё]*$/i.test(lowerOriginalStratigraphy)) {
const firstChar = lowerOriginalStratigraphy.charAt(0);
const geologicalIndices = {
// Российская система обозначений по ВСЕГЕИ
'g': '#A8D2CD', // Кембрий (голубой)
'є': '#A8D2CD', // Кембрий (кириллица)
'ε': '#A8D2CD', // Кембрий (греческий)
'o': '#AEE7D4', // Ордовик (голубой)
's': '#E0E6AB', // Силур (оливковый)
'd': '#F0C39D', // Девон (коричневый)
'c': '#BBBBBC', // Карбон (серый)
'p': '#FFE2C1', // Пермь (красный)
't': '#EED2F5', // Триас (фиолетовый)
'j': '#DCE5F8', // Юра (синий)
'k': '#E4F8C5', // Мел (зеленый)
'n': '#F8F5DB', // Неоген (бежевый)
'q': '#FEF0C5', // Четвертичный (желтый)
'r': '#9e9e9e', // Докембрий
'v': '#9e9e9e' // Венд
};
if (geologicalIndices[firstChar]) {
console.log('✅ Найдено по геологическому индексу (оригинал):', firstChar, geologicalIndices[firstChar]);
return geologicalIndices[firstChar];
}
}

console.log('❌ Не найдено совпадений, используется цвет по умолчанию');
return '#ffffff';
}
```

Основные изменения:

1. Сохраняем оригинальный текст: Добавлена переменная originalStratigraphy для хранения текста до конвертации.
2. Поиск в SPECIFIC_STRATIGRAPHY_COLORS по оригинальному тексту: Теперь поиск в специфичных цветах происходит по lowerOriginalStratigraphy (неконвертированный текст в нижнем регистре).
3. Поиск в стандартных цветах по конвертированному тексту: В стандартных цветах поиск остается по lowerStratigraphy (конвертированный текст).
4. Геологические индексы по оригинальному тексту: Поиск геологических индексов также происходит по оригинальному тексту.

Теперь функция будет правильно находить индексы вроде t1hh в SPECIFIC_STRATIGRAPHY_COLORS, так как поиск происходит до конвертации текста.

Добавлено (2025-10-21, 15:33)
---------------------------------------------
Проблема в том, что стратиграфия уже приходит в конвертированном виде из функции createMergedGeologyFromDocumentation. Нужно исправить эту функцию, чтобы она сохраняла оригинальный текст стратиграфии.

Вот исправленная функция createMergedGeologyFromDocumentation:

```javascript
function createMergedGeologyFromDocumentation(docType = 'primary') {
let documentation;
switch (docType) {
case 'primary':
documentation = xmlData.primaryDocumentation;
break;
case 'final':
documentation = xmlData.finalDocumentation;
break;
case 'gis':
documentation = xmlData.gisDocumentation;
break;
default:
documentation = xmlData.primaryDocumentation;
}

if (!documentation || documentation.length === 0) return [];

// Группируем интервалы по литологии, но сохраняем все исходные данные включая LCODE
const groupedIntervals = [];
let currentGroup = null;

documentation.forEach((interval, index) => {
// СОХРАНЯЕМ ОРИГИНАЛЬНУЮ СТРАТИГРАФИЮ ДО КОНВЕРТАЦИИ
const originalStratigraphy = interval.stratigraphy || 'Не указано';
const convertedStratigraphy = convertStratigraphyText(originalStratigraphy);

// Если это первый интервал или литология изменилась - создаем новую группу
if (!currentGroup ||
currentGroup.lithology !== interval.lithology ||
currentGroup.lithologyId !== interval.lithologyId) {

if (currentGroup) {
groupedIntervals.push(currentGroup);
}

currentGroup = {
from: interval.from,
to: interval.to,
lithology: interval.lithology,
lithologyId: interval.lithologyId, // ✅ Сохраняем ID литологии
lcode: interval.lcode, // ✅ Сохраняем LCODE
stratigraphy: convertedStratigraphy,
originalStratigraphy: originalStratigraphy, // ✅ СОХРАНЯЕМ ОРИГИНАЛ ДЛЯ ПОИСКА ЦВЕТОВ
intervals: [{
from: interval.from,
to: interval.to,
description: interval.description,
numberPP: interval.numberPP,
thickness: interval.thickness
}],
totalThickness: interval.thickness
};
} else {
// Продолжаем текущую группу
currentGroup.to = interval.to;
currentGroup.totalThickness += interval.thickness;
currentGroup.intervals.push({
from: interval.from,
to: interval.to,
description: interval.description,
numberPP: interval.numberPP,
thickness: interval.thickness
});
}
});

// Добавляем последнюю группу
if (currentGroup) {
groupedIntervals.push(currentGroup);
}

// Логируем результат для отладки
console.log('📊 Сгруппированные интервалы с LCODE и оригинальной стратиграфией:');
groupedIntervals.forEach((group, index) => {
console.log(` ${index + 1}. "${group.lithology}" - LCODE: ${group.lcode}, Стратиграфия (оригинал): "${group.originalStratigraphy}", (конвертированная): "${group.stratigraphy}"`);
});

return groupedIntervals;
}
```

И теперь обновим функцию getStratigraphyBackground, чтобы она использовала оригинальную стратиграфию из объекта:

```javascript
function getStratigraphyBackground(geoItem) {
// geoItem теперь может быть как строкой, так и объектом с полями stratigraphy и originalStratigraphy
let stratigraphy, originalStratigraphy;

if (typeof geoItem === 'object' && geoItem !== null) {
// Если передали объект (из createMergedGeologyFromDocumentation)
stratigraphy = geoItem.stratigraphy;
originalStratigraphy = geoItem.originalStratigraphy || geoItem.stratigraphy;
} else {
// Если передали строку (старый вариант)
stratigraphy = geoItem;
originalStratigraphy = geoItem;
}

if (!stratigraphy || stratigraphy === 'Не указано') {
return '#ffffff';
}

const lowerStratigraphy = stratigraphy.toLowerCase();
const lowerOriginalStratigraphy = originalStratigraphy.toLowerCase();

console.log('Раскраска стратиграфии:', {
original: originalStratigraphy,
converted: stratigraphy,
lowerOriginal: lowerOriginalStratigraphy,
lowerConverted: lowerStratigraphy
});

// 1. Сначала проверяем SPECIFIC_STRATIGRAPHY_COLORS на ОРИГИНАЛЬНЫЙ текст
if (typeof SPECIFIC_STRATIGRAPHY_COLORS !== 'undefined') {
// Точное совпадение в специфичных цветах по ОРИГИНАЛЬНОМУ тексту
if (SPECIFIC_STRATIGRAPHY_COLORS[lowerOriginalStratigraphy]) {
console.log('✅ Найдено ТОЧНОЕ совпадение в специфичных цветах (оригинал):', lowerOriginalStratigraphy, SPECIFIC_STRATIGRAPHY_COLORS[lowerOriginalStratigraphy]);
return SPECIFIC_STRATIGRAPHY_COLORS[lowerOriginalStratigraphy];
}

// Частичные совпадения в специфичных цветах по ОРИГИНАЛЬНОМУ тексту
const specificKeys = Object.keys(SPECIFIC_STRATIGRAPHY_COLORS)
.sort((a, b) => b.length - a.length);

for (const key of specificKeys) {
if (lowerOriginalStratigraphy.includes(key) ||
key.includes(lowerOriginalStratigraphy) ||
findSimilarMatch(lowerOriginalStratigraphy, key)) {
console.log('✅ Найдено частичное совпадение в специфичных цветах (оригинал):', key, SPECIFIC_STRATIGRAPHY_COLORS[key]);
return SPECIFIC_STRATIGRAPHY_COLORS[key];
}
}
}

// Остальной код функции остается таким же...
// [здесь идет стандартная логика поиска по конвертированному тексту]

console.log('❌ Не найдено совпадений, используется цвет по умолчанию');
return '#ffffff';
}
```

И обновим вызовы getStratigraphyBackground в функции drawGeologyColumn:

```javascript
// В функции drawGeologyColumn замените:
const borderColor = getStratigraphyBackground(group.stratigraphy);
// на:
const borderColor = getStratigraphyBackground(group);

// и:
visualElement.style.backgroundColor = getStratigraphyBackground(group.stratigraphy);
// на:
visualElement.style.backgroundColor = getStratigraphyBackground(group);
```

Также обновите вызов в функции createGeologyCard:

```javascript
// В функции createGeologyCard замените:
const borderColor = getStratigraphyBackground(group.stratigraphy);
// на:
const borderColor = getStratigraphyBackground(group);
```

Теперь функция будет получать оригинальную стратиграфию до конвертации и сможет правильно найти совпадения в SPECIFIC_STRATIGRAPHY_COLORS.

55555
Прикрепления:
2_43_8.rar (489.5 Kb)
0000
Прикрепления:
1061047.rar (491.4 Kb)
ыыыыы
Прикрепления:
Ttt
Прикрепления:
ggggggg.zip (7.1 Kb)
Аапа
Прикрепления:
222.zip (9.3 Kb)
1122233
Прикрепления:
wwwww
Прикрепления:
2_43_13.zip (461.6 Kb)
Поиск:
Новый ответ
Имя:
Текст сообщения: