Введение
В предыдущей статье мы рассмотрели с Вами вопрос интеграции фреймворка «Webix» в «Electron» и создание на основе этих фреймворков простого GUI приложения. Цель данной статьи является дальнейшую развитие вопроса интеграции в GUI интерфейс, построенный с помощью «Electron» и «Webix» других «JavaScript» фреймворков. Все элементы GUI реализованные в «Webix» характеризуются параметром «view». В зависимости от значения этого параметра будет отображаться тот или иной GUI элемент. Количество типов элементов «view», которые позволяет создавать «Webix» перекрывают порядка 90% задач при реализации интерфейса. Оставшиеся 10% это как раз тот случай, когда необходимо осуществить интеграцию либо ранее написанного кода либо фреймворка (библиотеки), которые в явном виде не поддерживаются в «Webix». Для осуществления интеграции со сторонними фреймворками(библиотеки) создадим вместе с Вами свой «view» элемент «Webix».
Постановка задачи
Создать GUI приложение «Electron+Webix», которое будет строить график функции вида «y=a*sin(b)+c» с возможностью изменения параметров функции «a, b и c» с динамической перерисовкой графика.
Небольшая оговорка
Сразу оговорюсь о том, что в «Webix» есть стандартные очень мощные средства для работы с графиками. Моей целью является не дополнить эти средства, а показать, как можно интегрировать в связку «Electron+Webix» дополнительные фреймворки или библиотеки. Просто так уж сложилось что у меня был готовый код этого графика с использованием «D3.js» и я решил его интегрировать в приложение.
Инструменты
- Visual Studio Code – редактор code.visualstudio.com.
- Node.JS – скачать вот отсюда nodejs.org/en
- Electron – установить «npm install --save-dev electron».
- Webix – скачать вот отсюда ru.webix.com.
- JQuery – установить «npm jquery»
- D3.js – скачать вот отсюда d3js.org
- Исходный код (https://github.com/kapakym/electron_webix) приложения, написанного в предыдущей статье.
Подготовка рабочего места
1. Так как я человек ленивый, то второй раз писать весь код с начала не хочу. Поэтому берем код из предыдущей статьи и заливаем его в нужную нам папку. Тут варианта два. Первый это просто скачать архив с сайта (рис. 1) либо его клонировать если у Вас установлен клиент «Git» (рис. 2).

Рис. 1 – Загрузка исходного кода с сайта

Рис. 2 – Клонирование репозитория
2. Что бы в дальнейшем не путаться переименуем папку (это необязательно), например вот так «electron_webix_2» и откроем ее в Visual Studio Code (рис. 3).

Рис. 3 – Открываем проект
3. Установим необходимые элементы. Первое что, нужно сделать это скачать по ссылке, приведенной в инструментах выше «Node.JS» и установить его. Без «Node.JS» у Вас ничего дальше не получиться. Далее открываем консоль «Ctr+`» в Visual Studio Code и вводим поочередно следующие команды (рис. 4):
- «npm install --save-dev electron»;
- «npm install jquery».

Рис. 4 – Установка Node.JS и JQuery
4. Далее нам с Вами еще понадобиться фреймворк «D3.js», его также качаем по ссылке из инструментов (файл «d3.zip»). Из загруженного архива «d3.zip» достаем файл «d3.js» и кладем его в папку “libs” (рис. 5).

Рис. 5 – Добавление библиотеки D3.js
На этом рабочее место мы с Вами подготовили.
Верстаем интерфейс
Теперь, когда рабочее место подготовлено перейдем непосредственно к созданию нашего приложения. Оно будет состоять из следующих «view» элементов:
- — toolbar, стандартный элемент «Webix». Данный элемент будет размещаться в верхней части окна и содержать кнопки управления окном и название приложения. Этот элемент интерфейса у нас с Вами уже был создан в материалах предыдущей стать и тут мы менять ничего не будем;
- — myview, который нам с Вами предстоит как раз создать, так сказать, кастомный “view”. Он будет представлять из себя область, в которой будет строиться график нашей функции с помощью фреймворка “D3.js”. Данную область кроме всего прочего научим подстраиваться под размеры окна нашего приложения. Разместим его в средней части окна приложения;
- — slider, стандартный элемент “Webix”. Данный элемент позволяет изменять свое значение с помощью так называемого «ползунка». Таких элементов создадим 3 шт., каждый будет отвечать за изменение определенного параметра нашей функции «a, b или c». Разместим их в нижней части окна приложения.
Приступим. Откроем файл “renderer.js” и отредактируем его т.е. уберем ненужные нам элементы из предыдущей статьи и заменим их элементами описанными выше.
Файл “renderer.js” было вот так:
// Подключаем модуль электрона const { remote } = require('electron') // Получаем указатель на окно браузера где будет производиться отображение нашего интерфейса let WIN = remote.getCurrentWindow() // Подключаем фреймворк webix const webix = require('./libs/webix.min.js') //Подключаем фреймворк JQuery $ = require('jquery') // Функция в которую в качестве параметра как раз и будет передаваться наша с Вами верстка webix.ui( { "id": 1587908357897, "rows": [ { "css": "webix_dark", "view": "toolbar", "height": 0, "cols": [ { "view": "label", "label": "Elcetron +Webix its cool!", css:"head_win" }, { "label": "-", "view": "button", "height": 38, "width": 40, id:"min-bt" }, { "label": "+", "view": "button", "height": 38, "width": 40, id:"max-bt" }, { "label": "x", "view": "button", "height": 38, "width": 40, id:"close-bt" } ] }, { "width": 0, "height": 0, "cols": [ { "url": "demo->5ea58f0e73f4cf00126e3769", "view": "sidebar", "width": 177 }, { "width": 0, "height": 0, "rows": [ { "template": "Hello WORLD! ", "view": "template" }, { "url": "demo->5ea58f0e73f4cf00126e376d", "type": "bar", "xAxis": "#value#", "yAxis": {}, "view": "chart" } ] } ] } ] } ) // Закрытие окна $$("close-bt").attachEvent("onItemClick", () => { const window = remote.getCurrentWindow(); window.close(); }) // Свернуть окно $$("min-bt").attachEvent("onItemClick", () => { const window = remote.getCurrentWindow(); window.minimize(); }) // Распахнуть окно $$("max-bt").attachEvent("onItemClick", () => { const window = remote.getCurrentWindow(); if (!window.isMaximized()) { window.maximize(); } else { window.unmaximize(); } })
Станет вот так:
// Подключаем модуль электрона const { remote } = require('electron') // Получаем указатель на окно браузера где будет производиться отображение нашего интерфейса let WIN = remote.getCurrentWindow() // Подключаем фреймворк webix const webix = require('./libs/webix.min.js') //Подключаем фреймворк JQuery $ = require('jquery') // Функция в которую в качестве параметра как раз и будет передаваться наша с Вами верст��а webix.ui( { "id": 1587908357897, "rows": [ { // view : toolbar - 1 элемент нашего интерфейса "css": "webix_dark", "view": "toolbar", "height": 0, "cols": [ // Название окна { "view": "label", "label": "Electron + Webix + D3.js", css:"head_win" }, // Кнопка "свернуть окно" { "label": "-", "view": "button", "height": 38, "width": 40, id:"min-bt" }, // Кнопка "развернуть окно" { "label": "+", "view": "button", "height": 38, "width": 40, id:"max-bt" }, // Закрыть приложение { "label": "x", "view": "button", "height": 38, "width": 40, id:"close-bt" } ] }, // view : myview - второй элемент нашего интерфейса, в котором мы будем рисовать график. { "view": "myview", id: "d3_chart" }, { "cols": [ // view : slider - 3 элемент. Будет менять величину амплитуды sin { "label": "Amplitude", "title": "#value#", "value": 50, "view": "slider", id: "slider_amplitude", }, // view : slider - 4 элемент. Будет менять величину смещения графика sin относительно оси Y { "label": "Bias", "title": "#value#", "value": 0, "view": "slider", "height": 38, id: "slider_scope", min:-50, max:50, step:1, }, // view : slider - 5 элемент. Будет менять величину частоты sin { "label": "Frequency", "title": "#value#", "value": 0.005, "view": "slider", "height": 38, id: "slider_freq", min:0, max:0.1, step:0.001, } ] } ] } ) // Закрытие окна $$("close-bt").attachEvent("onItemClick", () => { const window = remote.getCurrentWindow(); window.close(); }) // Свернуть окно $$("min-bt").attachEvent("onItemClick", () => { const window = remote.getCurrentWindow(); window.minimize(); }) // Распахнуть окно $$("max-bt").attachEvent("onItemClick", () => { const window = remote.getCurrentWindow(); if (!window.isMaximized()) { window.maximize(); } else { window.unmaximize(); } })
Рассмотрим более подробно, какие параметры в себя включает «{ «view»: «myview», id: «d3_chart» }»:
— view: myview – тип «view», пока не существует мы его еще не создали;
— id: d3_chart – «Webix» id, будем его использовать для обращения к нашему элементу.
Поясню еще параметры элемента «slider» для полноты картины:
— view: slider – тип «view», слайдер;
— label: «текст» – ярлык, который будет отображаться на слайдере;
— title: «#value» – текущее отображаемое значение слайдера;
— value: «значение» – значение слайдера по умолчанию;
— min: «значение» – минимальное значение слайдера;
— max: «значение» – максимальное значение слайдера";
— step: «значение» – шаг изменения величины слайдера.
Более подробно о всех параметрах элементов «Webix» можно почитать вот тут docs.webix.com/desktop__components.html
Создадим «myview»
Теперь создадим файл «view_chart.js» в котором и определим наш новый тип «myview» и открываем его в «VSC».

Рис. 6 – Приступаем к редактированию view_chart.js
Добавим в него следующий код:
webix.protoUI({ name: "myview", $init: function () { }, redraw: function (a, b, c) { }, }, webix.ui.view);
Функция «webix.protoUI» предназначена для создания кастомных элементов «Webix» базирующихся на стандартных элементах. Рассмотрим параметры, которые передаются этой функции:
- name – имя создаваемого элемента;
- $init – функция, которая будет запускаться при создании элемента. Другими словами, это конструктор;
- redraw – наша с Вами функция, которая будет отвечать за перерисовку графика функции. Ей будем передавать значения параметров наших переменных “a, b и c”
- webix.ui.view – родитель нашего элемента.
Более подробно о webix.protoUI можно почитать вот тут
Итак, когда каркас нашего “view” готов напишем пару функций которые, будут отвечать за прорисовку графика sin.
Функция расчета значений функции sin будет иметь вид:
function calcsin(a, b, c) { dataset = []; for (var i = 1; i < 360; i++) { dataset.push({ "x": i, "y": a * Math.sin(b * i) + c }); }; };
Тут все очень просто, подставляем значения параметров в математическую функцию sin и результат сохраняем в массиве. Добавим ее в конец файла «view_chart.js».
Далее добавим в конец файла «view_chart.js» функцию, которая будет чертить график используя фреймворк D3.js она будет иметь следующий вид:
function drawSin() { $(document).ready(function () { width = $("#mycontent").width() - margin.left - margin.right height = $("#mycontent").height() - margin.top - margin.bottom; var xScale = d3.scaleLinear() .domain([0, 360]) .range([0, width]); var yScale = d3.scaleLinear() .domain([-100, 100]) .range([height, 0]); var line = d3.line() .x(function (d, i) { return xScale(d.x); }) .y(function (d) { return yScale(d.y); }) .curve(d3.curveMonotoneX) var svg = d3.select("#mycontent").append("svg") .attr("float", "center") .attr("class", "webix_chart") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height/2 + ")") .call(d3.axisBottom(xScale)); svg.append("g") .attr("class", "y axis") .call(d3.axisLeft(yScale)) svg.append("path") .datum(dataset) .attr("class", "line") .attr("d", line); }) }
Порядок работы приведенной функции, следующий:
— вычисляем размеры графика относительно элемента div в который он упакован «width» и «height»;
— масштабируем значения графика по «X» и «Y»;
— создаем «html» элемент «svg» в котором рисуем ось «X» ось «Y» и непосредственно сам график;
Теперь, когда функции для расчета и отрисовки нашего графика готовы, подключим пару библиотек и объявим несколько переменных в начале файла «view_chart.js»:
// Подключаем ранее скачанную библиотеку D3.js const d3 = require("./libs/d3") // Подключаем библиотеку JQuery $ = require('jquery') // Массив в котором будем хранить расчетные значения по оси X и по оси Y var dataset = []; // Переменные которые будут хранить размеры нашего графика и отступы var margin = { top: 50, right: 50, bottom: 50, left: 50 } , width = $("#mycontent").width() - margin.left - margin.right , height = $("#mycontent").height() - margin.top - margin.bottom;
В переменных «width» и «height» будет храниться ширина и высота графика с учетом отступов.
Далее вернемся обратно к функции «webix.protoUI» и пропишем его функционал:
webix.protoUI({ name: "myview", $init: function () { // Инициализация View // Добавляем блок div с id=mycontent. В этом блоке будем рисовать график this.$view.innerHTML = "<div id='mycontent'></div>" // Вызываем функцию calcsin с начальными параметрами и заполняем массив data расчетными значениями calcsin(60, 0.05, 0) // Вызываем функцию drawSin и чертим график по данным из массива data drawSin() }, redraw: function (a, b, c) { // Функция для перерисовки графика // Удаляем старый график $("#mycontent").html("") // Выполняем очередной расчет и заполняем массив data calcsin(a, b, c) // Чертим график drawSin() }, }, webix.ui.view);
Наполнили содержимым две функции. Первая «init» выполняется только один раз при создании «view». Вторая будет вызваться, когда нам нужно будет перерисовать график.
Теперь полное содержимое файла «view_chart.js» будет выглядеть вот так:
// Подключаем ранее скачанную библиотеку D3.js const d3 = require("./libs/d3") // Подключаем библиотеку JQuery $ = require('jquery') // Массив в котором будем хранить расчетные значения по оси X и по оси Y var dataset = []; // Переменные которые будут хранить размеры нашего графика и отступы var margin = { top: 50, right: 50, bottom: 50, left: 50 } , width = $("#mycontent").width() - margin.left - margin.right , height = $("#mycontent").height() - margin.top - margin.bottom; webix.protoUI({ name: "myview", $init: function () { // Инициализация View // Добавляем блок div с id=mycontent. В этом блоке будем рисовать график this.$view.innerHTML = "<div id='mycontent'></div>" // Вызываем функцию calcsin с начальными параметрами и заполняем массив data расчетными значениями calcsin(60, 0.05, 0) // Вызываем функцию drawSin и чертим график по данным из массива data drawSin() }, redraw: function (a, b, c) { // Функция для перерисовки графика // Удаляем старый график $("#mycontent").html("") // Выполняем очередной расчет и заполняем массив data calcsin(a, b, c) // Чертим график drawSin() }, }, webix.ui.view); function drawSin() { $(document).ready(function () { width = $("#mycontent").width() - margin.left - margin.right height = $("#mycontent").height() - margin.top - margin.bottom; var xScale = d3.scaleLinear() .domain([0, 360]) .range([0, width]); var yScale = d3.scaleLinear() .domain([-100, 100]) .range([height, 0]); var line = d3.line() .x(function (d, i) { return xScale(d.x); }) .y(function (d) { return yScale(d.y); }) .curve(d3.curveMonotoneX) var svg = d3.select("#mycontent").append("svg") .attr("float", "center") .attr("class", "webix_chart") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height/2 + ")") .call(d3.axisBottom(xScale)); svg.append("g") .attr("class", "y axis") .call(d3.axisLeft(yScale)) svg.append("path") .datum(dataset) .attr("class", "line") .attr("d", line); }) } function calcsin(a, b, c) { dataset = []; for (var i = 1; i < 360; i++) { dataset.push({ "x": i, "y": a * Math.sin(b * i) + c }); }; };
Далее нужно подключить содержимое файла «view_chart.js» в файле «renderer.js». Для чего нужно добавить в начало файла «renderer.js» строчку «require("./view_chart.js")» как показано ниже:
// Подключаем модуль электрона const { remote } = require('electron') // Получаем указатель на окно браузера где будет производиться отображение нашего интерфейса let WIN = remote.getCurrentWindow() // Подключаем фреймворк webix const webix = require('./libs/webix.min.js') //Подключаем фреймворк JQuery $ = require('jquery') // Подключаем view_chart.js require("./view_chart.js")
Теперь необходимо добавить пару стилей в файл «styles.css», открываем его и добавляем стиль «svg path» и «#mycontent» как показано ниже:
.head_win { -webkit-app-region: drag; } /* Стиль графика */ svg path{ /* Цвет обводки */ stroke: #666; /* Без заливки */ fill: none; /* Толщина линии обводки */ stroke-width: 1; } /* Стиль блока mycontent, в котором чертим график */ #mycontent { /* Выравнивание текста по середине */ text-align: center; /* Без скроллбаров */ overflow: none; /* Бордюр */ border: 1px solid black; /* Цвет заливки фона блока */ background-color: antiquewhite; /* Высота блока */ height: calc(100% - 2px); }
После того, когда закончили со стилями, можно попробовать запустить наше приложение и посмотреть, что у нас получилось. Итак, жмем “F5” и после на экране должно появиться окошко как показано на рисунке 7.

Рис. 7 – Окно нашего приложения
Что мы видим на нем? Верхняя часть — это стандартный тулбар «Webix». Средняя часть — график, созданный нами в «myview». Нижняя часть — три слайдера, которые мы должны научить менять значения параметров нашей функции.
Заставим слайдеры работать
Теперь перейдем к шагу, в котором будем перерисовывать график в средней части нашего приложения в зависимости от значений слайдеров «Amplitude», «Bias» и «Frequency». Для чего добавим к слайдерам обработчик события «onChange» как показано ниже:
// view : slider - 3 элемент. Будет менять величину амплитуды sin { "label": "Amplitude", "title": "#value#", "value": 50, "view": "slider", id: "slider_amplitude", on: { onChange: function () { $$("d3_chart").redraw($$("slider_amplitude").getValue(), $$("slider_freq").getValue(), $$("slider_scope").getValue()); } } }, // view : slider - 4 элемент. Будет менять величину смещения графика sin относительно оси Y { "label": "Bias", "title": "#value#", "value": 0, "view": "slider", "height": 38, id: "slider_scope", min:-50, max:50, step:1, on: { onChange: function () { $$("d3_chart").redraw($$("slider_amplitude").getValue(), $$("slider_freq").getValue(), $$("slider_scope").getValue()); } } }, // view : slider - 5 элемент. Будет менять величину частоты sin { "label": "Frequency", "title": "#value#", "value": 0.005, "view": "slider", "height": 38, id: "slider_freq", min:0, max:0.1, step:0.001, on: { onChange: function () { $$("d3_chart").redraw($$("slider_amplitude").getValue(), $$("slider_freq").getValue(), $$("slider_scope").getValue()); } } }
В данном обработчике будем обращаться к нашему элементу по его «id» и вызывать созданную ранее нами функцию «redraw» для перерисовки графика передавая ей текущие значения слайдеров. Жмем “F5” запускаем наше приложение и пробуем изменить значения (рис. 8).

Рис. 8 – Проверка функционала слайдеров
Тянем график вслед за окном
И напоследок добавим функцию, которая будет адаптировать наш график под размеры окна, когда оно будет изменяться. Для чего открываем в редакторе файл «view_chart.js» и добавляем в его конец обработчик, который отлавливает изменения окна как показано ниже:
window.onresize = function ( e ) { $("#mycontent").html("") drawSin() }
Данная функция срабатывает при изменении размера окна. Ее функционал сводиться к очистки содержимого блока «mycontent» и перерисовки графика (вызов функции «drawSin()»). Каждый разы при вызове функция «drawSin()» переменным «width» и «height» присваиваться текущая информация о размерах (высоте и ширине) блока «mycontent» после чего график будет изменять масштаб под размеры блока.
Заключение
На этом все. Надеюсь, данная информация будет Вам полезна. Спасибо! Исходники можно скачать вот тут .
