Программная работа с графическими схемами. Готовое решение

Программирование - Инструментарий

Графическая схема; Программное изменение графической схемы; Построение графической схемы

37
Работоспособное, проверенное на практике, простое и удобное программное управление графическими схемами.

После множества публикаций, где идея была продемонстрирована, развита и обсуждена, мне по работе довелось-таки создать полноценное решение и погонять его всерьёз. Останавливаться на подробностях самой идеи не буду, она высказана много где (начиная с //1c-soft.it-terminal.ru/public/320691/ и заканчивая моей предыдущей). Концепцию взял у //1c-soft.it-terminal.ru/public/551576/ и допилил под свой извращённый вкус. Здесь просто выкладываю работающий исходник и обработку, позволяющую всё это эксплуатировать уже на уровне шаблона решения. То есть, можно взять обработку и довольно легко "допилить" под свои нужды. Собственно, она и делалась для себя любимого как универсал)

Обработка предусматривает просмотр, построение, изменение схем согласно любым исходным данным. Там есть специальные шлюзы-интерфейсы, куда можно подключить собственную конкретную механику. Я не стал особенно заморачиваться красотой расположения фигур на схеме - желающие могут почитать теорию графов сами или адаптировать публикацию Ильдаровича, я сделал лишь "квадратно-гнездовое" расположение (с возможностью группировки фигур в блоки или без) и "типа-древовидное", с распихиванием фигур по уровням сначала по длине пути к ним в графе, а потом по незанятым областям.

Фигуры размещаются каждая в свою "зону", линии отрисовываются автоматом. Можно показывать одиночные фигуры. Можно обрабатывать события схемы и её элементов. Для графов со многими исходящими есть варианты показа. Есть удобные визуальные прибамбасы. Форма работает в режиме просмотра и правки схемы.

Также в коде обработки предусмотрен вызов при открытии; есть механизмы работы с таблицей вершин, таблицей рёбер, матрицей переходов - кому что окажется удобнее. Ещё есть механизм отслеживания изменений и их индикации, сравнения данных из БД с данными из самой схемы, схема умеет записываться с сохранением всех внутренних данных и восстанавливаться из ранее записанного. Обработка на УФ для несинхрона.

Часть работы идёт в технике управления xml-json, часть уже в объектной технике графической схемы.

Код снабжён пояснениями, поэтому тут особенно много расписывать не буду. Если у кого возникнут вопросы - постараюсь в теме более подробно рассказать, т.к. заранее вообще не представляю, кого что заинтересует.

Всё сделано сугубо штатными возможностями 1С и промышленно используется уже несколько месяцев. Замечены несколько случаев вроде бы валидного наполнения, которые десериализатор не любит вплоть до того, что падает платформа, но никакой системы в них выявить не удалось. При необходимости также опишу подробнее.

У формы обработки есть встроенная справка, правда, в основном с точки зрения пользователя, но основные фишки там описаны.

Ещё раз подчеркну, что "готовым решением" является в первую очередь код управления схемой, а обработка - это шаблон, заготовка без заточки под конкретные нужды.

 

Пример вызова (а вообще их много в обработке):        

рИмяФигуры="Фигура1";
        рТипЭлемента=Тип("ЭлементГрафическойСхемыДействие");
        рСхема=Обработки.РаботаСГрафическойСхемой.ИнициализироватьСхему();
        
        эгсФигура=Обработки.РаботаСГрафическойСхемой.ИнициализироватьЭлементСхемы(рСхема,рТипЭлемента,рИмяФигуры);
        Если эгсФигура=Неопределено Тогда Возврат КонецЕсли;
        //эгсФигура.Вставить("explanation",рИмяФигуры);
        //
        рКоординаты=Новый Структура;
        рКоординаты.Вставить("Лево",40);
        рКоординаты.Вставить("Верх",40);
        рКоординаты.Вставить("Ширина",300);
        рКоординаты.Вставить("Высота",100);
        Обработки.РаботаСГрафическойСхемой.УстановитьКоординатыЭлементаСхемы(эгсФигура,рКоординаты);
        //
        Обработки.РаботаСГрафическойСхемой.УстановитьЗаголовокЭлементаСхемы(эгсФигура,"Некий заголовок");
        //
        идФигуры=Обработки.РаботаСГрафическойСхемой.ДобавитьЭлементВСхему(рСхема,эгсФигура);
        Если не ЗначениеЗаполнено(идФигуры) Тогда Возврат КонецЕсли;
        
        Если выбзнч.Значение<>Тип("ЭлементГрафическойСхемыДекорация") Тогда
            рИмяЛинии="Линия1";
            эгсЛиния=Обработки.РаботаСГрафическойСхемой.ИнициализироватьЭлементСхемы(рСхема,Тип("ЭлементГрафическойСхемыСоединительнаяЛиния"),рИмяЛинии);
            //
            рПараметры=Новый Структура("Схема",рСхема);
            рПараметры.Вставить("Начало",идФигуры);
            //рПараметры.Вставить("Конец",0); // стрелка не ведёт никуда
            рПараметры.Вставить("НачалоСторона",ТипСтороныЭлементаГрафическойСхемы.Низ);
            Обработки.РаботаСГрафическойСхемой.УстановитьКоординатыЭлементаСхемы(эгсЛиния,рПараметры);
            //
            идЛинии=Обработки.РаботаСГрафическойСхемой.ДобавитьЭлементВСхему(рСхема,эгсЛиния);
            //Если не ЗначениеЗаполнено(идЛинии) Тогда            
        КонецЕсли;
        
        гс=Обработки.РаботаСГрафическойСхемой.СобратьГрафическуюСхему(рСхема);


Собственно код самой главной исполняемой части:

#Область УправлениеГрафическойСхемой

#Область ОбщееСлужебное

// Возвращает наибольшее числовое значение свойства с именем рКлюч среди значений свойств элементов массива.
// Нечисловые значения свойств игнорируются.
// При ошибке возвращает 0.
//
// Параметры:
//    мДляПоиска - массив коллекций, в свойствах элементов которого ведётся рассмотрение;
//    рКлюч - имя свойства, чьи значения рассматриваются;
//    рСтартовоеЗначение - число, значение, начиная с которого ведётся рассмотрение.
//
Функция ПолучитьМаксимальноеЗначениеСвойства(мДляПоиска,рКлюч,рСтартовоеЗначение=0)
Попытка
    максЗначение=рСтартовоеЗначение;
    Для каждого знч из мДляПоиска Цикл
        рЗначение=СоотСвойство(знч,рКлюч,"Число");
        Попытка максЗначение=Макс(максЗначение,СоотСвойство(знч,рКлюч,"Число")) Исключение КонецПопытки;
    КонецЦикла;
    Возврат максЗначение;
Исключение
    Возврат 0;
КонецПопытки;
КонецФункции

// Возвращает элемент массива, значение которого по указанному ключу имеет указанное значение.
// При ошибке или ненахождении возвращает Неопределено.
//
// Параметры:
//    мДляПоиска - массив коллекций, в свойствах элементов которого ведётся рассмотрение;
//    рКлюч - имя свойства, чьи значения рассматриваются;
//    рИскомое - образец (значение любого типа), на равенство которому рассматриваются значения коллекций.
//
Функция НайтиПоЗначениюКлючаВМассивеСтруктур(мДляПоиска,рКлюч,рИскомое)
Попытка    
    рЧисло=?(ТипЗнч(рИскомое)=Тип("Число"),"Число","");
    Для каждого знч Из мДляПоиска Цикл
        Если СоотСвойство(знч,рКлюч,рЧисло)=рИскомое Тогда Возврат знч КонецЕсли;
    КонецЦикла;
    Возврат Неопределено;
Исключение
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Используется в первичной и вторичной механике для определения, чем будет коллекция - структурой или соответствием. По умолчанию структура.
Функция НовыйСоответствиеСтруктура()
    Возврат Новый Структура;
КонецФункции

// Получает строковое представление значения свойства элемента ГС по имени этого свойства. Используется в первичной и вторичной механике.
// Если напрямую не указан нужный тип, то число и дата преобразовываются в строку (краевые пробелы НЕ обрезаются),
// в остальных случаях возвращается значение без преобразования типов.
// При ошибке возвращает Неопределено.
//
// Параметры:
//    рЭлемент - структура или соответствие, описывающее элемент схемы;
//    рИмяСвойства - строка, имя свойства как ключ;
//    рНужныйТип - строка, строковое представление того типа, который должен быть у результата (обычно "Число", "Дата" итд).
//
Функция СоотСвойство(рЭлемент,рИмяСвойства,рНужныйТип="") Экспорт
    рТипЭлемента=ТипЗнч(рЭлемент);
    Попытка
        Если рТипЭлемента=Тип("Структура") Тогда
            рЗначение=Неопределено;
            Если не рЭлемент.Свойство(рИмяСвойства,рЗначение) Тогда Возврат "" КонецЕсли;
        ИначеЕсли рТипЭлемента=Тип("Соответствие") Тогда
            рЗначение=рЭлемент.Получить(рИмяСвойства);
            Если рЗначение=Неопределено Тогда Возврат "" КонецЕсли;
        Иначе
            Сообщить("В получение свойства передан объект типа "+Строка(рТипЭлемента)+", он не может быть обработан!");
            Возврат "";
        КонецЕсли;
    Исключение
        Сообщить("Ошибка при получении свойства "+рИмяСвойства+": "+ОписаниеОшибки());
        Возврат "";
    КонецПопытки;
    //
    рТип=ТипЗнч(рЗначение);
    Если не ПустаяСтрока(рНужныйТип) и рТип=Тип(рНужныйТип) Тогда
        // преобразовывать тип не надо
    Иначе
        Если рТип=Тип("Число") Тогда
            рЗначение=Формат(рЗначение,"ЧГ=0");
        ИначеЕсли рТип=Тип("Дата") Тогда
            рЗначение=Формат(рЗначение,"ДФ=ггггММддччммсс");
        ИначеЕсли рТип=Тип("Строка") Тогда
            //рЗначение=СокрЛП(рЗначение); // БЕЗ обрезки пробелов!
        Иначе
            // преобразовывать тип не надо
        КонецЕсли;        
    КонецЕсли;    
    Возврат рЗначение;
КонецФункции

#КонецОбласти


#Область ДействияПервичногоПостроения

// Действия первичного построения необходимы для построения схемы на основе атомарных данных, компонуют коллекцию коллекций свойств объектов схемы,
// которая затем будет через JSON преобразована в строку и десериализована в объект "ГрафическаяСхема".
// Рекомендуется ИнициализироватьСхему, затем создать нужные элементы, используя ИнициализироватьЭлементСхемы, УстановитьКоординатыЭлементаСхемы
// и УстановитьЗаголовокЭлементаСхемы, затем СобратьГрафическуюСхему.
// Обычно при первичном построении большинство коллекций это структуры, а не соответствия.
// Важно! ИнициализироватьСхему это единственная функция, которая применима только на начальном этапе, т.к. работает всегда со структурой, а не соответствием.

// Возвращает соответствие с одним элементом #value, типа Структура; состав структуры аналогичен сериализованным свойствам графической схемы.
// При ошибке возвращает Неопределено.
//
// Параметры:
//    рПараметры - структура, описывающая настройки схемы; необязательный, если не указан, схема создаётся с настройками по умолчанию;
//    параметры можно заполнять из данных уже имеющейся ГС с помощью ЗаполнитьЗначенияСвойств.
//    Ключи структуры:
//        ИспользоватьСетку (булево), по умолчанию Истина;
//        РежимОтрисовкиСетки (свойство ГС), по умолчанию Линии;
//        ГоризонтальныйШагСетки (число), по умолчанию 20;
//        ВертикальныйШагСетки (число), по умолчанию 20.
//
Функция ИнициализироватьСхему(рПараметры=Неопределено) Экспорт
Попытка
    Если ТипЗнч(рПараметры)<>Тип("Структура") Тогда рПараметры=Новый Структура КонецЕсли;
    
    рИспользоватьСетку=?(рПараметры.Свойство("ИспользоватьСетку"),рПараметры.ИспользоватьСетку,Истина);
    рРежимОтрисовкиСетки=?(рПараметры.Свойство("РежимОтрисовкиСетки"),рПараметры.РежимОтрисовкиСетки,РежимОтрисовкиСеткиГрафическойСхемы.Линии);
    рГоризонтальныйШагСетки=?(рПараметры.Свойство("ГоризонтальныйШагСетки"),рПараметры.ГоризонтальныйШагСетки,20);
    рВертикальныйШагСетки=?(рПараметры.Свойство("ВертикальныйШагСетки"),рПараметры.ВертикальныйШагСетки,20);
    //
    Если рИспользоватьСетку Тогда
        Если рРежимОтрисовкиСетки=РежимОтрисовкиСеткиГрафическойСхемы.Линии Тогда
            рОтрисовка="Lines";
        ИначеЕсли рРежимОтрисовкиСетки=РежимОтрисовкиСеткиГрафическойСхемы.Точки Тогда
            рОтрисовка="Dots";
        ИначеЕсли рРежимОтрисовкиСетки=РежимОтрисовкиСеткиГрафическойСхемы.ШахматнаяСетка Тогда
            рОтрисовка="Chess";
        Иначе
            рОтрисовка="None";
        КонецЕсли;
    Иначе
        рОтрисовка="None";
    КонецЕсли;    
    
    струСхема=Новый Структура;
    струСхема.Вставить("backColor", Новый Соответствие);
    струСхема.backColor.Вставить("#type", "jv8ui:Color");
    струСхема.backColor.Вставить("#value", "{http://v8.1c.ru/8.1/data/ui/style}FieldBackColor");
    струСхема.Вставить("enableGrid",рИспользоватьСетку);
    струСхема.Вставить("drawGridMode",рОтрисовка);
    струСхема.Вставить("gridHorizontalStep",рГоризонтальныйШагСетки);
    струСхема.Вставить("gridVerticalStep",рВертикальныйШагСетки);
    струСхема.Вставить("bpUUID", "00000000-0000-0000-0000-000000000000");
    струСхема.Вставить("useOutput", "Auto");
    струСхема.Вставить("printPropItem", Новый Массив);
    струСхема.printPropItem.Добавить(Новый Структура("key,val",6,10    ));
    струСхема.printPropItem.Добавить(Новый Структура("key,val",7,10    ));
    струСхема.printPropItem.Добавить(Новый Структура("key,val",8,10    ));
    струСхема.printPropItem.Добавить(Новый Структура("key,val",9,10    ));
    струСхема.printPropItem.Добавить(Новый Структура("key,val",13,0    ));
    струСхема.printPropItem.Добавить(Новый Структура("key,val",16,0    ));
    
    //струСхема.Вставить("item", Новый Массив); // добавлять только если есть хотя бы один элемент
    
    рСтруктурыТиповЭлементовГС=ПостроитьКэшСтруктурВсехТиповЭлементовСхемы(); // для элементов этой схемы
    
    значСхема=Новый Соответствие;
    значСхема.Вставить("#value",струСхема);
    значСхема.Вставить("СтруктурыТиповЭлементов",рСтруктурыТиповЭлементовГС);
    Возврат значСхема;
    
Исключение
    Сообщить("ИнициализироватьСхему, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Возвращает массив строковых кратких названий элементов ГС.
//
Функция ПостроитьНотациюТиповЭлементовСхемы()
    спТиповЭлементовГС=Новый СписокЗначений;
    спТиповЭлементовГС.Добавить(0,"Декорация");
    //спТиповЭлементовГС.Добавить(1,"ДекоративнаяЛиния"); // потом продумать вопрос, когда по коду определяется тип - неоднозначная ситуация!
    спТиповЭлементовГС.Добавить(1,"СоединительнаяЛиния");
    спТиповЭлементовГС.Добавить(2,"Старт");
    спТиповЭлементовГС.Добавить(3,"Завершение");
    спТиповЭлементовГС.Добавить(4,"Условие");
    спТиповЭлементовГС.Добавить(5,"Действие");
    спТиповЭлементовГС.Добавить(6,"ВыборВарианта");
    спТиповЭлементовГС.Добавить(7,"Разделение");
    спТиповЭлементовГС.Добавить(8,"Слияние");
    спТиповЭлементовГС.Добавить(9,"Обработка");
    спТиповЭлементовГС.Добавить(10,"ВложенныйБизнесПроцесс");
    Возврат спТиповЭлементовГС;
КонецФункции

// Возвращает число, соотвтетствующее типу стороны элемента ГС.
// При отсутствии подходящих значений или при ошибке возвращает Неопределено.
//
// Параметры:
//    рТип - значение типа ТипСтороныЭлементаГрафическойСхемы, если задание стороны делается для любого элемента ГС, кроме Выбора,
//        или структура с обязательными ключами ТипСтороны и НомерВарианта, если задание стороны делается для варианта элемента Выбор.
//
Функция ПреобразоватьТипСтороныЭлементаВЧисло(рТипИлиВариант)
Попытка
    // Кроме основных 1-5, используются стороны вариантов (только для элемента ВыборВарианта):
    //// чётные - левые стороны, нечётные - правые
    //// поэтому 1 вариант левая сторона - это 6, а 1 вариант правая сторона - 7
    //// для 2 варианта: 8 (лево) и 9 (право) соответственно
    Если ТипЗнч(рТипИлиВариант)=Тип("Структура") Тогда
        // это указание на вариант выбора
        Если рТипИлиВариант.ТипСтороны=ТипСтороныЭлементаГрафическойСхемы.Лево Тогда
            коэф=0;
        ИначеЕсли рТипИлиВариант.ТипСтороны=ТипСтороныЭлементаГрафическойСхемы.Право Тогда
            коэф=1;
        Иначе
            Возврат Неопределено;
        КонецЕсли;
        Возврат 2*(3+рТипИлиВариант.НомерВарианта)+коэф;
    Иначе
        Если рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Лево Тогда Возврат 1
        ИначеЕсли рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Верх Тогда Возврат 2
        ИначеЕсли рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Право Тогда Возврат 3
        ИначеЕсли рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Низ Тогда Возврат 4
        ИначеЕсли рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Центр Тогда Возврат 5
        Иначе Возврат Неопределено;        
        КонецЕсли;
    КонецЕсли;    
Исключение
    Сообщить("ПреобразоватьТипСтороныЭлементаВЧисло, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Возвращает числовой код типа элемента ГС согласно внутренней нотации.
// При ошибке возвращает Неопределено.
//
// Параметры:
//    рТипЭлемента - число (от 0 до 10), строковое представление типа или Тип элемента ГС (например, Тип("ЭлементГрафическойСхемыДействие")).
//
Функция ПолучитьКодТипаЭлементаСхемы(рТипЭлемента)
Попытка
    спТиповЭлементовГС=ПостроитьНотациюТиповЭлементовСхемы();
    
    Если ТипЗнч(рТипЭлемента)=Тип("Число") Тогда // сразу задан код
        Если спТиповЭлементовГС.НайтиПоЗначению(рТипЭлемента)=Неопределено Тогда Возврат Неопределено КонецЕсли;
        рКодТипаЭлемента=рТипЭлемента;
    ИначеЕсли ТипЗнч(рТипЭлемента)=Тип("Строка") Тогда // строка с кратким именем
        рКодТипаЭлемента=Неопределено;
        Для каждого знч Из спТиповЭлементовГС Цикл
            Если СокрЛП(знч.Представление)=СокрЛП(рТипЭлемента) Тогда
                рКодТипаЭлемента=знч.Значение; Прервать;
            КонецЕсли;
        КонецЦикла;        
    Иначе // тип
        рКодТипаЭлемента=Неопределено;
        Для каждого знч Из спТиповЭлементовГС Цикл
            Если ТипЗнч(рТипЭлемента)=Тип("ЭлементГрафическойСхемы"+СокрЛП(знч.Представление))
            или рТипЭлемента=Тип("ЭлементГрафическойСхемы"+СокрЛП(знч.Представление)) 
            Тогда
                рКодТипаЭлемента=знч.Значение; Прервать;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    
    Возврат рКодТипаЭлемента;
Исключение
    Сообщить("ПолучитьКодТипаЭлементаСхемы, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Возвращает структуру, состав которой аналогичен сериализованным свойствам указанного элемента графической схемы.
// Кроме описанных в этой коллекции, всегда добавляются ключи Имя (строковое имя элемента) и Тип (по параметру рТипЭлемента).
// При ошибке возвращает Неопределено.
//
// Параметры:
//    рТипЭлемента - число (от 0 до 10), строковое представление типа или Тип элемента ГС (например, Тип("ЭлементГрафическойСхемыДействие")).
//
Функция ПостроитьСтруктуруДляТипаЭлементаСхемы(рТипЭлемента)
Попытка
    рКодТипаЭлемента=ПолучитьКодТипаЭлементаСхемы(рТипЭлемента);
    Если рКодТипаЭлемента=Неопределено Тогда Возврат Неопределено КонецЕсли;

    струЭлемента=Новый Структура; // здесь это может быть структурой; потом оно при необходимости преобразуется в соответствие
    
    струЭлемента.Вставить("itemType",                рКодТипаЭлемента);
    
    струЭлемента.Вставить("lineColor",                Новый Соответствие);
    струЭлемента.lineColor.Вставить("#type",        "jv8ui:Color");    
    струЭлемента.lineColor.Вставить("#value",        "{http://v8.1c.ru/8.1/data/ui/style}BorderColor");
    //
    струЭлемента.Вставить("backColor",                Новый Соответствие);
    струЭлемента.backColor.Вставить("#type",        "jv8ui:Color");
    струЭлемента.backColor.Вставить("#value",        "auto");
    //
    струЭлемента.Вставить("textColor",                Новый Соответствие);
    струЭлемента.textColor.Вставить("#type",        "jv8ui:Color");
    струЭлемента.textColor.Вставить("#value",        "{http://v8.1c.ru/8.1/data/ui/style}FormTextColor");
    
    струЭлемента.Вставить("alignHor",                "Center");
    струЭлемента.Вставить("alignVer",                "Center");
    струЭлемента.Вставить("currentLanguage",        "#");
    струЭлемента.Вставить("picturePlacement",        "Left");
    струЭлемента.Вставить("textFont",                Новый Структура("kind","AutoFont"));
    струЭлемента.Вставить("tipText",                Новый Соответствие);
    струЭлемента.Вставить("transparent",            Ложь);
    струЭлемента.Вставить("hyperlink",                Ложь);
    струЭлемента.Вставить("itemTitle",                Новый Массив);
    струЭлемента.Вставить("groupNum",                0);
    
    
    Если рКодТипаЭлемента=0 Тогда
        струЭлемента.Вставить("angle",                    Новый Соответствие);
        струЭлемента.angle.Вставить("#type",    "jxs:decimal");
        струЭлемента.angle.Вставить("#value",    0);
        струЭлемента.Вставить("flipMode",                0);
        струЭлемента.Вставить("shape",                    "Block");
    Иначе
        струЭлемента.Вставить("border",                Новый Структура("width,gap,style",Новый Соответствие,Ложь,Новый Соответствие));
        струЭлемента.border.width.Вставить("#type",    "jxs:decimal");
        струЭлемента.border.width.Вставить("#value",    1);
        струЭлемента.border.style.Вставить("#type",    "jsch:ConnectorLineType");
        струЭлемента.border.style.Вставить("#value",    "Solid");
        струЭлемента.Вставить("point",                    Новый Массив);
    КонецЕсли;
    
    Если рКодТипаЭлемента=1 Тогда
        струЭлемента.Вставить("beginArrowStyle",        "None");
        струЭлемента.Вставить("connectFromItemId",        -1); // Если decorativeLine=Истина, то можно и из ниоткуда
        струЭлемента.Вставить("connectFromPortIndex",    0);
        струЭлемента.Вставить("connectToItemId",        -1);
        струЭлемента.Вставить("decorativeLine",        Истина); // Если Ложь, то будет неубираемая "пристегнутая" линия к объекту
        струЭлемента.Вставить("endArrowStyle",            "Filled");
        струЭлемента.Вставить("portIndexFrom",            4);
        струЭлемента.Вставить("portIndexTo",            0);
        струЭлемента.Вставить("textPos",                "FirstSegment");
    Иначе
        струЭлемента.Вставить("rectBottom",            40);
        струЭлемента.Вставить("rectLeft",                60);
        струЭлемента.Вставить("rectRight",                80);
        струЭлемента.Вставить("rectTop",                20);
        струЭлемента.Вставить("picture",                Новый Соответствие);
        струЭлемента.Вставить("pictureStyle",            4);
    КонецЕсли;
    
    Если рКодТипаЭлемента >= 2 Тогда
        струЭлемента.Вставить("pointUUID",                Строка(Новый УникальныйИдентификатор));
        струЭлемента.Вставить("passageState",            0);
        струЭлемента.Вставить("tableCode",                0);
    КонецЕсли;
    
    Если рКодТипаЭлемента=4 Тогда
        струЭлемента.Вставить("falsePortIndex",        1);
        струЭлемента.Вставить("truePortIndex",            3);
    ИначеЕсли рКодТипаЭлемента=5 Тогда
        струЭлемента.Вставить("addrZoneDivideYPos",    16);
        струЭлемента.Вставить("groupAddressing",        Ложь);
        струЭлемента.Вставить("isAddrZoneDivideValid",    Истина);
        струЭлемента.Вставить("explanation",            "");
    ИначеЕсли рКодТипаЭлемента=6 Тогда
        струЭлемента.Вставить("transition",            Новый Массив);
    ИначеЕсли рКодТипаЭлемента=10 Тогда
        струЭлемента.Вставить("subprocessUUID",        "00000000-0000-0000-0000-000000000000");
    КонецЕсли;
    
    Возврат струЭлемента;
    
Исключение
    Сообщить("ПостроитьСтруктуруДляТипаЭлементаСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Возвращает соответствие, где ключ - Тип элемента ГС, а значение - структура, соответствующая этому типу.
// Рекомендуется к использованию перед началом работы с ГС, для её ключа СтруктурыТиповЭлементов.
// При ошибке возвращает Неопределено.
//
Функция ПостроитьКэшСтруктурВсехТиповЭлементовСхемы() Экспорт
Попытка
    спТиповЭлементовГС=ПостроитьНотациюТиповЭлементовСхемы();
    
    соотСтруктурТипов=Новый Соответствие;
    //
    Для каждого знч Из спТиповЭлементовГС Цикл
        струЭлемента=ПостроитьСтруктуруДляТипаЭлементаСхемы(знч.Значение);
        Если струЭлемента=Неопределено Тогда Продолжить КонецЕсли;
        соотСтруктурТипов.Вставить(Тип("ЭлементГрафическойСхемы"+СокрЛП(знч.Представление)),струЭлемента);
    КонецЦикла;
    
    Возврат соотСтруктурТипов;
    
Исключение
    Сообщить("ПостроитьКэшСтруктурВсехТиповЭлементовСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
КонецПопытки;    
КонецФункции

// Возвращает для конкретного элемента ГС его структуру, состав её аналогичен сериализованным свойствам, полученным в ПостроитьСтруктуруДляТипаЭлементаСхемы.
// При ошибке возвращает Неопределено.
//
// Параметры:
//    рТипЭлемента - Тип элемента ГС, например, Тип("ЭлементГрафическойСхемыДействие");
//    рИмяЭлемента - строка, уникальное имя элемента в пределах схемы. Проверка уникальности происходит в момент добавления элемента в коллекцию элементов ГС.
//
Функция ИнициализироватьЭлементСхемы(рСхема,рТипЭлемента,рИмяЭлемента="") Экспорт
Попытка
    струЭлементаОбразец=рСхема.Получить("СтруктурыТиповЭлементов").Получить(рТипЭлемента);
    Если струЭлементаОбразец=Неопределено Тогда
        Сообщить("ИнициализироватьЭлементСхемы: структура элемента с типом """+Строка(рТипЭлемента)+""" не найдена!");
        Возврат Неопределено;
    КонецЕсли;
    
    струЭлемента=НовыйСоответствиеСтруктура();
    // в любом случае копируем так
    Для каждого киз Из струЭлементаОбразец Цикл
        Если ТипЗнч(киз.Значение)=Тип("Массив") Тогда знч=Новый Массив Иначе знч=киз.Значение КонецЕсли; // иначе по-дурному кэширует
        струЭлемента.Вставить(киз.Ключ,знч);
    КонецЦикла;
    
    // служебные, для удобства работы, перед сериализацией будут удалены.
    струЭлемента.Вставить("Имя",рИмяЭлемента);
    струЭлемента.Вставить("Тип",рТипЭлемента);
    
    струЭлемента.Вставить("itemCode",рИмяЭлемента);
    струЭлемента.Вставить("itemId",Неопределено); // определяется при добавлении в коллекцию элементов ГС
    струЭлемента.Вставить("itemTabOrder",Неопределено); // определяется при добавлении в коллекцию элементов ГС
    струЭлемента.Вставить("zOrder",Неопределено); // определяется при добавлении в коллекцию элементов ГС
    
    Если рТипЭлемента=Тип("ЭлементГрафическойСхемыДействие") 
    или рТипЭлемента=Тип("ЭлементГрафическойСхемыВложенныйБизнесПроцесс")
    Тогда
        струЭлемента.Вставить("taskDescription",рИмяЭлемента); // Должно быть задано при добавлении; возможно=itemCode
    КонецЕсли;

    Возврат струЭлемента;
    
Исключение
    Сообщить("ИнициализироватьЭлементСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Расставляет все точки, нужные для отрисовки элемента ГС, согласно указанным координатам, определяя прямоугольную область "обитания" элемента ГС и его фигуру.
//
// Параметры:
//    струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
//    рПараметры - структура, где:
//        для не-линий обязательны числовые: ключи Лево, Верх, Ширина, Высота;
//        для линий обязательны ключи: Схема (структура), Начало и Конец (структуры или id); и необязательны ДекоративнаяЛиния (булево),
//        НачалоСторона и КонецСторона (типа ТипСтороныЭлементаГрафическойСхемы).
//        При этом, если нужно, чтобы стрелка никуда не вела, следует НЕ указывать вообще ключ "Конец".
//
Процедура УстановитьКоординатыЭлементаСхемы(струЭлемента,рПараметры=Неопределено) Экспорт
Попытка
    рТипЭлемента=СоотСвойство(струЭлемента,"Тип");
    //
    Если рТипЭлемента=Тип("ЭлементГрафическойСхемыДекоративнаяЛиния") 
    или рТипЭлемента=Тип("ЭлементГрафическойСхемыСоединительнаяЛиния") 
    Тогда    
        рНачало=?(ТипЗнч(рПараметры.Начало)=Тип("Структура"),рПараметры.Начало.ItemId,рПараметры.Начало);
        Если рПараметры.Свойство("Конец") Тогда
            рКонец=?(ТипЗнч(рПараметры.Конец)=Тип("Структура"),рПараметры.Конец.ItemId,рПараметры.Конец);
        Иначе
            // линия никуда не ведёт
            рКонец=0;
        КонецЕсли;        
        //
        струЭлемента.Вставить("decorativeLine",?(рПараметры.Свойство("ДекоративнаяЛиния"),рПараметры.ДекоративнаяЛиния,Ложь));
        струЭлемента.Вставить("connectFromItemId",рНачало);
        струЭлемента.Вставить("connectToItemId",рКонец);
        //
        рТипСтороныНач=?(рПараметры.Свойство("НачалоСторона"),ПреобразоватьТипСтороныЭлементаВЧисло(рПараметры.НачалоСторона),Неопределено);
        рТипСтороныНач=?(рТипСтороныНач=Неопределено,1,рТипСтороныНач); // лево
        струЭлемента.Вставить("portIndexFrom",рТипСтороныНач);
        //
        рТипСтороныКон=?(рПараметры.Свойство("КонецСторона"),ПреобразоватьТипСтороныЭлементаВЧисло(рПараметры.КонецСторона),Неопределено);
        Если рКонец<>0 Тогда
            рТипСтороныКон=?(рТипСтороныКон=Неопределено,2,рТипСтороныКон); // верх
            струЭлемента.Вставить("portIndexTo",рТипСтороныКон);
        КонецЕсли;
        
        рПортВарианта=СоотСвойство(струЭлемента,"connectFromPortIndex");
        //
        // Рисуем только начало и окончание. Остальное система дополнит сама при отрисовке.        
        рКоординатыНач=РассчитатьКоординатыЭлемента(рПараметры.Схема,рНачало,рТипСтороныНач,рПортВарианта);
        Если рКонец<>0 Тогда
            рКоординатыКон=РассчитатьКоординатыЭлемента(рПараметры.Схема,рКонец,рТипСтороныКон);
            Если рКоординатыНач=Неопределено или рКоординатыКон=Неопределено Тогда
                Сообщить("УстановитьКоординатыЭлементаСхемы, ошибка расчёта координат элемента "+СокрЛП(струЭлемента.Имя)+"!");
                Возврат;
            КонецЕсли;
        КонецЕсли;
        //
        струЭлемента.Вставить("connectFromPortIndex",рПортВарианта);
        
        мКоординат=СоотСвойство(струЭлемента,"point");
        Если ТипЗнч(мКоординат)<>Тип("Массив") Тогда мКоординат=Новый Массив КонецЕсли;
        соотНач=НовыйСоответствиеСтруктура();
        соотНач.Вставить("x",рКоординатыНач.x);
        соотНач.Вставить("y",рКоординатыНач.y);
        мКоординат.Добавить(соотНач);
        Если рКонец<>0 Тогда
            соотКон=НовыйСоответствиеСтруктура();
            соотКон.Вставить("x",рКоординатыКон.x);
            соотКон.Вставить("y",рКоординатыКон.y);
            мКоординат.Добавить(соотКон);
        Иначе
            соотКон=НовыйСоответствиеСтруктура();
            соотКон.Вставить("x",рКоординатыНач.x);
            соотКон.Вставить("y",рКоординатыНач.y+20);
            мКоординат.Добавить(соотКон);
        КонецЕсли;
        //
        // случай, когда линия идет снизу на верхнюю границу (ставим точку середины)
        Если рКонец<>0 и СоотСвойство(мКоординат[0],"y","Число") > СоотСвойство(мКоординат[1],"y","Число") И СоотСвойство(струЭлемента,"portIndexTo","Число")=2 Тогда
            х1=РассчитатьКоординатыЭлемента(рПараметры.Схема,СоотСвойство(струЭлемента,"connectFromItemId","Число"),3).x;
            х2=РассчитатьКоординатыЭлемента(рПараметры.Схема,СоотСвойство(струЭлемента,"connectToItemId","Число"),1).x;
            Если х1<>Неопределено и х2<>Неопределено Тогда
                xmid=Цел((х1+х2)/2);
                рВертШагСетки=СоотСвойство(СоотСвойство(рПараметры.Схема,"#value"),"gridVerticalStep");
                ymid=СоотСвойство(мКоординат[1],"y","Число")-рВертШагСетки;
                соотСред=НовыйСоответствиеСтруктура();
                соотСред.Вставить("x",xmid);
                соотСред.Вставить("y",ymid);
                мКоординат.Вставить(1,соотСред);
            КонецЕсли;            
        КонецЕсли;
        //
        струЭлемента.Вставить("point",мКоординат);
        
    Иначе
        // координаты и размер
        //Если рКоординаты.Лево=Неопределено И рКоординаты.Верх<>Неопределено Тогда // ищем максимальный X
        //    рКоординаты.Лево=ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(струСхемы.item,"rectRight") + струСхемы.gridHorizontalStep;
        //КонецЕсли;
        //Если рКоординаты.Верх=Неопределено И рКоординаты.Лево<>Неопределено Тогда // ищем максимальный Y
        //    рКоординаты.Верх=ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(струСхемы.item,"rectBottom") + струСхемы.gridVerticalStep;
        //КонецЕсли;
        струЭлемента.Вставить("rectLeft",рПараметры.Лево);
        струЭлемента.Вставить("rectTop",рПараметры.Верх);
        струЭлемента.Вставить("rectRight",рПараметры.Лево+рПараметры.Ширина);
        струЭлемента.Вставить("rectBottom",рПараметры.Верх+рПараметры.Высота);
        РасставитьТочкиФигурыЭлемента(струЭлемента);
        
    КонецЕсли;
    
Исключение
    Сообщить("УстановитьКоординатыЭлементаСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
КонецПопытки;
КонецПроцедуры    

// Собственно готови данные для отрисовки конкретной фигуры элемента ГС. Вспомогательная для УстановитьКоординатыЭлементаСхемы.
//
// Параметры:
//    струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
//    рМодификатор - число; до 10 включительно совпадает с нотацией типа элемента ГС, значения с 11 по 13 - нетипичные геометрические фигуры.
//
Процедура РасставитьТочкиФигурыЭлемента(струЭлемента,рМодификатор=Неопределено)
Попытка
    itemType=?(рМодификатор=Неопределено,СоотСвойство(струЭлемента,"itemType","Число"),рМодификатор);
    //
    rectLeft=СоотСвойство(струЭлемента,"rectLeft","Число");
    rectTop=СоотСвойство(струЭлемента,"rectTop","Число");
    rectRight=СоотСвойство(струЭлемента,"rectRight","Число");
    rectBottom=СоотСвойство(струЭлемента,"rectBottom","Число");
    
    мТочек1=Новый Массив;
    
    Если itemType=0 ИЛИ itemType=5 ИЛИ itemType=9 ИЛИ itemType=10 Тогда // Декорация/Действие/Обработка/ВложенныйПроцесс: Прямоугольник
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-1));
        //
    ИначеЕсли itemType=2 Тогда // Старт: Прямоугольник + треугольник снизу (высота треугольника=5)
        dy=Цел((rectRight-rectLeft)/2/Sqrt(3));
        dy=?(rectBottom-dy<=rectTop,Цел((rectBottom-rectTop)/2),dy); // корректировка запредельных значений
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectBottom-dy));
        мТочек1.Добавить(Новый Структура("x,y",Цел((rectLeft+rectRight)/2),rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-dy));
        //
    ИначеЕсли itemType=3 Тогда // Завершение: Прямоугольник + треугольник сверху (высота треугольника=5)
        dy=Цел((rectRight-rectLeft)/2/Sqrt(3));
        dy=?(rectTop+dy>=rectBottom,Цел((rectRight-rectLeft)/2),dy); // корректировка запредельных значений
        мТочек1.Добавить(Новый Структура("x,y",Цел((rectLeft+rectRight)/2),rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop+dy));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop+dy));
        //
    ИначеЕсли itemType=4 Тогда // Условие: Шестиугольник (расчет середины высоты:dy/2,угол 60 градусов) 
        dx=Цел((rectBottom-rectTop)/2/Sqrt(3)); // ДельтаX при 60 градусном уклоне
        dx=?(2*dx>(rectRight-rectLeft),Цел((rectRight-rectLeft)/4),dx); // корректировка запредельных значений
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,Цел((rectTop+rectBottom)/2)));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dx,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,Цел((rectTop+rectBottom)/2)));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dx,rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectBottom-1));
        //
    ИначеЕсли itemType=6 Тогда // ВыборВарианта: Прямоугольник
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-1));
        //
    ИначеЕсли itemType=7 Тогда // Разделение: Треугольник, острый угол вниз
        dx=Цел((rectRight-rectLeft)/2)-1;
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+2*dx,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectBottom-1));
        //
    ИначеЕсли itemType=8 Тогда // Слияние: Треугольник, острый угол вверх
        dx=Цел((rectRight-rectLeft)/2)-1;
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+2*dx,rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectTop));    
        //
    ИначеЕсли itemType=11 Тогда // (нетиповые фигуры) Галстук - бабочка
        dx=Цел((rectBottom-rectTop)/2/Sqrt(3)); // ДельтаX при 60 градусном уклоне
        dx=?(2*dx>(rectRight-rectLeft),Цел((rectRight-rectLeft)/4),dx); // корректировка запредельных значений
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,Цел((rectTop+rectBottom)/2)));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",Цел((rectLeft+rectRight)/2),Цел((rectTop+rectBottom)/2)));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dx,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,Цел((rectTop+rectBottom)/2)));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dx,rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",Цел((rectLeft+rectRight)/2),Цел((rectTop+rectBottom)/2)));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectBottom-1));
        //
    ИначеЕсли itemType=12 Тогда // (нетиповые фигуры) Звезда шерифа
        // сторона маленького треугольника
        dc=Цел((rectRight-rectLeft)/3);
        dchalf=Цел(dc/2);
        dchigh=Цел(dchalf*Sqrt(3));
        //dx=Цел((rectBottom-rectTop)/2/Sqrt(3)); // ДельтаX при 60 градусном уклоне
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop+dchigh));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dc,rectTop+dchigh));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dc+dchalf,rectTop));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dc,rectTop+dchigh));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop+dchigh));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dchalf,rectTop+2*dchigh));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop+3*dchigh));
        мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dc,rectTop+3*dchigh));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dc+dchalf,rectBottom-1));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dc,rectTop+3*dchigh));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop+3*dchigh));
        мТочек1.Добавить(Новый Структура("x,y",rectLeft+dchalf,rectTop+2*dchigh));
        //
    ИначеЕсли itemType=13 Тогда // (нетиповые фигуры) Круг
        // сторона маленького треугольника
        radius=Мин(Цел((rectRight-rectLeft)/2),Цел((rectBottom-rectTop)/2));
        xcenter=rectLeft + radius;
        ycenter=rectTop + radius;
        pi=3.141592635897;
        НачУгол=-20;
        КонУгол=200;
        ШагГрад=10;
        у=НачУгол;
        Пока у<=КонУгол Цикл
            Угол=у*pi/180;
            x=Окр(Cos(Угол)*radius);
            y=Окр(Sin(Угол)*radius);
            мТочек1.Добавить(Новый Структура("x,y",xcenter+x,ycenter+y));
            у=у + ШагГрад;
        КонецЦикла;
        //
    КонецЕсли;
    
    Если ТипЗнч(НовыйСоответствиеСтруктура())=Тип("Соответствие") Тогда // преобразуем
        мТочек2=Новый Массив;
        Для каждого рТочка1 Из мТочек1 Цикл
            соот=Новый Соответствие;
            Для каждого киз Из рТочка1 Цикл
                соот.Вставить(киз.Ключ,киз.Значение);
            КонецЦикла;
            мТочек2.Добавить(соот);
        КонецЦикла;
    Иначе
        мТочек2=мТочек1;
    КонецЕсли;
    
    струЭлемента.Вставить("point",мТочек2);
    
Исключение
    Сообщить("РасставитьТочкиФигурыЭлемента, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры

// Устанавливает элементу ГС заголовок без локализации языка, по умолчанию.
//
// Параметры:
//    струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
//    рЗаголовок - строка, произвольная надпись на элементе.
//
Процедура УстановитьЗаголовокЭлементаСхемы(струЭлемента,рЗаголовок) Экспорт
    соот=НовыйСоответствиеСтруктура();
    соот.Вставить("lang","#");
    соот.Вставить("content",рЗаголовок);
    мЗаголовка=СоотСвойство(струЭлемента,"itemTitle");
    Если ТипЗнч(мЗаголовка)<>Тип("Массив") Тогда мЗаголовка=Новый Массив КонецЕсли;
    мЗаголовка.Добавить(соот);
    струЭлемента.Вставить("itemTitle",мЗаголовка);
КонецПроцедуры

// Добавляет структуру, описывающую элемент ГС, в соответствующую коллекцию элементов самой структуры ГС.
// Возвращает уникальный в пределах схемы id добавленного элемента, 1 и более. При ошибке возвращает Неопределено.
//
// Параметры:
//    рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы;
//    струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы).
//
Функция ДобавитьЭлементВСхему(рСхема,струЭлемента) Экспорт
Попытка
    рИмяЭлемента=СокрЛП(СоотСвойство(струЭлемента,"itemCode"));
    струИмеющегося=ПолучитьЭлементСхемыПоИмени(рСхема,рИмяЭлемента);
    Если струИмеющегося<>Неопределено Тогда // такой уже есть
        Возврат СоотСвойство(струИмеющегося,"itemId","Число");
    КонецЕсли;
    
    струСхемы=Неопределено;
    мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,струСхемы);
    
    // Ищем максимальный itemId
    струЭлемента.Вставить("itemId",ПолучитьМаксимальноеЗначениеСвойства(мЭлементовГС,"itemId")+1);
    
    // Ищем такой же itemCode (заимствованный код; возможно, нужен рефакторинг!)
    //Если НайтиПоЗначениюКлючаВМассивеСтруктур(струСхемы.item,"itemCode",струЭлемента.itemCode) <> Неопределено Тогда
    //    _базоваяЧасть=струЭлемента.itemCode;
    //    _нс=1;
    //    Пока НайтиПоЗначениюКлючаВМассивеСтруктур(струСхемы.item,"itemCode",_базоваяЧасть + Формат(_нс,"ЧГ=")) <> Неопределено Цикл
    //        _нс=_нс + 1;
    //    КонецЦикла;
    //    струЭлемента.itemCode=_базоваяЧасть + Формат(_нс,"ЧГ=");
    //КонецЕсли;
    
    // Ищем максимальный itemTabOrder
    // пока так. Это порядок обхода. Возможно нужно более продвинутое вычисление сделать 
    // (считать все подчиненные и соединенные элементы последнего и прибавлять на их количество к максимальному)
    струЭлемента.Вставить("itemTabOrder",ПолучитьМаксимальноеЗначениеСвойства(мЭлементовГС,"itemTabOrder")+5);
    
    // Ищем максимальный zOrder
    струЭлемента.Вставить("zOrder",ПолучитьМаксимальноеЗначениеСвойства(мЭлементовГС,"zOrder",-1)+1);
    
    // собственно добавление в коллекцию элементов схемы
    мЭлементовГС.Добавить(струЭлемента);
    
    струСхемы.Вставить("item",мЭлементовГС);
    рСхема.Вставить("#value",струСхемы);
    
    Возврат СоотСвойство(струЭлемента,"itemId","Число");
Исключение
    Сообщить("ДобавитьЭлементВСхему, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Добавляет вариант в элемент типа "Выбор". Элемент уже должен быть инициализирован. Возвращает порядковый номер варианта, начиная с 0.
// При ошибке возвращает Неопределено.
//
// Параметры:
//    струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
//    рИмяВарианта - строка, имя варианта, уникальное в пределах выбора;
//    рЗаголовокВарианта - строка, произвольная надпись на варианте.
//
Функция ДобавитьВариантВыбора(струЭлемента,рИмяВарианта,рЗаголовокВарианта) Экспорт
Попытка
    соотЗаголовка=НовыйСоответствиеСтруктура();
    соотЗаголовка.Вставить("lang","#");
    соотЗаголовка.Вставить("content",рЗаголовокВарианта);
    мЗаголовка=Новый Массив;
    мЗаголовка.Добавить(соотЗаголовка);
    
    соотВарианта=НовыйСоответствиеСтруктура();
    соотВарианта.Вставить("name",рИмяВарианта);
    соотВарианта.Вставить("description",мЗаголовка);    
    соотВарианта.Вставить("backColor",СоотСвойство(струЭлемента,"backColor"));
    
    мВариантов=СоотСвойство(струЭлемента,"transition");
    Если ТипЗнч(мВариантов)<>Тип("Массив") Тогда мВариантов=Новый Массив КонецЕсли;
    мВариантов.Добавить(соотВарианта);
    
    струЭлемента.Вставить("transition",мВариантов);
    Возврат мВариантов.Количество()-1;
Исключение
    Сообщить("ДобавитьВариантВыбора, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Получает координаты для работы с портом (точкой входа/выхода соединительной линии); возвращает структуру с ключами x,y;
// используется при первичной работе со схемой (далее можно обращаться к свойствам элемента как объекта);
// При ошибке возвращает Неопределено.
//
// Параметры:
//    рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы;
//    itemId - уникальный в пределах схемы идентификатор элемента;
//    portIndex - код типа стороны элемента получателя (см. ПреобразоватьТипСтороныЭлементаВЧисло), ни в коем случае убирать "Знач"!
//    connectFromPortIndex - код типа стороны элемента отправителя (см. ПреобразоватьТипСтороныЭлементаВЧисло), изменяется внутри функции.
//
Функция РассчитатьКоординатыЭлемента(рСхема,itemId,Знач portIndex,connectFromPortIndex=0)
Попытка
    рРезультат=Новый Структура("x,y",0,0);
    
    струСхемы=Неопределено;
    мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,струСхемы);
    Если мЭлементовГС.Количество()=0 Тогда
        Сообщить("Внутренняя ошибка: коллекция элементов схемы пуста!",СтатусСообщения.Важное);
    Иначе
        струЭлемента=НайтиПоЗначениюКлючаВМассивеСтруктур(мЭлементовГС,"itemId",itemId);
        Если струЭлемента=Неопределено Тогда
            Сообщить("Внутренняя ошибка: элемент не найден по своему id "+СокрЛП(itemId)+"!",СтатусСообщения.Важное);
            Возврат рРезультат;
        КонецЕсли;
    КонецЕсли;
    
    // Порты:
    //1: Лево 
    //2: Верх 
    //3: Право 
    //4: Низ 
    //5: Центр 
    //6: Вариант 1 Лево 
    //7: Вариант 1 Право 
    //8: Вариант 2 Лево 
    //9: Вариант 2 Право
    // Каждый вариант - это 18 точек по шкале Y от rectBottom. Середина=rectBottom - (18/2)
    
    dy=0;
    Если portIndex > 5 Тогда
        caseCount=СоотСвойство(струЭлемента,"transition").Количество();
        connectFromPortIndex=Цел((portIndex-6)/2);
        portIndex=1 + (portIndex%2)*2;
        dy=(caseCount - connectFromPortIndex - 1)*18 + 18/2;
    КонецЕсли;
    
    рЛево=СоотСвойство(струЭлемента,"rectLeft","Число");
    рВерх=СоотСвойство(струЭлемента,"rectTop","Число");
    рПраво=СоотСвойство(струЭлемента,"rectRight","Число");
    рНиз=СоотСвойство(струЭлемента,"rectBottom","Число");
    
    Если portIndex=1 Тогда
        рРезультат.x=рЛево;
        рРезультат.y=?(dy>0,рНиз-1-dy,Цел((рВерх+рНиз+1)/2));
    ИначеЕсли portIndex=2 Тогда
        рРезультат.x=Цел((рЛево+рПраво+1)/2);
        рРезультат.y=рВерх;
    ИначеЕсли portIndex=3 Тогда
        рРезультат.x=рПраво;
        рРезультат.y=?(dy>0,рНиз-1-dy,Цел((рВерх+рНиз+1)/2));
    ИначеЕсли portIndex=4 Тогда
        рРезультат.x=Цел((рЛево+рПраво+1)/2);
        рРезультат.y=рНиз;
    ИначеЕсли portIndex=5 Тогда
        рРезультат.x=Цел((рЛево+рПраво+1)/2);
        рРезультат.y=Цел((рВерх+рНиз+1)/2);
    КонецЕсли;
    
    Возврат рРезультат;
Исключение
    Сообщить("РассчитатьКоординатыЭлемента, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции

#КонецОбласти


#Область ДействияВторичнойНастройки

// Действия вторичной настройки полезны, когда объект "ГрафическаяСхема" уже есть, и надо изменить свойство элемента, не имеющее во встроенном языке 
// возможности записи, т.е. которые только на чтение. Важно: вторичная работа ведётся уже с соответствиями, а не со структурами, т.е. все коллекции данных,
// бывшие структурами при первичном построении, теперь будут являться соответствиями.
// Используется сериализация схемы в строку, чтение строки через JSON в коллекцию коллекций, и работа с их элементами и значениями.
// Рекомендуется использовать РазобратьГрафическуюСхему, затем выполнить настройку нужных свойств, затем СобратьГрафическуюСхему.

// Возвращает массив структур или соответствий, содержащий элементы ГС. В случае необходимости дополняет коллекции до минимально правильного состава.
// При ошибке или отсутствии данных возвращает пустой массив.
//
// Параметры:
//    рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
//    знчСхемы - значение, хранимое в основном ключе #value схемы, структура или соответствие. Может изменяться внутри функции; необязательное.
//
Функция ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы=Неопределено) Экспорт
Попытка
    Если ТипЗнч(рСхема)<>Тип("Соответствие") Тогда рСхема=Новый Соответствие КонецЕсли;
    //
    знчСхемы=СоотСвойство(рСхема,"#value");
    Если ТипЗнч(знчСхемы)<>Тип("Структура") и ТипЗнч(знчСхемы)<>Тип("Соответствие") Тогда
        рСхема.Вставить("#value",Новый Соответствие);
        знчСхемы=СоотСвойство(рСхема,"#value");
    КонецЕсли;    
    //
    мЭлементовГС=СоотСвойство(знчСхемы,"item");
    Если ТипЗнч(мЭлементовГС)<>Тип("Массив") Тогда мЭлементовГС=Новый Массив КонецЕсли;
    //
    знчСхемы.Вставить("item",мЭлементовГС);
    рСхема.Вставить("#value",знчСхемы);
    //
    Возврат мЭлементовГС;
Исключение
    Сообщить("ПолучитьКоллекциюЭлементовСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
    Возврат Новый Массив;
КонецПопытки;
КонецФункции

// Устанавливает ZOrder (порядок плана отображения относительно других перекрывающих фигур) для всех элементов указанного типа.
//
// Параметры:
//    рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
//    рТипЭлемента - число (от 0 до 10), строковое представление типа или Тип элемента ГС (например, Тип("ЭлементГрафическойСхемыДействие"));
//    ZOrder - число, приоритет по возрастанию (старший накрывает младших), изменяется внутри процедуры.
//
Процедура УстановитьZOrderВсемЭлементам(рСхема,рТипЭлемента,ZOrder) Экспорт
Попытка        
    рКодТипаЭлемента=ПолучитьКодТипаЭлементаСхемы(рТипЭлемента);
    Если рКодТипаЭлемента=Неопределено Тогда Возврат КонецЕсли;
    
    знчСхемы=Неопределено;
    мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
    Если мЭлементовГС.Количество()=0 Тогда Возврат КонецЕсли; // нет ни одного элемента
    //
    Для каждого рЭлементГС из мЭлементовГС цикл
        Если СоотСвойство(рЭлементГС,"itemType","Число")=рКодТипаЭлемента Тогда
            рЭлементГС.Вставить("zOrder",ZOrder);
            ZOrder=ZOrder+1;
        КонецЕсли;
    КонецЦикла;
    
    знчСхемы.Вставить("item",мЭлементовГС);
    рСхема.Вставить("#value",знчСхемы);
Исключение
    Сообщить("УстановитьZOrderВсемЭлементам, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры

// Возвращает элемент схемы как соответствие или структуру из массива item схемы.
// При ошибке или отсутствии возвращает Неопределено.
//
// Параметры:
//    рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
//    рИмяЭлемента - строка, уникальное имя элемента в пределах схемы.
//
Функция ПолучитьЭлементСхемыПоИмени(рСхема,рИмяЭлемента) Экспорт
Попытка
    мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема);
    Если мЭлементовГС.Количество()=0 Тогда Возврат Неопределено КонецЕсли; // нет ни одного элемента
    //
    Для каждого рЭлементГС Из мЭлементовГС Цикл
        Если СокрЛП(СоотСвойство(рЭлементГС,"itemCode"))=рИмяЭлемента Тогда
            Если ТипЗнч(рЭлементГС)=Тип("Соответствие") Тогда
                // приводим к структуре (изначально он ею и был, поэтому все ключи применимы)
                стру=Новый Структура;
                Для каждого киз Из рЭлементГС Цикл
                    стру.Вставить(киз.Ключ,киз.Значение);
                КонецЦикла;
                Возврат стру;
            ИначеЕсли ТипЗнч(рЭлементГС)=Тип("Структура") Тогда
                Возврат рЭлементГС;
            Иначе
                Возврат Неопределено;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    //
    Возврат Неопределено;
Исключение
    Сообщить("ПолучитьЭлементСхемыПоИмени, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции    

// Устанавливает элементу массива item схемы соответствие/структуру с новыми значениями ключей свойств, т.е. по сути обновляет свойства элемента в коллекции.
// Возвращает "ссылку" на структуру/соответствие конкретного обработанного элемента ГС. При ошибке возвращает Неопределено.
//
// Параметры:
//    рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
//    рИмяЭлемента - строка, уникальное имя элемента в пределах схемы;
//    рЗначение - структура, содержащая значения ключей, описывающих свойства элемента согласно нотации и ПостроитьСтруктуруДляТипаЭлементаСхемы.
//
Функция ОбновитьЭлементСхемыПоИмени(рСхема,рИмяЭлемента,рЗначение) Экспорт
Попытка
    знчСхемы=Неопределено;
    мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
    Если мЭлементовГС.Количество()=0 Тогда Возврат Неопределено КонецЕсли; // нет ни одного элемента
    //
    Для каждого рЭлементГС Из мЭлементовГС Цикл
        Если СокрЛП(СоотСвойство(рЭлементГС,"itemCode"))=рИмяЭлемента Тогда
            Для каждого киз Из рЗначение Цикл // и никаких ЗаполнитьЗначенияСвойств!
                рЭлементГС.Вставить(киз.Ключ,киз.Значение);
            КонецЦикла;
            Прервать;
        КонецЕсли;
    КонецЦикла;
    //
    знчСхемы.Вставить("item",мЭлементовГС);
    рСхема.Вставить("#value",знчСхемы);
    //
    Возврат рЭлементГС;
Исключение
    Сообщить("ОбновитьЭлементСхемыПоИмени, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Удаляет элемент из коллекции элементов схемы, в т.ч. при необходимости и связанные с ним.
//
// Параметры:
//    рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
//    рИмяЭлемента - строка, уникальное имя элемента в пределах схемы;
//    рУдалятьСвязанныеЛинии - булево; имеет смысл только при удалении фигуры (не Линии), по умолчанию Ложь;
//        если Истина, то линии, входящие в фигуру и исходящие из неё, также будут удалены;
//        если Ложь, то линии, входящие в фигуру и исходящие из неё, будут вести "в никуда" (в связочные id ставится -1).
//
Процедура УдалитьЭлементСхемыПоИмени(рСхема,рИмяЭлемента,рУдалятьСвязанныеЛинии=Ложь) Экспорт
Попытка
    знчСхемы=Неопределено;
    мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
    Если мЭлементовГС.Количество()=0 Тогда Возврат КонецЕсли; // нет ни одного элемента
    
    мНенужных=Новый Массив;
    пози=Неопределено;
    Для й=0 По мЭлементовГС.Количество()-1 Цикл
        Если СокрЛП(СоотСвойство(мЭлементовГС[й],"itemCode"))=рИмяЭлемента Тогда
            мНенужных.Добавить(мЭлементовГС.Получить(й)); Прервать;
        КонецЕсли;
    КонецЦикла;
    Если мНенужных.Количество()=1 Тогда
        рУдаляемый=мНенужных.Получить(0);
        рТипЭлемента=СоотСвойство(рУдаляемый,"itemType","Число");
        Если рТипЭлемента=Неопределено Тогда Возврат КонецЕсли;
        Если рТипЭлемента=1 Тогда // это линия
            // пока ничего не делаем
        Иначе
            идФигуры=СоотСвойство(рУдаляемый,"itemId","Число");
            Для каждого рЭлемент Из мЭлементовГС Цикл
                Если СоотСвойство(рЭлемент,"itemType","Число")<>1 Тогда Продолжить КонецЕсли; // не линия
                Если СоотСвойство(рЭлемент,"connectFromItemId","Число")=идФигуры Тогда
                    Если рУдалятьСвязанныеЛинии Тогда
                        мНенужных.Добавить(рЭлемент);
                    Иначе
                        рЭлемент.Вставить("connectFromItemId",-1); // обрезаем
                    КонецЕсли;                    
                КонецЕсли;
                Если СоотСвойство(рЭлемент,"connectToItemId","Число")=идФигуры Тогда
                    Если рУдалятьСвязанныеЛинии Тогда
                        мНенужных.Добавить(рЭлемент);
                    Иначе
                        рЭлемент.Вставить("connectToItemId",-1); // обрезаем
                    КонецЕсли;                    
                КонецЕсли;
            КонецЦикла; // по поиску линий, связанных с удаляемой фигурой
        КонецЕсли; // если удаляется фигура/линия
    КонецЕсли;
    //
    // собственно удаляем
    Для каждого рЭлемент Из мНенужных Цикл
        мЭлементовГС.Удалить(мЭлементовГС.Найти(рЭлемент));
    КонецЦикла;
    
    знчСхемы.Вставить("item",мЭлементовГС);
    рСхема.Вставить("#value",знчСхемы);
Исключение
    Сообщить("УдалитьЭлементСхемыПоИмени, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры

// Возвращает структуру ВариантаВыбора по её имени или порядковому номеру среди вариантов.
// При ошибке или отсутствии данных возвращает Неопределено.
//
// Параметры:
//    рЭлемент - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
//    рИмяИлиНомер - строковое имя или числовой номер (от 0 по возрастанию в порядке №№ вариантов в выборе).
//
Функция ПолучитьВариантВыбораПоИмениИлиНомеру(рЭлемент,рИмяИлиНомер) Экспорт
Попытка
    мВариантов=СоотСвойство(рЭлемент,"transition");
    Если ТипЗнч(мВариантов)<>Тип("Массив") Тогда Возврат Неопределено КонецЕсли;
    //
    Если ТипЗнч(рИмяИлиНомер)=Тип("Строка") Тогда // по имени
        Для й=0 По мВариантов.Количество()-1 Цикл
            рВариант=мВариантов.Получить(й);
            Если СокрЛП(СоотСвойство(рВариант,"name"))<>СокрЛП(рИмяИлиНомер) Тогда Продолжить КонецЕсли;
            стру=Новый Структура;
            стру.Вставить("НомерВарианта",й);
            Для каждого киз Из рВариант Цикл
                стру.Вставить(киз.Ключ,киз.Значение);
            КонецЦикла;
            Возврат стру;
        КонецЦикла;
    ИначеЕсли ТипЗнч(рИмяИлиНомер)=Тип("Число") Тогда
        Если мВариантов.Количество()<=рИмяИлиНомер Тогда Возврат Неопределено КонецЕсли;
        Возврат мВариантов.Получить(рИмяИлиНомер);
    КонецЕсли;
    //
    Возврат Неопределено;
Исключение
    Сообщить("ПолучитьВариантВыбораПоИмениИлиНомеру, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Заполняет структуру ВариантаВыбора по её имени или порядковому номеру среди вариантов переданными значениями коллекции.
// Если варианта с таким именем/номером нет, ничего не делает.
//
// Параметры:
//    рЭлемент - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
//    рИмяИлиНомер - строковое имя или числовой номер (от 0 по возрастанию в порядке №№ вариантов в выборе);
//    рЗначение - структура/соответствие данных конкретного варианта выбора.
//
Процедура ОбновитьВариантВыбораПоИмениИлиНомеру(рЭлемент,рИмяИлиНомер,рЗначение) Экспорт
Попытка
    мВариантов=СоотСвойство(рЭлемент,"transition");
    Если ТипЗнч(мВариантов)<>Тип("Массив") Тогда Возврат КонецЕсли;
    //
    Если мВариантов.Количество()<=рИмяИлиНомер Тогда Возврат КонецЕсли;
    //
    Если ТипЗнч(рИмяИлиНомер)=Тип("Строка") Тогда // по имени
        Для й=0 По мВариантов.Количество()-1 Цикл
            рВариант=мВариантов.Получить(й);
            Если СокрЛП(СоотСвойство(рВариант,"name"))=СокрЛП(рИмяИлиНомер) Тогда
                Для каждого киз Из рЗначение Цикл
                    рВариант.Вставить(киз.Ключ,киз.Значение);
                КонецЦикла;
                Прервать;
            КонецЕсли;            
        КонецЦикла;
    ИначеЕсли ТипЗнч(рИмяИлиНомер)=Тип("Число") Тогда
        рВариант=мВариантов.Получить(рИмяИлиНомер);
        Для каждого киз Из рЗначение Цикл
            рВариант.Вставить(киз.Ключ,киз.Значение);
        КонецЦикла;
    КонецЕсли;    
    //
    рЭлемент.Вставить("transition",мВариантов);
    //
Исключение
    Сообщить("ОбновитьВариантВыбораПоИмениИлиНомеру, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры

// Удаляет элемент ВариантаВыбора по её имени или порядковому номеру среди вариантов из массива вариантов выбора.
// Если варианта с таким именем/номером нет, ничего не делает.
//
// Параметры:
//    рЭлемент - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
//    рИмяИлиНомер - строковое имя или числовой номер (от 0 по возрастанию в порядке №№ вариантов в выборе).
//
Процедура УдалитьВариантВыбораПоИмениИлиНомеру(рЭлемент,рИмяИлиНомер) Экспорт
Попытка
    мВариантов=СоотСвойство(рЭлемент,"transition");
    Если ТипЗнч(мВариантов)<>Тип("Массив") Тогда Возврат КонецЕсли;
    //
    Если ТипЗнч(рИмяИлиНомер)=Тип("Строка") Тогда // по имени
        пози=Неопределено;
        Для й=0 По мВариантов.Количество()-1 Цикл
            рВариант=мВариантов.Получить(й);
            Если СокрЛП(СоотСвойство(рВариант,"name"))=СокрЛП(рИмяИлиНомер) Тогда
                пози=й; Прервать;
            КонецЕсли;
        КонецЦикла;        
    ИначеЕсли ТипЗнч(рИмяИлиНомер)=Тип("Число") Тогда
        Если мВариантов.Количество()<=рИмяИлиНомер Тогда Возврат КонецЕсли;
        пози=рИмяИлиНомер;
    Иначе
        Возврат;
    КонецЕсли;
    //
    Если пози<>Неопределено Тогда
        мВариантов.Удалить(пози);
        рЭлемент.Вставить("transition",мВариантов);
    КонецЕсли;    
    //
Исключение
    Сообщить("УдалитьВариантВыбораПоИмениИлиНомеру, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры    
    
#КонецОбласти


// Преобразует соответствие структур и массивов в графическую схему. Возвращает объект типа "Графическая схема".
// При ошибке возвращает Неопределено.
//
// Параметры:
//    рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы.
//
Функция СобратьГрафическуюСхему(рСхема) Экспорт
Попытка
    // убираем служебные поля
    Попытка рСхема.Удалить("СтруктурыТиповЭлементов") Исключение КонецПопытки;
    //    
    знчСхемы=Неопределено;
    мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
    Если мЭлементовГС.Количество()=0 Тогда
        знчСхемы.Удалить("item");
    Иначе
        Для каждого рЭлементГС Из мЭлементовГС Цикл
            Попытка рЭлементГС.Удалить("Имя") Исключение КонецПопытки;
            Попытка рЭлементГС.Удалить("Тип") Исключение КонецПопытки;
        КонецЦикла;
        знчСхемы.Вставить("item",мЭлементовГС);
    КонецЕсли;    
    рСхема.Вставить("#value",знчСхемы);
    
    // приводим из коллекций в строку JSON
    рЗапись=Новый ЗаписьJSON;
    рЗапись.УстановитьСтроку(); // кодировка и прочее тут неважно
    ЗаписатьJSON(рЗапись,рСхема);
    //
    строСхемы=рЗапись.Закрыть();
    //сообщить("Сборка: "+Символы.ПС+стросхемы);
    //
    // десериализуем в ГС
    рЧтение=Новый ЧтениеJSON;    
    рЧтение.УстановитьСтроку(строСхемы);
    Возврат СериализаторXDTO.ПрочитатьJSON(рЧтение,Тип("ГрафическаяСхема"));
Исключение
    Сообщить("СобратьГрафическуюСхему, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Преобразует графическую схему в соответствие соответствий и массивов. Возвращает соответствие.
// Внимание! Даже если ранее в свойствах коллекций были структуры, они заменятся на соответствия, т.к. только такое восстановление из JSON возможно.
// Дописывает служебные поля: в коллекцию самой схемы дописывает СтруктурыТиповЭлементов (кэш структур всех типов элементов ГС),
// в коллекцию каждого элемента ГС дописывает Имя (строковое имя элемента) и Тип (тип в нотации 1С, например, Тип("ЭлементГрафическойСхемыДействие")).
// При ошибке возвращает Неопределено.
//
// Параметры:
//    рГС - объект типа "Графическая схема".
//
Функция РазобратьГрафическуюСхему(рГС) Экспорт
Попытка
    // нормализуем ГС (по неизвестным причинам, обратная сериализация не понимает, например. жирный шрифт)
    рШрифт=Новый Шрифт; // по умолчанию (восстанавливать оформление, если надо, потом и другими средствами)
    Для каждого рЭлементГС Из рГС.ЭлементыГрафическойСхемы Цикл
        Попытка рЭлементГС.Шрифт=рШрифт Исключение КонецПопытки;
    КонецЦикла;
    
    // сериализуем ГС
    рЗапись=Новый ЗаписьJSON;
    рЗапись.УстановитьСтроку();
    СериализаторXDTO.ЗаписатьJSON(рЗапись,рГС,НазначениеТипаXML.Явное);
    //
    строСхемы=рЗапись.Закрыть();
    //сообщить("Разборка: "+Символы.ПС+стросхемы);
    //
    // приводим из строки JSON в коллекцию (все структуры будут отражены как соответствия!)
    рЧтение=Новый ЧтениеJSON;
    рЧтение.УстановитьСтроку(строСхемы);
    рСхема=ПрочитатьJSON(рЧтение,Истина);
    
    Если ТипЗнч(рСхема)<>Тип("Соответствие") Тогда Возврат Неопределено КонецЕсли;
    
    // добавляем служебные поля
    рСхема.Вставить("СтруктурыТиповЭлементов",ПостроитьКэшСтруктурВсехТиповЭлементовСхемы());
    
    // добавляем служебные тип и имя к каждому элементу и вносим всё обратно в схему
    знчСхемы=Неопределено;
    мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
    Если мЭлементовГС.Количество()>0 Тогда
        спТипов=ПостроитьНотациюТиповЭлементовСхемы();
        Для каждого рЭлементГС Из мЭлементовГС Цикл
            рЭлементГС.Вставить("Имя",СоотСвойство(рЭлементГС,"itemCode"));
            знчТип=спТипов.НайтиПоЗначению(СоотСвойство(рЭлементГС,"itemType","Число"));
            Если знчТип<>Неопределено Тогда
                рЭлементГС.Вставить("Тип",    Тип("ЭлементГрафическойСхемы"+СокрЛП(знчТип.Представление)));
            Иначе
                рЭлементГС.Вставить("Тип",Неопределено);
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    //            
    знчСхемы.Вставить("item",мЭлементовГС);
    рСхема.Вставить("#value",знчСхемы);
    
    Возврат рСхема;    
Исключение
    Сообщить("РазобратьГрафическуюСхему, общая ошибка: "+ОписаниеОшибки());
    Возврат Неопределено;
КонецПопытки;
КонецФункции    

#КонецОбласти 

Кому пригодится или кого заинтересует - будет хорошо.

Ещё раз спасибо гигантам, на плечах которых стоит это решение. В первую очередь Diversus'у, высказавшему идею ещё в далёкие времена.

 

 

37

Скачать файлы

Наименование Файл Версия Размер
Программная работа с графическими схемами. Готовое решение.:
.epf 86,47Kb
17.02.18
15
.epf 86,47Kb 15 Скачать

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. genayo 18.02.18 17:48 Сейчас в теме
А можно рассказать о практическом применении, если не трудно?
2. triviumfan 7 18.02.18 18:36 Сейчас в теме
(1)
Концепцию взял у //infostart.ru/public/551576/ и допилил под свой извращённый вкус.

Вроде все понятно, любит извращения. А для рисования есть другое ПО :)
3. Yashazz 2316 18.02.18 20:17 Сейчас в теме
(1) Ну, одно из интересных применений, авось, в ближайшие недели 2-3 выложу как отдельную публикацию, а вообще, например, визуализация сценариев работы сотрудников колл-центра. Последовательности вопросов при общении с клиентами. А вот что стало самым главным заказом, я рассказать не смогу, т.к. заказчиком выступает ФНС и всё секретно)

(2) Для рисования таких схем есть Visio, но плюсы схем в 1С, думаю, очевидны. Какое ещё ПО вы знаете? Можете ли предложить готовое решение работы с ним?
4. pm74 130 18.02.18 20:45 Сейчас в теме
(0) тема интересная плюсанул , поразбираюсь на досуге
с визуализацией в 1с плохо - это факт
поэтому на ИС традиционно высок интерес к разным штуковинам на javascript или наподобие этой
мои 5 коп. на тему применимости см. рис. (до правильной программной компоновки тоже руки пока не дошли)
Прикрепленные файлы:
5. Yashazz 2316 19.02.18 11:18 Сейчас в теме
(4) А вот этот замечательный скриншот - он руками был сделан? Можно подробнее в личку?
6. pm74 130 19.02.18 11:48 Сейчас в теме
(5)
он руками был сделан

нет ну что вы , конечно программно
сделал по мотивам какой то публикации, к сожалению не помню авторства
используется для формирования схемы ДКАпо таблице переходов

&НаСервере
Функция НоваяТочкаXDTO(x,y) 	Точка=СериализаторXDTO.Фабрика.Создать(СериализаторXDTO.Фабрика.Тип("http://v8.1c.ru/8.2/data/graphscheme","Point"));
	Точка.x=x;
	Точка.y=y;	
	Возврат Точка;
КонецФункции
&НаСервере
Функция НовыйОбъектXDTO(Тип,ИмяЭлемента,Верх,Лево,Высота,Ширина,Содержание,Фигура,Цвет=Неопределено,Шрифт=Неопределено,LastID=0)Экспорт
	НО=СериализаторXDTO.Фабрика.Создать(СериализаторXDTO.Фабрика.Тип("http://v8.1c.ru/8.2/data/graphscheme","GraphSchemeItem"));
	НО.itemType=Тип;
	НО.itemCode=ИмяЭлемента;
	НО.itemId=LastID;
	НО.zOrder=LastID;
	LastID=LastID+1;
	НО.itemTabOrder=1;
	НО.rectLeft=Лево;
	НО.rectRight=Лево+Ширина;
	НО.rectTop=Верх;
	НО.rectBottom=Верх+Высота;
	НО.Border=СериализаторXDTO.ЗаписатьXDTO(Новый Линия(ТипСоединительнойЛинии.Сплошная,1));
	НО.Point.Добавить(НоваяТочкаXDTO(НО.rectLeft,НО.rectTop));
	НО.Point.Добавить(НоваяТочкаXDTO(НО.rectRight-1,НО.rectTop));
	НО.Point.Добавить(НоваяТочкаXDTO(НО.rectRight-1,НО.rectBottom-1));
	НО.Point.Добавить(НоваяТочкаXDTO(НО.rectLeft,НО.rectBottom-1));
	Заголовки=СериализаторXDTO.Фабрика.Создать(СериализаторXDTO.Фабрика.Тип("http://v8.1c.ru/8.1/data/core","LocalStringType"));
	ЗаголовокЭлемента=СериализаторXDTO.Фабрика.Создать(СериализаторXDTO.Фабрика.Тип("http://v8.1c.ru/8.1/data/core","LocalStringItemType"));
	ЗаголовокЭлемента.lang="#";
	ЗаголовокЭлемента.content=Содержание;
	Заголовки.Item.Добавить(ЗаголовокЭлемента);
	НО.itemTitle=Заголовки;
	Примечания=СериализаторXDTO.Фабрика.Создать(СериализаторXDTO.Фабрика.Тип("http://v8.1c.ru/8.1/data/core","LocalStringType"));
	Примечание=СериализаторXDTO.Фабрика.Создать(СериализаторXDTO.Фабрика.Тип("http://v8.1c.ru/8.1/data/core","LocalStringItemType"));
	Примечание.lang="#";
	Примечание.content=Содержание;
	Примечания.Item.Добавить(Примечание);
	НО.tipText=Примечания;
	НО.currentLanguage="#";
	НО.shape=Фигура;
	Если Цвет=Неопределено Тогда
		НО.backColor=Новый Цвет;
	Иначе
		НО.backColor=Новый Цвет(Цвет[0],Цвет[1],Цвет[2]);
	КонецЕсли;
	
	Если Не Шрифт=Неопределено Тогда
		НО.textFont=СериализаторXDTO.ЗаписатьXDTO(Шрифт);
		
	КонецЕсли;	
	Возврат НО;
КонецФункции
&НаСервере
Функция НоваяЛинияXDTO(ГрафСхемаXDTO,ПервыйЭлемент,ВторойЭлемент,Содержание,Подсказка,ПортВыхода=Неопределено,ПортВхода=Неопределено,LastID=0,Цвет=Неопределено)Экспорт
	НО=СериализаторXDTO.Фабрика.Создать(СериализаторXDTO.Фабрика.Тип("http://v8.1c.ru/8.2/data/graphscheme","GraphSchemeItem"));
	НО.itemType=1;
	НО.itemId=LastID+100;
	//НО.zOrder=LastID+100;
	LastID=LastID+1;
	НО.itemTabOrder=2;
	НО.itemCode=Подсказка;
	Заголовки=СериализаторXDTO.Фабрика.Создать(СериализаторXDTO.Фабрика.Тип("http://v8.1c.ru/8.1/data/core","LocalStringType"));
	ЗаголовокЭлемента=СериализаторXDTO.Фабрика.Создать(СериализаторXDTO.Фабрика.Тип("http://v8.1c.ru/8.1/data/core","LocalStringItemType"));
	ЗаголовокЭлемента.lang="#";
	ЗаголовокЭлемента.content=Содержание;
	Заголовки.Item.Добавить(ЗаголовокЭлемента);
	НО.itemTitle=Заголовки;
	НО.currentLanguage="#";
	НО.textPos="Middle";
	НО.beginArrowStyle=СтильСтрелки.Нет;
	НО.endArrowStyle=СтильСтрелки.Незаполненная;
	НО.lineColor=Цвет;
	
	ДанныеПервый=ПортыЭлементаXDTO(ГрафСхемаXDTO,ПервыйЭлемент);
	ДанныеВторой=ПортыЭлементаXDTO(ГрафСхемаXDTO,ВторойЭлемент);
	
	НО.connectFromItemId=ДанныеПервый.itemId;
	НО.connectToItemId=ДанныеВторой.itemId;
	НО.connectFromPortIndex=0;
	
	Если ПортВыхода=Неопределено Или ПортВхода=Неопределено Тогда
		Если ДанныеПервый.itemId<ДанныеВторой.itemId Тогда
			ПортВыхода=2;ПортВхода=2;
		ИначеЕсли ДанныеПервый.itemId>ДанныеВторой.itemId Тогда 
			ПортВыхода=4; ПортВхода=4;
		Иначе 
			ПортВыхода=1; ПортВхода=2;
		КонецЕсли;
	КонецЕсли;	
	
	НО.portIndexFrom=ПортВыхода;
	НО.portIndexTo=ПортВхода;
	НО.backColor=новый Цвет;
	НО.decorativeLine=Истина;
	НО.textFont=СериализаторXDTO.ЗаписатьXDTO(новый Шрифт("Calibri",10,,Истина));
	НО.textColor=Цвет;
	НО.Border=СериализаторXDTO.ЗаписатьXDTO(новый Линия(ТипСоединительнойЛинии.Сплошная,1));
	НО.Point.Добавить(НоваяТочкаXDTO(ДанныеПервый["port"+ПортВыхода+"x"],ДанныеПервый["port"+ПортВыхода+"y"]));
	НО.Point.Добавить(НоваяТочкаXDTO(ДанныеВторой["port"+ПортВхода+"x"],ДанныеВторой["port"+ПортВхода+"y"]));	
	Возврат НО;
КонецФункции
&НаСервере
Функция ПортыЭлементаXDTO(ГрафСхемаXDTO,itemCode)Экспорт
	Данные=Новый Структура;
	Данные.Вставить("itemId",0);
	Данные.Вставить("port1x",0);
	Данные.Вставить("port1y",0);
	Данные.Вставить("port2x",0);
	Данные.Вставить("port2y",0);
	Данные.Вставить("port3x",0);
	Данные.Вставить("port3y",0);
	Данные.Вставить("port4x",0);
	Данные.Вставить("port4y",0);
	Для Каждого Элемент Из ГрафСхемаXDTO.item Цикл
		Если СокрЛП(НРег(Элемент.itemCode))=СокрЛП(НРег(itemCode)) Тогда
			Данные.Вставить("itemId",Элемент.itemId);
			Данные.Вставить("port1x",Элемент.rectLeft);
			Данные.Вставить("port1y",Элемент.rectTop+(Элемент.rectBottom-Элемент.rectTop)/2);
			Данные.Вставить("port2x",Элемент.rectLeft+(Элемент.rectRight-Элемент.rectLeft)/2);
			Данные.Вставить("port2y",Элемент.rectTop);
			Данные.Вставить("port3x",Элемент.rectRight);
			Данные.Вставить("port3y",Элемент.rectTop+(Элемент.rectBottom-Элемент.rectTop)/2);
			Данные.Вставить("port4x",Элемент.rectLeft+(Элемент.rectRight-Элемент.rectLeft)/2);
			Данные.Вставить("port4y",Элемент.rectBottom);
			Прервать;
		КонецЕсли;	  
	КонецЦикла;
	Возврат Данные;
КонецФункции
&НаСервере
Функция ИндексЭлементаXDTO(ГрафСхемаXDTO,itemCode)Экспорт
	Для Й=0 По ГрафСхемаXDTO.item.Количество()-1 Цикл
		Если СокрЛП(НРег(ГрафСхемаXDTO.item[Й].itemCode))=СокрЛП(НРег(itemCode)) Тогда
			Возврат Й;
		КонецЕсли;	  
	КонецЦикла;
	Возврат -1;
КонецФункции
&НаСервере
Функция НайтиЭлементXDTO(ГрафСхемаXDTO,itemCode)Экспорт
	Для Каждого Элемент Из ГрафСхемаXDTO.item Цикл
		Если СокрЛП(НРег(Элемент.itemCode))=СокрЛП(НРег(itemCode)) Тогда
			Возврат Элемент;
		КонецЕсли;	  
	КонецЦикла;
	Возврат Неопределено;
КонецФункции
&НаСервере
Процедура УдалитьЭлементXDTO(ГрафСхемаXDTO,itemCode)Экспорт
	ГрафСхемаXDTO.item.Удалить(ИндексЭлементаXDTO(ГрафСхемаXDTO,itemCode));
КонецПроцедуры
&НаСервере
Функция ПолучитьТекстЭлементаXDTO(ГрафСхемаXDTO,itemCode)Экспорт
	Элемент=НайтиЭлементXDTO(ГрафСхемаXDTO,itemCode);
	Возврат Элемент.itemTitle.item[0].content;
КонецФункции
&НаСервере
Процедура ИзменитьТекстЭлементаXDTO(ГрафСхемаXDTO,itemCode,НовыйТекст)Экспорт
	Элемент=НайтиЭлементXDTO(ГрафСхемаXDTO,itemCode);
	Элемент.itemTitle.item[0].content=НовыйТекст;
КонецПроцедуры


Показать


к сожалению работает не идеально , порты линий нужно проставить вручную , линии переходов иногда "склеиваются " , чтобы их разделить нужно немного "пошевелить " элемент
7. pm74 130 19.02.18 12:14 Сейчас в теме
(5)с графической схемой работать удобно , единственный минус(имо) - нет расшифровки , поэтому сделать полностью интеракивной (например как рис.) сложновато
Прикрепленные файлы:
8. Yashazz 2316 19.02.18 12:50 Сейчас в теме
В предложенной публикации порты управляются программно, их можно перевешивать как угодно. А что линии склеиваются, так это штатная манера самой 1С, для созданных вручную всё так же.
нет расшифровки

Это почему? Есть расшифровка, у меня часть бизнес-логики именно на ней основана. Очень даже есть.
9. pm74 130 19.02.18 12:57 Сейчас в теме
(8)
Есть расшифровка

интересненько , я пока внимательно не прочитал (имел в виду "родную" расшифровку как в ТД )
интерфейс наподобие обработки расшифровки тоже есть ?

(8)
управляются программно
т.е. есть алгоритм для "красивого" расположения входных и выходных портов ?
10. Yashazz 2316 19.02.18 17:49 Сейчас в теме
(9) Конечно есть. Роднее некуда. Собственно, это штатная возможность граф.схем, я ею пользовался, но ничего сверхъестественного не делал, чтобы она работала, она как раз и сама по себе в платформе очень даже.

Алгоритма нет, есть параметр, который этим управляет. Свойство с типом, как в платформе. Делать алгоритм для красоты, скажу честно, было тупо некогда)
11. pm74 130 19.02.18 19:43 Сейчас в теме
(10)
Собственно, это штатная возможность граф.схем, я ею пользовался, но ничего сверхъестественного не делал

Я и впрямь что-то упустил. (связанную с элементами ГС какую- то коллекцию-реквизит пока не рассматриваем) , вроде в ГС нет параметров из которых можно напрямую извлечь ссылку на объект базы данных.
Не могли бы пояснить , а то чего-то недопонимаю.
(10)
есть параметр, который этим управляет
это понятно , имел в виду вручную (либо по умолчанию) назначение номеров входов / выходов

Под "красивой" расстановкой подразумевалось полностью программное расположение элементов , обеспечивающее наилучшую компактность и читабельность схемы при наименьшем пересечении линий.
12. Serg O. 156 21.02.18 07:00 Сейчас в теме
Листинг на 5 стр. Зачем? Читать код тут...никто не будет.
Сами же пишете, что у Вас готовое для допила решение
13. Yashazz 2316 21.02.18 19:23 Сейчас в теме
(12) Читать не будут. Копипастить будут) Особенно те, у кого стартманей нету)) Читайте внимательнее: обработка - для допила, а код - уже боевой.
Оставьте свое сообщение