-
Notifications
You must be signed in to change notification settings - Fork 1
Rendering
This page discusses rendering in general, and also how it pertains to YACC.
This has lots to do with the so-called Visual Tree (or VT), which is different from the Component Tree, which is structurally represented in the XAML. These are different due to DataTemplate, ContentPresenter and the like. This topic assumes you know (a little) about this; use Search Engines to find out more!
The .NET Framework likes to call certain methods like LayoutUpdated very frequently, so internally the Chart implementation goes to great lengths to debounce this, and call additional methods only when absolutely necessary. In particular, LayoutUpdated is called with the same rectangle size very often, usually due to changes elsewhere in the Visual Tree.
Another place where non-determinism abounds is in the DataContextChanged event chains. Again, the Chart makes efforts not redistribute a new DataContext to components, unless it really did change.
There are also some subtleties tracking ObservableCollection with regard to entering the Visual Tree. Components that start out in the XAML get triggered through the collection's callbacks very early, before the VT is available. Again, the Chart makes an effort to defer these until such time is appropriate.
At some point, "series" values must become "chart" values, the "world" to "device" coordinate transformation. A mistake is made when committing to "device" coordinates for the chart elements "too early".
Why is this a mistake? Re-read the paragraph about LayoutUpdated up there! Even in the case where the layout really does require updating, every visual element requires recomputing new device coordinates for the new size. Maybe not an issue for small data sets, but there's a better way anyway.
Since WPF came out, we've had this awesome thing called a Transform. Of course, anyone who knows about Linear Algebra (e.g. Game Programming with DirectX or OpenGL) has known about this for a long time. So the "better way" is to just store the series data in its "natural" form, and adjust the Transform as required.
Now we've avoided recalculating the entire chart when it resizes. When the layout changes size, we just adjust the Transform for each component, a very fast operation. There's even the future opportunity to animate it!
Rendering a series becomes simpler. Just build up a PathGeometry or whatever in actual values from the data, and attach to a Path and voila! It's even better if the Geometry coordinates are "normalized" in (in one or both dimensions) that makes them "relocatable" without recalculation.
How does a ChartComponent get the Transform it needs, what with multiple series etc.? This is where the Axes come into play.
Rather than "push" axis updates on every value observed, the rendering pipeline "pulls" them at appropriate points.
While "observing" values entering the chart (either IDataSourceRenderer or IRequireRender), each ChartComponent internally tracks its Minimum and Maximum values (on its value axis). At strategic points, these extents are passed to the Axes (via IProvideXXXAxisExtents), which then end up with the "global" extents for all components.
Come AfterAxesFinalized and Transform time, they provide this back to each component, so they can finalize Transforms based on the axes Minimum Maximum and Range.
The process for rendering falls into these general categories:
- a full render
- a transforms render
- a component render
- an incremental render
The Canvas attached properties, e.g. Canvas.Left, are leveraged to enable implicit composition animations on chart elements, which are Path and TextBlock. The reason for this change: the Canvas control takes changes in the attached properties to Visual.Offset animations.
This is coming in 1.6.x.
This has an effect on Geometry construction and formation of Transform to "offload" some of the offset to the Canvas attached properties.
Implicit composition animations are supported starting at UWP API version 3.
There is also ability to attach show and hide animations in a similar way.
Implicit show and hide animations are supported starting at UWP API version 4.
Because of the API version issues, you may be responsible for version adaptive code, depending on your project settings in Visual Studio.
A component's access to the Visual Tree is controlled via a Layer. Rather than add visual elements ad hoc, they are managed through this proxy. The Layer provides identification and management of the component's visual elements, and hides how the Chart chooses to manage them in its own visual tree.
This executes the entire rendering pipeline.
- axes are reset (to
NaN) - layout (
IRequireLayout)- components can "claim" space on the chart's
Rect
- components can "claim" space on the chart's
- layout-complete (
IRequireLayoutComplete)- this is when the data region
Rectis determined - called after all layout is finalized
- this is when the data region
- data source rendering pipeline (DSRP)
- render (
IDataSourceRenderer)
- render (
-
DataSeriestransfer axis extents (IProvideXXXAxisExtents)- called after all DSRP completes
- non-axis render (
IRequireRender) and transfer axis extents (IProvideXXXAxisExtents) - all-axes-finalized (
IRequireAfterAxesFinalized)- called after all components transfer axis extents
- the place to calculate based on axis
Minimum,MaximumandRange
- render axis components (
IRequireRender)- it's now safe for axes to render their visual representation
- transform all components (
IRequireTransform)
Each ChartComponent maintains a Dirty flag, which it uses to bypass re-calculations during the render step. This can make a full render fast, if only one ChartComponent of many was changed.
This is very fast, consisting of only the transform portion. This render is performed when no data has changed, only the size of the Charts rectangle changed.
A ChartComponent may provide "dynamic" values that are not part of the DSRP (like HorizontalRule), and how the chart is updated when this happens is optimized, i.e. "unnecessary" parts of the rendering logic are skipped.
For ObservableCollection and other collections that support INotifyCollectionChanged, incremental updates are supported.
This executes the necessary components of a full render for entering items, but critically, facilitates the "shifting" of items on the right-hand side of the insert/delete point, and re-executing any dynamic logic (selectors and formatters).
Coupled with implicit composition animations on chart elements, this enables a very nice motion effect.
Rather than allow ChartComponents to arbitrarily access Visual Tree etc. this process is carefully controlled via Context interfaces. Each method of the rendering "pipeline" has a corresponding interface intended to provide only the necessary access.
©2017-22 eScape Technology LLC This project is Apache licensed!