1.7.5. Creating Chart with Incremental Data Update
This chart will retrieve data from a datasource, and this data will be updated automatically. When new data is added to the datasource, the chart is not refreshed completely: data points are added on the fly each 2 seconds. This approach will be useful, for example, for creating dynamically updated dashboards.
In this example we will show the dynamic of new orders amounts based on the Order
entity from the Sales sample application.
-
Download the Sales application and add the charts component to it as described in Setting up the Application Project section.
-
Create a new screen in Studio. Call it orders-history, as it will display the history dashboard for new order entries.
-
Add the
serialChart
component to the screen layout. To use the incremental data update feature, we have to create thecollectionDatasource
and bind the chart to it. We will not load the data from the database in this example, instead, the sample data will be generated on the fly, so you don’t need to create a query.Set the
date
property for the category axis and theamount
property for the value axis.<?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>
-
We will update the chart on the fly using
timer
- a special UI component that will send HTTP-requests to the server side.Open the Properties tab of the screen designer and add the timer by clicking on the Timers button. Set the timer id. Let’s say the data should be updated each 2 seconds, so set the delay to 2000 milliseconds.
In the onTimer field we define the name of Java method -
updateChart
. This method will be invoked each time the timer fires an event. Generate this method in the controller by clicking the >> button and click Apply to save it.Figure 15. Creating a timer -
Switch to the IDE. Before starting to work on the timer logic, inject necessary dependencies:
timeSource
,metadata
, and the datasource instance. We will generate a newOrder
instance with random amount value each time the timer event is fired. The new instance is added to the datasource using theincludeItem()
method.Initialize the chart in the
init()
method using the same logic for creating a randomOrder
instance.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); } }
At this stage the chart is already functional, but the datasource size will increase rapidly, so we need to limit the number of items to be displayed.
Figure 16. The data is automatically updated each 2 seconds -
Create a
Queue
of orders. Each time the timer event is fired, the generated item is added to the top of theitemsQueue
. When the queue size exceeds 10 items, the oldest item is excluded.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); } }
- Result
-
All the data is sent to the browser incrementally. If you open Chrome developer console on the Network tab, you will see that each 2 seconds our web page sends an HTTP request to the backend, and in response to that the backend sends very small JSON message. The JSON contains one
add
and oneremove
operation for the amount value. Thus, we do not re-send all the data.