1.5.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.

  1. Download the Sales application and add the charts component to it as described in Setting up the Application Project section.

  2. Create a new screen in Studio. Call it orders-history, as it will display the history dashboard for new order entries.

  3. Add the serialChart component to the screen layout. To use the incremental data update feature, we have to create the collectionDatasource 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 the amount 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>
  4. 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.

    chart incremental update
    Figure 3. Creating a timer
  5. 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 new Order instance with random amount value each time the timer event is fired. The new instance is added to the datasource using the includeItem() method.

    Initialize the chart in the init() method using the same logic for creating a random Order 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.

    chart incremental update 2
    Figure 4. The data is automatically updated each 2 seconds
  6. Create a Queue of orders. Each time the timer event is fired, the generated item is added to the top of the itemsQueue. 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 one remove operation for the amount value. Thus, we do not re-send all the data.

chart incremental update 3
Figure 5. The chart shows only 10 records at a time