Пишем редактор мнемосхем для SCADA-системы на Fabric.js. Часть 2-я
Остановились мы на том, что при загрузке SVG из файла разгруппированным у элементов со свойством transform=«translate (X Y)» указатели для изменения размера оказываются в левом верхнем углу, тогда как само изображение в координатах X Y. Обойти этот баг оказалось не так просто. Опытным путем было замечено, что если установить координаты в 0 0, запомнив перед этим их transformMatrix, а затем восстановить их до X Y указатели для изменения размера совпадут с изображением. И вот для этого и была написана следующая функция:
function After_load() {
var kol = 0;
canvas.forEachObject(function(obj){
var transformMatrix1 = [1,0,0,1,0,0];
var str_x;
var str_y;
var tr_y;
var tx_sg = obj.toSVG();
if ((tx_sg.indexOf('transform="translate')>=0) && (tx_sg.indexOf('=0))
{
obj.setOriginX('left');
obj.set('lockScalingX','false');
obj.set('lockScalingY','false');
}
var transformMatrix2 = obj.get('transformMatrix');
var strokeWidth1 = obj.getStrokeWidth();
var m_x = obj.getLeft();
var m_y = obj.getTop();
var calcTransformMatrix2 = obj.calcTransformMatrix();
transformMatrix1[1] = transformMatrix2[1];
transformMatrix1[2] = transformMatrix2[2];
if (!((tx_sg.indexOf('transform="translate')>=0) && (tx_sg.indexOf('=0)))
{
obj.setTransformMatrix(transformMatrix1);
}
obj.setTop(transformMatrix2[5]+m_y*transformMatrix2[3]);*/
if (tx_sg.indexOf('=0){
obj.setLeft(transformMatrix2[4]+(m_x)*transformMatrix2[0]-(strokeWidth1/2)*transformMatrix2[0]);//(m_x-0.5)*transformMatrix2[0]
obj.setTop(transformMatrix2[5]+(m_y)*transformMatrix2[3]-(strokeWidth1/2)*transformMatrix2[3]); //(m_y-0.5)*transformMatrix2[3]
}
else
if ((tx_sg.indexOf('=0) || (tx_sg.indexOf('=0) || (tx_sg.indexOf('=0) && (tx_sg.indexOf('=0))//if (tx_sg.indexOf('");
var poz1 = tx_sg.indexOf("0) && (poz1>0))
{
var poz3 = tx_sg.indexOf(">",poz1+1);
if (poz3>0){
var str = tx_sg.substring(poz3+1,poz2);
}
}// if ((poz2>0) && (poz1>0))
tx_sg = obj.toSVG();
poz2 = tx_sg.indexOf(" ");
poz1 = tx_sg.indexOf("0) && (poz1>0))
{
var poz3 = tx_sg.indexOf('x="',poz1+1);
if (poz3>0){
var poz4 = tx_sg.indexOf('"',poz3+4);
str_x = tx_sg.substring(poz3+3,poz4);
}
poz3 = tx_sg.indexOf('y="',poz1+1);
if (poz3>0){
var poz4 = tx_sg.indexOf('"',poz3+4);
str_y = tx_sg.substring(poz3+3,poz4);
}
}// if ((poz2>0) && (poz1>0))
poz1 = tx_sg.indexOf('transform="translate(');
if (poz1>0)
{
poz2 = tx_sg.indexOf(" ",poz1+21);
var poz3 = tx_sg.indexOf(')',poz2+1);
tr_y = tx_sg.substring(poz2,poz3);
}
m_x = obj.getLeft();
m_y = obj.getTop();
transformMatrix2 = obj.get('transformMatrix');
obj.setTransformMatrix(transformMatrix1);
obj.setTop(transformMatrix2[5]-parseFloat(tr_y)-parseFloat(str_y)-strokeWidth1*0.58);
obj.setLeft(transformMatrix2[4] + parseFloat(str_x) - (strokeWidth1/2) );
}
else
{
obj.setLeft(transformMatrix2[4]+(m_x)*transformMatrix2[0]-(strokeWidth1/2)*transformMatrix2[0]);//(m_x-0.5)*transformMatrix2[0]
obj.setTop(transformMatrix2[5]+(m_y)*transformMatrix2[3]-(strokeWidth1/2)*transformMatrix2[3]); //(m_y-0.5)*transformMatrix2[3]
}
obj.setScaleX(transformMatrix2[0]);
obj.setScaleY(transformMatrix2[3]);
obj.set('transparentCorners','true');
obj.setCoords();
kol = kol + 1;
});
if (kol === 0)
{
$.post("After_load", kol);
}
else
{
Is_After_load = true;
}
};
После загрузки документа canvas не всегда содержал элементы. Они появлялись на нем несколько позже. Пришлось делать вот так:
setTimeout(function() {
if (!Is_After_load){
After_load();
}
}, 5000);
Теперь от запуска в браузере перейдем к собственному редактору. Html-страницы будем отдавать через компонент idhttpserver. Для отображения будем использовать Chromium, в точнее компонент DcefBrowser. Idhttpserver открывает порт 15500, Chromium открывает страницу 127.0.0.1:15500/. Можно, например, открыть 127.0.0.1:15500/ любым другим браузером и вести редактирование с помощью него. Подсовывая свой Chromium пользователю, а не заставляя его использовать свой браузер, мы даём ему браузер в котором гарантированно будут работать java-скрипты так как нам нужно.
Index.html через idhttpserver мы отдаём немного модифицированным:
- Canvas становится таких размеров, какие задал пользователь в настройках.
- Загружается тот SVG-файл, который сейчас редактирует пользователь.
- Подгружаются пользовательские SVG-файлы из библиотеки изображений.
- Заполняется список переменных, получаемых от ОРС-серверов.
С помощью методов
$.post
и toSVG
можно сделать следующие вещи: Сохранение схемы в SVG:
function Post_sheme() {
$.post("save.php", canvas.toSVG());
};
В http-сервере ловим post-запрос и сохраняем в SVG
ARequestInfo.UnparsedParams
.Добавление SVG-изображения в библиотеку. Выделяем несколько элементов, нажимаем на кнопку «Добавить выделенные в библиотеку», выполняется
$.post("new.php?"+$("input[name=namenewsvg]").val(), Buff_clipb);
Где
$("input[name=namenewsvg]").val()
— это имя SVG-изображения.Копирование в буфер обмена операционной системы. Выделяем несколько элементов, нажимаем на кнопку «копировать», выполняется следующий скрипт:
var tx = canvas.getActiveGroup().toSVG();
tx = '';
$.post("copy.php", tx);
В http-сервере ловим post-запрос и копируем в буфер обмена
Clipboard.AsText := ARequestInfo.UnparsedParams;
Теперь можно открыть другую схему и вставить в неё SVG. Для этого перейти на вкладку «Загрузить SVG» — вставить в текстовое поле содержимое буфера обмена, нажать на кнопку «Загрузить».
Теперь перейдем к установлению связей между переменной, получающей своё значение от ОРС-сервера и SVG-изображением.
Для привязки аналоговых переменных используется элемент текст. Надо создать текст на вкладке «текст». Выделить его. На вкладке «Привязка» выбрать имя переменной. При этом текст станет {{val.Имя_переменной}}
. Т.е. привязка текста осуществляется через содержимое text
.
Для отображения дискретной переменной надо использовать 2 элемента. Один будет использоваться для отображения включённого состояния, другой для отключенного. Т.е. когда элемент включен, будет видим элемент, отображающий включенное состояние. Элемент, отображающий отключенное состояние будет невидим.
Чтобы привязать элемент, отображающий включенное состояние надо выделить его, в выпадающем списке выберать Имя_переменной_on
. Чтобы привязать элемент, отображающий отключенное состояние нужно выделить его, в выпадающем списке выбрать Имя_переменной_off
. Стрелками на клавиатуре переместить элементы так, чтобы один оказался под другим.
function setIDObj() {
var activeObject = canvas.getActiveObject();
if (activeObject) {
activeObject.set({
id : $("input[name=nameobj]").val()
});
var tx_sg = activeObject.toSVG();
var poz2 = tx_sg.indexOf("");
var poz1 = tx_sg.indexOf("0) && (poz1>0))
{
var ttx = $("input[name=nameobj]").val();
if (ttx.indexOf("{{")<=0)
{
ttx = "{{val."+ttx+"}}";
}
activeObject.set({
text: ttx
});
canvas.renderAll();
}
}
};
Теперь про рисование. Многие графические редакторы позволяют сохранять в формате SVG. Пока я использую следующую схему.
Уже имеющуюся схему открываю в Visio, копирую её в Inkscape, сохраняю в SVG, открываю в блокноте, копирую, вставляю в редакторе через вкладку «Загрузить SVG» — текстовое поле — кнопка «загрузить».
Из Visio в Inkscape приходится копировать, т.к. Visio создает SVG, который FabricJS не может нормально рендерить.
Посмотреть редактор online без возможности сохранения можно здесь.
Скачать прототип SCADA-системы с редактором здесь.
А в следующей статье мы будем оживлять нарисованную схему.