gifts2017

Программное создание графических схем (v.2): API для ГрафическойСхемы

Опубликовал Serg (serg_infostart) в раздел Программирование - Практика программирования

Пример динамического создания графических схем, добавления элементов любых видов. Любые схемы без бизнес-процессов. Программная работа со схемой. Отличие от существующей статьи в том, что здесь используется объектная модель. Исправил и упростил некоторые моменты - результат соответствует схеме, созданной руками. Добавил возможность рисования произвольных форм для существующих фигур.

Создано по мотивам статьи: Программное создание графических схем от Yashazz

Увидев решение об XML-сериализации ГрафическойСхемы (и десериализации ее) мелькнула мысль: "А ведь можно сделать и JSON-сериализацию". Но ведь JSON можно и десериализовать в Массивы и Соответствия. Вот она - объектная модель! Оперируем Структурами, Массивами и Соответствиями, далее делаем JSON сериализацию и десериализацию в ГрафическуюСхему.

Мой код, создающий ту же самую схему из статьи:

	// Здесь только для понимания. Вообще везде передаются коды типов, цифры
	ТипЭлементаГрафическойСхемы = Новый Структура;
	ТипЭлементаГрафическойСхемы.Вставить("Декорация",0);		//0		"Декорация"
	ТипЭлементаГрафическойСхемы.Вставить("ДекоративнаяЛиния",1);//1		"ДекоративнаяЛиния"
	ТипЭлементаГрафическойСхемы.Вставить("Старт",2);			//2		"Старт"
	ТипЭлементаГрафическойСхемы.Вставить("Завершение",3);		//3		"Завершение"
	ТипЭлементаГрафическойСхемы.Вставить("Условие",4);			//4		"Условие"
	ТипЭлементаГрафическойСхемы.Вставить("Действие",5);			//5		"Действие"
	ТипЭлементаГрафическойСхемы.Вставить("ВыборВарианта",6);	//6		"ВыборВарианта"
	ТипЭлементаГрафическойСхемы.Вставить("Разделение",7);		//7		"Разделение"
	ТипЭлементаГрафическойСхемы.Вставить("Слияние",8);			//8		"Слияние"
	ТипЭлементаГрафическойСхемы.Вставить("Обработка",9);		//9		"Обработка"
	ТипЭлементаГрафическойСхемы.Вставить("ВложенныйПроцесс",10);//10	"ВложенныйПроцесс"
	
	// Инициализация
	Схема = ИнициализироватьСхему();
	
	// Добавляем элементы один за другим
	_старт = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.Старт,"ТестСтарта");
	_старт.itemTitle.Добавить(Новый Структура("lang,content","#","Погнали!"));
	_id_старт = ДобавитьЭлемент(Схема,_старт,100,30,100,40);
	
	_действие = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.Действие,"ТестДействия");
	_действие.itemTitle.Добавить(Новый Структура("lang,content","#","Это действие"));
	_действие.taskDescription = "Некое действие в системе";
	_действие.explanation = "И.И.Иванов";
	_id_действие = ДобавитьЭлемент(Схема,_действие,100,120,100,60);
	
	_условие = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.Условие,"ТестУсловия");
	_условие.itemTitle.Добавить(Новый Структура("lang,content","#","А я знаю?"));
	_id_условие = ДобавитьЭлемент(Схема,_условие,100,220,100,40);
	
	_действие2 = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.Действие,"ТестДействия2");
	_действие2.itemTitle.Добавить(Новый Структура("lang,content","#","Тоже что-то"));
	_действие2.taskDescription = "Некое действие в системе";
	_действие2.explanation = "Петров";
	_id_действие2 = ДобавитьЭлемент(Схема,_действие2,10,280,100,60);
	
	_линия2 = ПодготовитьСтруктуруЛинии(Схема,_id_условие,_id_действие2,1,2,Ложь,"ТестЛинии2");
	_линия2.itemTitle.Добавить(Новый Структура("lang,content","#","Нет"));
	ДобавитьЭлемент(Схема,_линия2);
	
	_обработка = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.Обработка,"ТестОбработки");
	_обработка.itemTitle.Добавить(Новый Структура("lang,content","#","Зашли сюды"));
	_id_обработка = ДобавитьЭлемент(Схема,_обработка,180,290,100,60);
	
	_линия3 = ПодготовитьСтруктуруЛинии(Схема,_id_условие,_id_обработка,3,2,Ложь,"ТестЛинии3");
	_линия3.itemTitle.Добавить(Новый Структура("lang,content","#","Да"));
	ДобавитьЭлемент(Схема,_линия3);
	
	_завершение = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.Завершение,"ТестСтопа");
	_завершение.itemTitle.Добавить(Новый Структура("lang,content","#","Тпрууу!"));
	_id_завершение = ДобавитьЭлемент(Схема,_завершение,740,210,100,40);
	
	_процесс = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.ВложенныйПроцесс,"ТестБП");
	_процесс.itemTitle.Добавить(Новый Структура("lang,content","#","Вложенный БП"));
	_процесс.taskDescription = "Нечто внутри!";
	_id_процесс = ДобавитьЭлемент(Схема,_процесс,560,290,100,60);
	
	_выбор = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.ВыборВарианта,"ТестВарианта");
	_выбор.itemTitle.Добавить(Новый Структура("lang,content","#","Так как, собственно?"));
	__вар1 = Новый Структура("name,description,backColor","Вариант1",Новый Массив,_выбор.backColor);
	__вар1.description.Добавить(Новый Структура("lang,content","#","Превосходно!"));
	__вар2 = Новый Структура("name,description,backColor","Вариант2",Новый Массив,_выбор.backColor);
	__вар2.description.Добавить(Новый Структура("lang,content","#","Замечательно!"));
	_выбор.transition.Добавить(__вар1);
	_выбор.transition.Добавить(__вар2);
	_id_выбор = ДобавитьЭлемент(Схема,_выбор,440,100,140,100);
	ДобавитьЭлемент(Схема,ПодготовитьСтруктуруЛинии(Схема,_id_выбор,_id_завершение,7,2,Ложь)); 	// тут 7 это правая сторона 1-го варианта
	ДобавитьЭлемент(Схема,ПодготовитьСтруктуруЛинии(Схема,_id_выбор,_id_процесс,9,2,Ложь));		// тут 9 это правая сторона 2-го варианта
	// про 7 и 9 порты нужно пояснить...
	// у каждого элемента есть 5 основных портов (точек соединения): 1,2,3,4,5 (Лево, Верх, Право, Низ, Центр)
	// А далее идут стороны вариантов (только для элемента ВыборВарианта):
	// четные - левые стороны, нечетные - правые
	// поэтому 1 вариант левая сторона - это 6, а 1 вариант правая сторона - 7
	// для 2 варианта: 8 (лево) и 9 (право) соответственно
	// общая формула вычисления стороны (1 или 3 - лево или право) для портов > 5 следующая: portIndex = 1 + (portIndex%2)*2;
	
	_линия4 = ПодготовитьСтруктуруЛинии(Схема,_id_обработка,_id_выбор,3,2,Ложь,"ТестЛинии4");
	ДобавитьЭлемент(Схема,_линия4);
	
	ДобавитьЭлемент(Схема,ПодготовитьСтруктуруЛинии(Схема,_id_старт,_id_действие,4,2,Ложь));
	ДобавитьЭлемент(Схема,ПодготовитьСтруктуруЛинии(Схема,_id_действие,_id_условие,4,2,Ложь));
	
	// Вывод схемы
	ЗаписьJSON = Новый ЗаписьJSON;
	ЧтениеJSON = Новый ЧтениеJSON;
	
	ЗаписьJSON.УстановитьСтроку();
	ЗаписатьJSON(ЗаписьJSON,Схема);
	стрJSON = ЗаписьJSON.Закрыть();
	Сообщить(стрJSON);
	ЧтениеJSON.УстановитьСтроку(стрJSON);
	зн = СериализаторXDTO.ПрочитатьJSON(ЧтениеJSON,Тип("ГрафическаяСхема")); // Вот он результат!
	ЭлементыФормы.ГС.УстановитьСхему(зн); // Мой элемент на форме называется ГС

Можно оценить экономию места в модуле.

Модуль, который реализует API для работы с ГрафическимиСхемами
Функция ИнициализироватьСхему() Экспорт
	стрСхема = Новый Структура;
	стрСхема.Вставить("backColor"			, Новый Соответствие);
	стрСхема.backColor.Вставить("#type"		, "jv8ui:Color");
	стрСхема.backColor.Вставить("#value"	, "{http://v8.1c.ru/8.1/data/ui/style}FieldBackColor");
	стрСхема.Вставить("enableGrid"			, Истина); // Включить сетку
	стрСхема.Вставить("drawGridMode"		, "Lines"); // Сетка. Варианты: None,Dots,Chess,Lines
	стрСхема.Вставить("gridVerticalStep"	, 20);
	стрСхема.Вставить("gridHorizontalStep"	, 20);
	стрСхема.Вставить("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",стрСхема);
	Возврат значСхема;
КонецФункции

Функция ДобавитьЭлемент(Схема,СтруктураЭлемента,x=Неопределено,y=Неопределено,dx=40,dy=40) Экспорт // dx,dy - тут для упрощения
	
	структураСхемы = Схема.Получить("#value");
	Если НЕ структураСхемы.Свойство("item") Тогда
		структураСхемы.Вставить("item", Новый Массив); // добавлять только если есть хотя бы один элемент
	КонецЕсли;
	
	Если НайтиПоЗначениюКлючаВМассивеСтруктур(структураСхемы.item,"itemCode",СтруктураЭлемента.itemCode) = СтруктураЭлемента Тогда
		Возврат СтруктураЭлемента.itemid;
	КонецЕсли;
	
	// Ищем максимальный itemid
	СтруктураЭлемента.itemid = ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(структураСхемы.item,"itemid") + 1;
	
	// Ищем такой же itemCode
	Если НайтиПоЗначениюКлючаВМассивеСтруктур(структураСхемы.item,"itemCode",СтруктураЭлемента.itemCode) <> Неопределено Тогда
		_базоваяЧасть = СтруктураЭлемента.itemCode;
		_нс = 1;
		Пока НайтиПоЗначениюКлючаВМассивеСтруктур(структураСхемы.item,"itemCode",_базоваяЧасть + Формат(_нс,"ЧГ=")) <> Неопределено Цикл
			_нс = _нс + 1;
		КонецЦикла;
		СтруктураЭлемента.itemCode = _базоваяЧасть + Формат(_нс,"ЧГ=");
	КонецЕсли;
	
	// Ищем максимальный itemTabOrder
	СтруктураЭлемента.itemTabOrder = ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(структураСхемы.item,"itemTabOrder") + 5; // пока так. Это порядок обхода. Возможно нужно более продвинутое вычисление сделать (считать все подчиненные и соединенные элементы последнего и прибавлять на их количество к максимальному)
	
	// Ищем максимальный zOrder
	СтруктураЭлемента.zOrder = ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(структураСхемы.item,"zOrder",-1) + 1;
	
	// *** Устанавливаем координаты и размер (опциоально)
	Если СтруктураЭлемента.itemType <> 1 Тогда // если не Линия
		Если x = Неопределено И y <> Неопределено Тогда // ищем максимальный X
			x = ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(структураСхемы.item,"rectRight") + структураСхемы.gridHorizontalStep;
		КонецЕсли;
		Если y = Неопределено И x <> Неопределено Тогда // ищем максимальный Y
			y = ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(структураСхемы.item,"rectBottom") + структураСхемы.gridVerticalStep;
		КонецЕсли;
		ЗаполнитьЗначенияСвойств(СтруктураЭлемента,Новый Структура("rectLeft,rectTop,rectRight,rectBottom",x,y,x+dx,y+dy));
		РасставитьТочкиФигуры(СтруктураЭлемента);
	КонецЕсли;
	
	// ДОБАВЛЕНИЕ ПОДГОТОВЛЕННОГО ЭЛЕМЕНТА
	структураСхемы.item.Добавить(СтруктураЭлемента);
	
	Возврат СтруктураЭлемента.itemid;
	
КонецФункции

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

Функция ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(массивДляПоиска,ключСтруктуры,СтартовоеЗначениеКлюча=0)
	МаксимальноеЗначениеКлюча = СтартовоеЗначениеКлюча;
	Для каждого элемент из массивДляПоиска Цикл
		МаксимальноеЗначениеКлюча = Макс(МаксимальноеЗначениеКлюча,элемент[ключСтруктуры]);
	КонецЦикла;
	Возврат МаксимальноеЗначениеКлюча;
КонецФункции

Функция НайтиПоЗначениюКлючаВМассивеСтруктур(массивДляПоиска,ключСтруктуры,значениеКлюча)
	Ответ = Неопределено;
	Для каждого элемент из массивДляПоиска Цикл
		Если элемент[ключСтруктуры] = значениеКлюча Тогда
			Ответ = элемент;
			Прервать;
		КонецЕсли;
	КонецЦикла;
	Возврат Ответ;
КонецФункции

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

Функция ПодготовитьСтруктуруЛинии(Схема,connectFromItemId,connectToItemId,portIndexFrom=Неопределено,portIndexTo=Неопределено,decorativeLine=Истина,itemCode=Неопределено) Экспорт
	
	Ответ = ПолучитьСтруктуруТипа(1,itemCode);
	Ответ.decorativeLine = decorativeLine; // не декоративная, а прицепленная линия
	Ответ.connectFromItemId = connectFromItemId;
	Ответ.connectToItemId = connectToItemId;
	Ответ.portIndexFrom = ?(portIndexFrom=Неопределено,1,portIndexFrom); // лево
	Ответ.portIndexTo = ?(portIndexTo=Неопределено,2,portIndexTo); // верх
	
	// Рисуем только начало и окончание. Остальное система дополнит сама при отрисовке.
	Ответ.point.Добавить(ПолучитьКоординатыЭлемента(Схема,connectFromItemId,portIndexFrom,Ответ.connectFromPortIndex));
	Ответ.point.Добавить(ПолучитьКоординатыЭлемента(Схема,connectToItemId,portIndexTo));
	
	// Багфикс: случай когда линия идет снизу на верхнюю границу
	Если Ответ.point[0].y > Ответ.point[1].y И Ответ.portIndexTo = 2 Тогда
		//Ответ.point.Вставить(1,Новый Структура("x,y",Ответ.point[1].x,Ответ.point[1].y-Схема["#value"].gridVerticalStep));
		xmid = Цел((ПолучитьКоординатыЭлемента(Схема,connectFromItemId,3).x + ПолучитьКоординатыЭлемента(Схема,connectToItemId,1).x)/2);
		Ответ.point.Вставить(1,Новый Структура("x,y",xmid,Ответ.point[1].y-Схема["#value"].gridVerticalStep));
	КонецЕсли;
	
	Возврат Ответ;
	
КонецФункции

Функция ПолучитьКоординатыЭлемента(Схема,itemId,portIndex,connectFromPortIndex=0)
	Ответ = Новый Структура("x,y",0,0);
	
	структураСхемы = Схема.Получить("#value");
	Если НЕ структураСхемы.Свойство("item") Тогда
		Возврат Ответ;
	КонецЕсли;
	СтруктураЭлемента = НайтиПоЗначениюКлючаВМассивеСтруктур(структураСхемы.item,"itemid",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;
	КонецЕсли;
	
	Если portIndex = 1 Тогда
		Ответ.x = СтруктураЭлемента.rectLeft;
		Ответ.y = ?(dy>0,СтруктураЭлемента.rectBottom-1-dy,Цел((СтруктураЭлемента.rectTop+СтруктураЭлемента.rectBottom+1)/2));
	ИначеЕсли portIndex = 2 Тогда
		Ответ.x = Цел((СтруктураЭлемента.rectLeft+СтруктураЭлемента.rectRight+1)/2);
		Ответ.y = СтруктураЭлемента.rectTop;
	ИначеЕсли portIndex = 3 Тогда
		Ответ.x = СтруктураЭлемента.rectRight;
		Ответ.y = ?(dy>0,СтруктураЭлемента.rectBottom-1-dy,Цел((СтруктураЭлемента.rectTop+СтруктураЭлемента.rectBottom+1)/2));
	ИначеЕсли portIndex = 4 Тогда
		Ответ.x = Цел((СтруктураЭлемента.rectLeft+СтруктураЭлемента.rectRight+1)/2);
		Ответ.y = СтруктураЭлемента.rectBottom;
	ИначеЕсли portIndex = 5 Тогда
		Ответ.x = Цел((СтруктураЭлемента.rectLeft+СтруктураЭлемента.rectRight+1)/2);
		Ответ.y = Цел((СтруктураЭлемента.rectTop+СтруктураЭлемента.rectBottom+1)/2);
	КонецЕсли;
	
	Возврат Ответ;
КонецФункции

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

Всем спасибо за внимание, плюсы, лайки, баг-репорты...!

P.S.

Заметили в коде возможности рисования? Второй скрин - это как раз результат. В процедуре вывода добавил следующий код (с модификатором фигур):

Модуль вывода дополнительных фигур
	_разделение = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.Разделение,"ТестБП");
	_разделение.itemTitle.Добавить(Новый Структура("lang,content","#","Разделяем"));
	//_разделение.taskDescription = "Пишем свое описание";
	_id_разделение = ДобавитьЭлемент(Схема,_разделение,585,380,50,30);
	ДобавитьЭлемент(Схема,ПодготовитьСтруктуруЛинии(Схема,_id_процесс,_id_разделение,4,2,Ложь));

	_действие5 = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.Действие);
	_действие5.itemTitle.Добавить(Новый Структура("lang,content","#","Произвольное 
	|задание
	|"));
	_id_условие5 = ДобавитьЭлемент(Схема,_действие5,400,400,100,100);
	_действие5.explanation = "С.Ю.Сидоров";
	//РасставитьТочкиФигуры(_условие5,11); // галстук-бабочка
	РасставитьТочкиФигуры(_действие5,13); // круг
	ДобавитьЭлемент(Схема,ПодготовитьСтруктуруЛинии(Схема,_id_разделение,_id_условие5,4,2,Ложь));
	
	_процесс5 = ПолучитьСтруктуруТипа(ТипЭлементаГрафическойСхемы.Завершение);
	_процесс5.alignVer = "Top";
	_процесс5.itemTitle.Добавить(Новый Структура("lang,content","#","Звезда
	|шерифа
	|для победителя"));
	_id_процесс5 = ДобавитьЭлемент(Схема,_процесс5,700,400,100,100);
	РасставитьТочкиФигуры(_процесс5,12); // звезда шерифа
	ДобавитьЭлемент(Схема,ПодготовитьСтруктуруЛинии(Схема,_id_разделение,_id_процесс5,4,2,Ложь));

В планах упрощение API, вынос параметров координат в подготовку структуры элемента, из структуры элемента исключить вычисляемые поля (itemId, zOrder, itemTabOrder) для исключения ошибок, в процедуру ПодготовитьСтруктуруЛинии() добавить возможность обработки в качестве Источника и Приемника готовые структуры Элементов, а не только их itemId (Число) - опять же для удобства и сокращения кода. Результаты представлю по мере готовности...

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Владимир Насыров (Spacer) 28.09.16 09:47
Интересная статья. А есть ли какие нибудь наработки по динамическому созданию географических схем?
Возможно подобный подход применим и к ним.
2. Serg (serg_infostart) 28.09.16 10:35
(1) Spacer, пока нет. Не было необходимости. Но подозреваю, что этот же подход сработает.
3. Ruslan (rus128) 28.09.16 11:16
все красиво, кроме:
// Сдесь только для понимания.
Вообще-то правильно писать "Здесь".
4. Serg (serg_infostart) 28.09.16 16:17
(3) rus128, там еще много где косяки по тексту... поправлю в ближайшее время...
5. Serg (serg_infostart) 29.09.16 14:50
Понял, что можно рисовать любые фигуры. Накидал примеров в статью. :)
Конечно, если пользователь будет двигать края фигуры, то карета превратится опять в тыкву наша замечательная фигурка станет тем, чем является по сути.
С типовыми фигурами ничего не произойдет.

А вот и домашнее задание.
Кто нарисует машинку? (добавить кусок кода в процедуру РасставитьТочкиФигуры() с новым Модификатором)
6. Игорь Steelvan (Steelvan) 09.11.16 11:29
7. Яков Коган (Yashazz) 09.11.16 12:02
(5) serg_infostart, здрасссьте. Я вроде об этом писал, что можно любые фигурки рисовать. Удобнее всего - из фигуры "Условие", она как шестиугольник наиболее гибкая.
В целом - была у меня мысль с json поработать, но в 1С это штука новая, а всё новое в платформе первые года 3 косячит и лучше не связываться) Хотя json лаконичнее, конечно.
8. Андрей Белов (ЧерныйКот) 09.11.16 14:30
Делал тоже самое через объектную модель ДокументDOM. Ваш вариант выглядит более простым.
9. Serg (serg_infostart) 11.11.16 18:57
(7) Yashazz, приветствую.
В принципе не имеет значения, "сколько"-угольник изначальная фигура, т.к. точек для нее можно добавить очень много (много больше шести).

Про JSON... тут я показал, как оно внутри строится... Но чтобы привести к стандартной 1С-овской модели работы с объектами, конечно, нужно всю эту штуку еще раз упаковать.
Сделать Структуру с переводом реквизитов на русский язык, аккуратно разложить по смысловым объектам, возможно, запихнуть это в обработку, чтобы в одном месте лежало. Вот пока руки никак не дойдут, но надеюсь представить...
ИМХО, должно быть что-то вроде: "СоздатьОбъект() - схема", "ДобавитьЭлемент(Тип)", "Вывести()", "СоединитьЭлементы()",... плюс нетривиальная штука - рисовать соединения, чтобы они сразу штатно отображались (штатно, это так, как если мы покажем как есть, сдвинем элемент туда-сюда руками, линия перерисуется, обходя другие объекты).

Совершенству нет предела...
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа