2.6.5. Создание диаграммы с инкрементальным обновлением данных
В следующем примере мы рассмотрим диаграмму, которая получает данные из контейнера и обновляет их автоматически. Когда в контейнер добавляются новые данные, диаграмма не обновляется полностью: новые точки добавляются в график на лету каждые 2 секунды. Этот принцип удобно использовать при создании динамически обновляемых виджетов.
Пример основан на тестовом приложении Sales, к которому мы добавим диаграмму для отображения динамики новых заказов, то есть создания новых экземпляров сущности Order
.
-
Скачайте приложение Sales и добавьте к нему компонент charts, следуя инструкции из раздела Настройка проекта приложения.
-
Создайте в Studio новый пустой экран. Назовите его orders-history, так как в нём будет отображаться история создания новых заказов.
-
Добавьте к экрану компонент
serialChart
. Чтобы реализовать инкрементальное обновление данных, необходимо создать контейнер данных с типомCollectionContainer
и привязать к нему диаграмму. В этом примере мы не будем загружать данные из базы, вместо этого мы будем создавать тестовые данные на лету, поэтому загрузчик данных в контейнере создавать не нужно.Для оси категорий укажите атрибут
date
, для оси значений – атрибутamount
.<?xml version="1.0" encoding="UTF-8" standalone="no"?> <window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd" caption="msg://caption" messagesPack="com.company.sales.web" xmlns:chart="http://schemas.haulmont.com/charts/charts.xsd"> <data> <collection id="ordersDc" class="com.company.sales.entity.Order" view="_local"/> </data> <layout> <chart:serialChart id="orderHistoryChart" categoryField="date" dataContainer="ordersDc" width="100%"> <chart:graphs> <chart:graph valueField="amount"/> </chart:graphs> </chart:serialChart> </layout> </window>
-
Для обновления данных на лету используйте timer – специальный UI-компонент, который будет отправлять HTTP-запросы на сторону сервера.
-
Переключитесь на вкладку Designer.
-
В группе Non-visual components в палитре компонентов найдите Timer и перетащите его в область иерархии компонентов экрана.
-
Выделите компонент в иерархии и перейдите на вкладку Properties.
-
Задайте идентификатор таймера в поле id.
-
Допустим, мы хотим, чтобы данные обновлялись каждые 2 секунды, в этом случае в поле delay укажите значение 2000 миллисекунд.
-
В поле onTimer укажите имя метода Java –
updateChart
. Этот метод будет вызываться каждый раз при срабатывании события таймера. Сгенерируйте метод в контроллере экрана, нажав на кнопку >>. -
Поставьте флажки repeating и autostart.
Рисунок 28. Создание таймера -
-
Откройте контроллер экрана
OrdersHistory
. Для разработки логики работы таймера инжектируйте следующие зависимости:timeSource
,metadata
и контейнер данных для сущностиOrder
. Мы будем генерировать новый экземпляр сущностиOrder
с произвольным значениемamount
при каждом событии срабатывания таймера. Новый экземпляр добавляется к контейнеру данных с помощью методаgetMutableItems().add()
.Инициализируйте диаграмму в методе
onInit()
, создав таким же образом исходный экземпляр сущностиOrder
.package com.company.sales.web; import com.company.sales.entity.Order; import com.haulmont.cuba.core.global.Metadata; import com.haulmont.cuba.core.global.TimeSource; import com.haulmont.cuba.gui.components.Timer; import com.haulmont.cuba.gui.model.CollectionContainer; import com.haulmont.cuba.gui.screen.*; import javax.inject.Inject; import java.math.BigDecimal; import java.util.Random; @UiController("sales_OrdersHistory") @UiDescriptor("orders-history.xml") public class OrdersHistory extends Screen { @Inject private Metadata metadata; @Inject private TimeSource timeSource; @Inject private CollectionContainer<Order> ordersDc; private Random random = new Random(42); @Subscribe private void onInit(InitEvent event) { Order initialValue = metadata.create(Order.class); initialValue.setAmount(new BigDecimal(random.nextInt(1000) + 100)); initialValue.setDate(timeSource.currentTimestamp()); ordersDc.getMutableItems().add(initialValue); } public void updateChart(Timer source) { Order orderHistory = metadata.create(Order.class); orderHistory.setAmount(new BigDecimal(random.nextInt(1000) + 100)); orderHistory.setDate(timeSource.currentTimestamp());; ordersDc.getMutableItems().add(orderHistory); } }
На этом этапе диаграмма полностью функциональна, но размер контейнера данных после запуска таймера будет стремительно расти, поэтому реализуем ограничение количества отображаемых заказов.
Рисунок 29. Данные автоматически обновляются каждые 2 секунды -
Создайте экземпляр класса
Queue
для очереди заказов. При каждом срабатывании таймера созданный заказ будет добавлен наверх очередиitemsQueue
. Когда размер очереди превышает 10 заказов, самый старый заказ удаляется.private Queue<Order> itemsQueue = new LinkedList<>();
public void updateChart(Timer source) { Order orderHistory = metadata.create(Order.class); orderHistory.setAmount(new BigDecimal(random.nextInt(1000) + 100)); orderHistory.setDate(timeSource.currentTimestamp());; ordersDc.getMutableItems().add(orderHistory); itemsQueue.add(orderHistory); if (itemsQueue.size() > 10) { Order item = itemsQueue.poll(); ordersDc.getMutableItems().add(item); } }
- Результат
-
Данные поступают в браузер инкрементально. Если открыть консоль разработчика в Chrome, на вкладке Network будет видно, что каждые 2 секунды страница отправляет HTTP-запрос на backend и в ответе получает очень маленький JSON, содержащий только операции
add
иremove
со значениями поляamount
. Это позволяет избежать повторной пересылки всех данных диаграммы.