1.7.5. Создание диаграммы с инкрементальным обновлением данных
В следующем примере мы рассмотрим диаграмму, которая получает данные из источника данных и обновляет их автоматически. Когда в источник добавляются новые данные, диаграмма не обновляется полностью: новые точки добавляются в график на лету каждые 2 секунды. Этот принцип удобно использовать при создании динамически обновляемых виджетов.
Пример основан на тестовом приложении Sales, к которому мы добавим диаграмму для отображения динамики новых заказов, то есть создания новых экземпляров сущности Order
.
-
Скачайте приложение Sales и добавьте к нему компонент charts, следуя инструкции из раздела Настройка проекта приложения.
-
Создайте в Studio новый пустой экран. Назовите его orders-history, так как в нём будет отображаться история создания новых заказов.
-
Добавьте к экрану компонент
serialChart
. Чтобы реализовать инкрементальное обновление данных, необходимо создать источник данных с типомcollectionDatasource
и привязать к нему диаграмму. В этом примере мы не будем загружать данные из базы, вместо этого мы будем создавать тестовые данные на лету, поэтому запрос в источнике данных создавать не нужно.Для оси категорий укажите атрибут
date
, для оси значений - атрибутamount
.<?xml version="1.0" encoding="UTF-8" standalone="no"?> <window xmlns="http://schemas.haulmont.com/cuba/window.xsd" caption="msg://caption" class="com.company.sales.web.screens.OrdersHistory" messagesPack="com.company.sales.web.screens" xmlns:chart="http://schemas.haulmont.com/charts/charts.xsd"> <dsContext> <collectionDatasource id="ordersDs" class="com.company.sales.entity.Order" allowCommit="false" refreshMode="NEVER"/> </dsContext> <dialogMode height="600" width="800"/> <layout expand="orderHistoryChart" spacing="true"> <chart:serialChart id="orderHistoryChart" categoryField="date" datasource="ordersDs" width="100%"> <chart:graphs> <chart:graph valueField="amount"/> </chart:graphs> </chart:serialChart> </layout> </window>
-
Для обновления данных на лету используйте
timer
- специальный UI-компонент, который будет отправлять HTTP-запросы на сторону сервера.Отройте вкладку Properties дизайнера экрана и нажмите на кнопку Timers, чтобы добавить таймер на экран. Заполните поле id. Допустим, мы хотим, чтобы данные обновлялись каждые 2 секунды, в этом случае в поле delay укажем значение 2000 миллисекунд.
В поле onTimer укажем имя метода Java -
updateChart
. Этот метод будет вызываться каждый раз при срабатывании события таймера. Сгенерируйте метод в контроллере экрана, нажав на кнопку >>, после чего сохраните его, нажав Apply.Рисунок 15. Создание таймера -
Откройте контроллер экрана в IDE. Для разработки логики работы таймера нам понадобится инжектировать следующие зависимости:
timeSource
,metadata
и экземпляр источника данных. Мы будем генерировать новый экземпляр сущностиOrder
с произвольным значениемamount
при каждом событии срабатывания таймера. Новый экземпляр добавляется к источнику данных с помощью методаincludeItem()
.Инициализируйте диаграмму в методе
init()
, создав таким же образом исходный экземпляр сущностиOrder
.public class OrdersHistory extends AbstractWindow { @Inject private Metadata metadata; @Inject private TimeSource timeSource; @Inject private CollectionDatasource<Order, UUID> ordersDs; private Random random = new Random(42); @Override public void init(Map<String, Object> params) { super.init(params); Order initialValue = metadata.create(Order.class); initialValue.setAmount(new BigDecimal(random.nextInt(1000) + 100)); initialValue.setDate(timeSource.currentTimestamp()); ordersDs.includeItem(initialValue); } public void updateChart(Timer source) { Order orderHistory = metadata.create(Order.class); orderHistory.setAmount(new BigDecimal(random.nextInt(1000) + 100)); orderHistory.setDate(timeSource.currentTimestamp());; ordersDs.includeItem(orderHistory); } }
На этом этапе диаграмма полностью функциональна, но размер источника данных после запуска таймера будет стремительно расти, поэтому реализуем ограничение количества отображаемых заказов.
Рисунок 16. Данные автоматически обновляются каждые 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());; ordersDs.includeItem(orderHistory); itemsQueue.add(orderHistory); if (itemsQueue.size() > 10) { Order item = itemsQueue.poll(); ordersDs.excludeItem(item); } }
- Результат
-
Данные поступают в браузер инкрементально. Если открыть консоль разработчика в Chrome, на вкладке Network будет видно, что каждые 2 секунды страница отправляет HTTP-запрос на backend и в ответе получает очень маленький JSON, содержащий только операции
add
иremove
со значениями поляamount
. Это позволяет избежать повторной пересылки всех данных диаграммы.