@Jan Kappen: This is a question that frequently comes up. Let me try to explain our reasoning for making this change. One of our goals with matlab.graphics.chartcontainer.ChartContainer was to make it easy for users to write charts that behave like built-in charts and work in the graphics ecosystem like other charts.
One aspect of our built-in charts is that (for improved performace and to support saving to FIG-files), you can change the data in your property after the object/chart has been created. You can also query the data back from the chart (most often to modify it, such as appending new data).
In the class you defined above, the Data property is a user-settable property, but the chart, as written above, won't work if the user changes the timetable stored in Data to have a different number of variables after creating the chart:
t = datetime()+hours(1:10)';
t1 = array2timetable(rand(10),'RowTimes', t);
t2 = array2timetable(rand(10,5),'RowTimes', t);
b = BrokenChartClass(t1);
With the code above, you would set NumSubPlots to 10 within the constructor then you would set both DataAxes and DataLines within setup. This is only happening once when the chart is first created, and doesn't update when the Data changes. That means that within update you would get an index-out-of-bounds error because your table is narrower than 10. In addition, the number of axes and lines created will be incorrect. If you were to set Data to a table with 20 variables, your code as written would ignore all but the first 10 variables.
If a property is designed to be set by a user, it should be changable by the user at any time, including after the chart was first created.
There are two ways to allow the user to change the Data property without breaking the chart:
Option 1 - Not recommended (because it is more complicated): You can add a set.Data method. That method will be called any time the Data property is set. Within that method, you can update the values of NumSubPlots and DataAxes and DataLines based on the new value. This is not the recommended pattern for two reasons:
- It causes complicated order-dependency issues between properties, which makes the logic in the code harder to follow and also often breaks saving and loading the chart. If you want to use this approach, you can resolve most of the save issues by making the NumSubPlots and DataAxes and DataLines transient properties.
- It prevents the graphics system from automatically optimizing the performance of your chart. set.Data will run any time the user sets the Data property, but update will only run when MATLAB goes idle or you call drawnow. Imagine a user who is iteratively setting values in the Data property (see the code below). In that example, set.Data will run 10 times, but update will only run once. For that reason, we recommend using update instead (option 2).
t = datetime()+hours(1:10)';
t1 = array2timetable(rand(10),'RowTimes', t);
b = BrokenChartClass(t1);
b.Data{1,ii} = b.Data{1,ii}+1;
Option 2 - Recommended: With update, check whether the number of variables in the table matches the current value of NumSubPlots. If they are different, update NumSubPlots and DataAxes and DataLines accordingly. This is the recommended pattern.
Note that neither of those approaches relied on an IsInitialized state on the chart, because that just recreates the same problem as-if you were to have run the code within setup.
What we found when people were writing charts is that in their first draft of the chart they would do a bunch of setup operations within setup, leveraging the user provided name/value pairs. Then they would discover that the chart doesn't support changing the data after the chart is created (and that saving and loading the chart is broken) and add duplicate code to the update method to do the same operation (or typically a subset of the operation) again. In reality, the code never should have been in setup in the first place.
The general rule of thumb I apply is that:
- setup should only be used to do setup that will never change in the chart. Because users of the chart can change property values, setup should never rely on user-settable property values (or property values that could be changed indirectly by users, such as NumSubplots).
- update should be used for anything that could change, such as if the user sets a property value.
By setting the name/value pairs after calling setup, we are encouraging people who are writing charts to follow that rule of thumb by not allowing access to the name/value pairs until after setup has finished.
We actually include an example chart in our documentation that shows the recommended pattern for adjusting the number of lines to update based on the data: Optimized Chart Class for Displaying Variable Number of Lines. I recommend taking a look at that example, as it can be easily adapted to create one axes per line as well. Our goal with matlab.graphics.chartcontainer.ChartContainer was to allow all MATLAB users to easily create their own charts that behave like first-class citizens, but it is worth mentioning for power users that:
- update is a "special" method. The graphics system will automatically call update once per graphics update and it will only call update if something has indicated to the system that your chart is out-of-date (such as a property value was set or the figure changed sizes).
- setup is much less "special". It is called automatically from the ChartContainer constructor. If your chart does not have the ConstructOnLoad attribute, it is also called once when your chart is loaded from a FIG-file. Otherwise, there is nothing stopping a power-user from leaving setup empty and putting all the setup code within their own custom constructor, in which case they would have access to anything the user specified.