Real-Time Applications


Overview

The ChartObject library is well suited to handle real-time applications that continuously need to update one or multiple graphs. The DataObject library provides, for each primitive data class, a set of convenience functions to modify the data by replacing, extending or shifting the data values. The DataObject library also provides a batching mechanism, so that multiple updates for different data elements will result in only one redraw. Finally, the ChartObject supports a double-buffering mechanism which allows for smooth graph updates.

Example

In this example, we want to graph and update multiple sets of data. The horizontal direction represents the time in seconds where the samples are generated. The vertical direction represents the point amplitudes, in the application's units. The graph is initially empty. Points are first appended until we reach the number of points defined in constant MAX_POINT. After that, the data is shifted so that only the last MAX_POINT points are displayed. A complete listing of this example is available in file chart_rt.c, in directory examples.

The creation of the chart is shown below. We create a line graph and set the vertical limits of the plot between -1 and 1, which is the range of the data values. In the horizontal direction we don't set any limits so that auto-scaling is used. We set resource XmNxAutoRangeMode to XintUSE_MIN_MAX so that the chart does not round-up the starting time and ending time limits for the horizontal axis.

    chart_geometry.x1 = 0.0;
    chart_geometry.y1 = 0.0;
    chart_geometry.x2 = 100.0;
    chart_geometry.y2 = 100.0;

    chart = (Object)XtVaCreateWidget("chart",
                        (WidgetClass)xintChartObjectClass,
                        edit,
                        XmNchartTitle, "Real Time Example\nwith Data Series",
                        XmNgeometry,    &chart_geometry,
                        XmNchartType,   XintCHART_TYPE_LINE,
                        XmNfillStyle,   XintFILL_NONE,
                        NULL);

    y_limits.minimum = -1;
    y_limits.maximum = 1;
    XtVaSetValues((Widget)
                  XintChartGetComponent(chart, XintCHART_COMPONENT_PLOT),
                  XmNxAutoRangeMode, XintUSE_MIN_MAX,
                  XmNyLimits, &y_limits,
                  NULL);

For the data, we will use DataSeries objects because the data points can arrive at non constant intervals. We first create a DataGroup object since we want to display multiple curves. We also create a DataLabel object that will be used to display the time values on the horizontal axis. All the data objects are created with no data initially.

    data_group  = (Object)XtVaCreateWidget("data_group",
                                          (WidgetClass)xintDataGroupObjectClass,
                                           edit, NULL);

    /*
     * We use DataSeries to be able to position samples exactly at times
     * where it is generated. Start with no data.
     */
     for (i = 0; i < num_sets; i++)
          XtVaCreateWidget("set", (WidgetClass)xintDataSeriesObjectClass,
                           edit,
                           XmNcount,       0,
                           XmNdataType,    XintDATA_TYPE_FLOAT,
                           XmNdataGroup,   data_group,
                           NULL);

    /*
     * Add empty Data Label object to handle the time labels
     */
    XtVaCreateWidget("label", (WidgetClass)xintDataLabelObjectClass,
                     edit,
                     XmNlabelCount,  0,
                     XmNdataGroup,   data_group,
                     NULL);

    XintChartAssociateData(chart, data_group);

The update of the data is done as follows. We first update the data series using function XintDataSeriesExtend if the number of inserted points is less that MAX_POINT and XintDataSeriesShift otherwise. We use function XintDataListIterate to retrieve the ID of the data series from the data group. The update of the data label is done in a similar fashion. We only generate an annotation every 10 points so that labels don't overlap on the horizontal axis. Note that we pass the label position (time value) along with the label string to the data label update functions. Finally, the whole update sequence is surrounded by calls to XintDataBatchUpdate so that only one redraw gets generated from all the changes we have made to the data.

    XintDataBatchUpdate(data_group, True);

    for (i=0; i < nseries; i++) {

         data_series = XintDataListIterate(&data_group, 1,
                                           xintDataSeriesObjectClass, i);
         if (points_inserted < MAX_POINT)
             XintDataSeriesExtend(data_series, &float_time, &y[i], 1);
         else
             XintDataSeriesShift(data_series, &float_time, &y[i], 1);
    }

    /*
     * Upate the labels  (generate a label only every 10th value)
     */
    if (points_inserted % 10 == 0) {

        data_label = XintDataListIterate(&data_group, 1,
                                         xintDataLabelObjectClass, 0);

        sprintf(string_buffer, "%3.1f", float_time);

        str_array[0] = string_buffer;
        if (points_inserted < MAX_POINT)
            XintDataLabelExtend(data_label, str_array, &float_time, 1);
        else
            XintDataLabelShift(data_label, str_array, &float_time, 1);
    }

    XintDataBatchUpdate(data_group, False);