The design ideas in the previous section suggest that we should have gone with more of a MVC approach. This section includes some early notes on this approach. This section is a more concrete proposal for the same idea: the section called “Data-Driven Models”.
A Plot (the view) only needs to replot() if its DataSet (the model) changes. So a plot plugs into a DataSet by listening for its 'DataSetChanged' event and replotting on any changes. When the user chooses a new domain, the DataSet domain is still changed through updateDataSets, but the replot happens through the change listener callback when the DataSet domain and its values have actually changed, rather than forcing the plot to always call replot() after updateDataSets() assuming the DataSet has changed. Then the DataSource would be able to trigger plot updates if any data in the domain change (eg, inserted). This could be a very coarse capability with some kind of global DataSourceChanged signal, and maybe even the callback could list the changed datasets, but allowing changes to be notified on a per-dataset basis allows for more fine-grained control.
What about a plot which depends upon multiple datasets, and a multiple of them is about to be changed by a call to updateDataSets. We don't want to replot on each dataset change, only on the last one. Maybe the updateDataSets() call first suspends change notifications, updates all the datasets, then resumes the notifications, which cascades through all the datasets. When a dataset dependency has re-sync'ed with its datasets, it must call some method to tell the dataset that its dependent is caught up and should not get further change notifications. Like a rev number: the dataset has a rev number, and if the rev number is higher than the rev last plotted by a plot window, then a replot is triggerd.
Only the plots whose data have changed will be replotted, and the Plot does not need any logic to determine if its datasets are in the set of changed datasets or if its dataset revisions are still up to date. So every Plot registers itself as a listener for its DataSets' DataSetUpdated callbacks. If a point gets edited in a dataset, all of its views will be notified. The PlotPage, on the other hand, registers all of the DataSets on the page with the more general DataSourceUpdate notification, to which it can pass a set of interested DataSets and a DataDomain with the DataAppended mask set. When new data arrive, that will trigger the PlotPage callback, which in turn changes its domain and calls updateDataSets on all of its Plots DataSets. Once all of those DataSets get updated, the DataSource can traverse the set of DataSets calling their DataSetUpdated callbacks, but only if the DataSet revision is still outdated. For example, a DataSetUpdated callback forces a replot of a plot with multiple traces. The Plot calls the sync() call on all of its DataSets. That means any further callbacks on those DataSets will be skipped because the revisions no longer disagree.
Pseudo-code for Plot::replot():
for each dataset ds: double* times = ds->getTimes(); double* values = ds->getValues(); qwtPlot->plot (times, values); ds->sync();
When a DataSetBack gets updated with new data, it traverses its list of dependent datasets and increases the rev number (within the domain map and not within the DataSet proxy itself) for every dataset which covers the changed data. The DataSet::sync() call synchronizes the DataSet proxy's rev number with the rev number in the DataSetBack, thereby foregoing any further updates from that DataSet.
The idea here is to model as completely as possible the hierarchy of datasets in any particular view so the DataSource can intelligently and accurately identify the dependencies and determine when to trigger an update on the view, without knowing anything about the view itself.