/// <summary>
        /// Reconfigure components in response to layout change.
        /// Happens After OnApplyTemplate.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Chart_LayoutUpdated(object sender, object e)
        {
            // This is (NaN,NaN) if we haven't been sized yet
            var sz = new Size(ActualWidth, ActualHeight);

            //_trace.Verbose($"LayoutUpdated ({sz.Width}x{sz.Height})");
            if (!double.IsNaN(sz.Width) && !double.IsNaN(sz.Height))
            {
                // we are sized; see if dimensions actually changed
                if (sz.Width == 0 || sz.Height == 0)
                {
                    return;
                }
                if (CurrentLayout.IsSizeChanged(sz))
                {
                    _trace.Verbose($"LayoutUpdated.trigger ({sz.Width}x{sz.Height})");
                    var ls = new LayoutState()
                    {
                        Dimensions = sz, Layout = CurrentLayout.Layout
                    };
                    try {
                        RenderComponents(ls);
                    }
                    catch (Exception ex) {
                        _trace.Error($"{ex}");
                    }
                    finally {
                        CurrentLayout = ls;
                    }
                }
            }
        }
 /// <summary>
 /// Ctor.
 /// Establish default values.
 /// </summary>
 public Chart() : base()
 {
     DefaultStyleKey = typeof(Chart);
     LegendItems     = new ObservableCollection <LegendBase>();
     DataSources     = new ChartDataSourceCollection();
     DataSources.CollectionChanged += DataSources_CollectionChanged;
     Components = new ChartComponentCollection();
     Components.CollectionChanged += new NotifyCollectionChangedEventHandler(Components_CollectionChanged);
     Axes                = new List <IChartAxis>();
     DeferredEnter       = new List <ChartComponent>();
     LayoutUpdated      += new EventHandler <object>(Chart_LayoutUpdated);
     SizeChanged        += Chart_SizeChanged;
     DataContextChanged += Chart_DataContextChanged;
     CurrentLayout       = new LayoutState();
     Layers              = new List <IChartLayer>();
     AllPhases.Add(Layout_All);
     AllPhases.Add(LayoutComplete_All);
     AllPhases.Add(AfterAxesFinalized);
     AllPhases.Add(Render_AllAxes);
     AllPhases.Add(Render_NotAnAxis);
     AllPhases.Add(Render_Components);
     AllPhases.Add(Render_Components_PostAxis);
     AllPhases.Add(Transforms_All);
     AllPhases.Add(DataSourceUpdates_All);
     AllPhases.Add(ValueExtents_DataSeries);
     AllPhases.Add(ValueExtents_NotDataSeries);
 }
 /// <summary>
 /// Perform a full layout and rendering pass.
 /// At least ONE component reported as dirty.
 /// The full rendering sequence is: axis-reset, layout, render, transforms.
 /// SETs <see cref="LayoutState.Type"/> to FALSE.
 /// </summary>
 /// <param name="ls">Layout state.</param>
 protected void FullLayout(LayoutState ls)
 {
     ls.Type = RenderType.Full;
     ls.InitializeLayoutContext(Padding);
     _trace.Verbose($"full starting {ls.LayoutRect}");
     // Phase I: reset axes
     Phase_ResetAxes();
     // Phase II: claim space (IRequireLayout)
     Phase_Layout(ls);
     // Phase III: data source rendering pipeline (IDataSourceRenderer)
     Phase_RenderDataSources(ls);
     //Phase_AxisLimits((cc2) => cc2 is DataSeries && (cc2 is IProvideValueExtents));
     Phase_AxisLimits(ValueExtents_DataSeries.Items);
     // Phase IV: render non-axis components (IRequireRender)
     Phase_RenderComponents(ls);
     //Phase_AxisLimits((cc2) => !(cc2 is DataSeries) && (cc2 is IProvideValueExtents));
     Phase_AxisLimits(ValueExtents_NotDataSeries.Items);
     // Phase V: axes finalized
     Phase_AxesFinalized(ls);
     // Phase VI: post-axes finalized
     Phase_RenderPostAxesFinalized(ls);
     // Phase VII: render axes (IRequireRender)
     Phase_RenderAxes(ls);
     // Phase VIII: configure all transforms
     Phase_Transforms(ls);
 }
 /// <summary>
 /// Phase: render-components.
 /// </summary>
 /// <param name="ls">Layout state.</param>
 protected void Phase_RenderComponents(LayoutState ls)
 {
     foreach (IRequireRender irr in Render_Components.Items /*Components.Where((cc2) => !(cc2 is IChartAxis) && (cc2 is IRequireRender) && !(cc2 is IRequireRenderPostAxesFinalized))*/)
     {
         var ctx = ls.RenderFor(irr as ChartComponent, Surface, Components, DataContext);
         irr.Render(ctx);
     }
 }
 /// <summary>
 /// Adjust layout and transforms based on size change.
 /// SETs <see cref="LayoutState.Type"/> to <see cref="RenderType.TransformsOnly"/>.
 /// </summary>
 /// <param name="ls">Layout state.</param>
 protected void TransformsLayout(LayoutState ls)
 {
     ls.Type = RenderType.TransformsOnly;
     ls.InitializeLayoutContext(Padding);
     _trace.Verbose($"transforms-only starting {ls.LayoutRect}");
     Phase_Layout(ls);
     Phase_Transforms(ls);
 }
 /// <summary>
 /// Phase: render post-axes-finalized.
 /// </summary>
 /// <param name="ls">Layout state.</param>
 protected void Phase_RenderPostAxesFinalized(LayoutState ls)
 {
     foreach (IRequireRender irr in Render_Components_PostAxis.Items /*Components.Where((cc2) => !(cc2 is IChartAxis) && (cc2 is IRequireRender) && (cc2 is IRequireRenderPostAxesFinalized))*/)
     {
         var ctx = ls.RenderFor(irr as ChartComponent, Surface, Components, DataContext);
         _trace.Verbose($"render-post-axes-finalized {(irr as ChartComponent).Name} rect:{ctx.Area}");
         irr.Render(ctx);
     }
 }
 /// <summary>
 /// Phase: transforms.
 /// </summary>
 /// <param name="ls">Layout state.</param>
 protected void Phase_Transforms(LayoutState ls)
 {
     foreach (IRequireTransforms irt in Transforms_All.Items /*Components.Where((cc2) => cc2 is IRequireTransforms)*/)
     {
         var ctx = ls.RenderFor(irt as ChartComponent, Surface, Components, DataContext);
         _trace.Verbose($"transforms {irt} {ctx.Area}");
         irt.Transforms(ctx);
     }
 }
        /// <summary>
        /// Phase: Data Source Render Pipeline.
        /// </summary>
        /// <param name="ls">Layout state.</param>
        protected void Phase_RenderDataSources(LayoutState ls)
        {
            var dsctx = new DefaultDataSourceRenderContext(Surface, Components, ls.LayoutDimensions, Rect.Empty, ls.Layout.RemainingRect, DataContext);

            foreach (DataSource ds in DataSources)
            {
                ds.Render(dsctx);
            }
        }
 /// <summary>
 /// Phase: after-axes-finalized.
 /// </summary>
 /// <param name="ls">Layout state.</param>
 protected void Phase_AxesFinalized(LayoutState ls)
 {
     foreach (var iraaf in AfterAxesFinalized.Items)
     {
         var cc  = iraaf as ChartComponent;
         var ctx = ls.RenderFor(cc, Surface, Components, DataContext);
         _trace.Verbose($"axes-finalized {cc.Name} rect:{ctx.Area}");
         iraaf.AxesFinalized(ctx);
     }
 }
 /// <summary>
 /// Phase: axes have seen all values let them render (IRequireRender).
 /// </summary>
 /// <param name="ls">Layout state.</param>
 protected void Phase_RenderAxes(LayoutState ls)
 {
     foreach (var irr in Render_AllAxes.Items /*Axes.Where((cc2) => cc2 is IRequireRender)*/)
     {
         var ctx = ls.RenderFor(irr as ChartComponent, Surface, Components, DataContext);
         _trace.Verbose(() => {
             var axis = irr as IChartAxis;
             return($"limits {(irr as ChartComponent).Name} ({axis.Minimum},{axis.Maximum}) r:{axis.Range} rect:{ctx.Area}");
         });
         irr.Render(ctx);
     }
 }
 /// <summary>
 /// Transforms for single component.
 /// </summary>
 /// <param name="ls">Layout state.</param>
 /// <param name="rrea">Refresh request.</param>
 protected void ComponentTransforms(LayoutState ls, RefreshRequestEventArgs rrea)
 {
     if (rrea.Component is IRequireTransforms irt)
     {
         var rect = ls.Layout.For(rrea.Component);
         _trace.Verbose($"component-transforms {rrea.Component} {rrea.Axis} {rect}");
         var ctx = new DefaultRenderContext(Surface, Components, ls.LayoutDimensions, rect, ls.Layout.RemainingRect, DataContext)
         {
             Type = RenderType.TransformsOnly
         };
         irt.Transforms(ctx);
     }
 }
 /// <summary>
 /// Determine what kind of render is required, and run it.
 /// If anything is dirty, full layout, else adjust transforms.
 /// Once all components are "clean" only the visual transforms are updated; no data traversal is done.
 /// </summary>
 /// <param name="ls">The current layout state.</param>
 protected void RenderComponents(LayoutState ls)
 {
     _trace.Verbose($"render-components {ls.Dimensions.Width}x{ls.Dimensions.Height}");
     if (ls.Dimensions.Width == 0 || ls.Dimensions.Height == 0)
     {
         return;
     }
     if (DataSources.Cast <DataSource>().Any((ds) => ds.IsDirty))
     {
         FullLayout(ls);
     }
     else
     {
         TransformsLayout(ls);
     }
 }
 /// <summary>
 /// Phase: Layout.
 /// IRequireLayout, finalize rects, IRequireLayoutComplete.
 /// </summary>
 /// <param name="ls">Layout state.</param>
 protected void Phase_Layout(LayoutState ls)
 {
     foreach (IRequireLayout irl in Layout_All.Items /*Components.Where((cc2) => cc2 is IRequireLayout)*/)
     {
         _trace.Verbose($"layout {irl}");
         irl.Layout(ls.Layout);
     }
     // what's left is for the data series area
     _trace.Verbose($"remaining {ls.Layout.RemainingRect}");
     ls.Layout.FinalizeRects();
     foreach (IRequireLayoutComplete irlc in LayoutComplete_All.Items /*Components.Where((cc2) => cc2 is IRequireLayoutComplete)*/)
     {
         _trace.Verbose($"layout-complete {irlc}");
         var rect = ls.Layout.For(irlc as ChartComponent);
         var ctx  = new DefaultLayoutCompleteContext(ls.Layout.Dimensions, rect, ls.Layout.RemainingRect);
         irlc.LayoutComplete(ctx);
     }
 }
 private void Chart_SizeChanged(object sender, SizeChangedEventArgs e)
 {
         #if false
     _trace.Verbose($"SizeChanged prev({e.PreviousSize.Width}x{e.PreviousSize.Height}) new({e.NewSize.Width}x{e.NewSize.Height})");
     if (e.NewSize.Width == 0 || e.NewSize.Height == 0)
     {
         return;
     }
     if (CurrentLayout.IsSizeChanged(e.NewSize))
     {
         var ls = new LayoutState()
         {
             Dimensions = e.NewSize, Layout = CurrentLayout.Layout
         };
         RenderComponents(ls);
         CurrentLayout = ls;
     }
         #endif
 }
        /// <summary>
        /// Process incremental updates to a <see cref="DataSource"/>.
        /// </summary>
        /// <param name="ncca">The operation.  Only <see cref="NotifyCollectionChangedAction.Add"/> and <see cref="NotifyCollectionChangedAction.Remove"/> are supported.</param>
        /// <param name="ls">Current layout state.</param>
        /// <param name="ds">The <see cref="DataSource"/> that changed.</param>
        /// <param name="startIndex">Starting index of update.</param>
        /// <param name="items">Item(s) involved in the update.</param>
        protected void IncrementalUpdate(NotifyCollectionChangedAction ncca, LayoutState ls, DataSource ds, int startIndex, IList items)
        {
            _trace.Verbose($"incr-update {ncca} '{ds.Name}' {ds} @{startIndex}+{items.Count}");
            ls.Type = RenderType.Incremental;
            // Phase I: reset axes
            Phase_ResetAxes();
            // Phase II: Phase_Layout (skipped)
            // Phase III: this loop comprises the DSRP
            foreach (var irdsu in DataSourceUpdates_All.Items.Where(irdsu2 => irdsu2.UpdateSourceName == ds.Name) /* Components.Where(xx => xx is IRequireDataSourceUpdates irdsu2 && irdsu2.UpdateSourceName == ds.Name)*/)
            {
                var cc = irdsu as ChartComponent;
                _trace.Verbose($"incr {ncca} '{cc.Name}' {cc}");
                var ctx = ls.RenderFor(cc, Surface, Components, DataContext);
                switch (ncca)
                {
                case NotifyCollectionChangedAction.Add:
                    irdsu.Add(ctx, startIndex, items);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    irdsu.Remove(ctx, startIndex, items);
                    break;
                }
            }
            // TODO above stage MAY generate additional update events, e.g. to ISeriesItemValues, that MUST be collected and distributed
            // TODO do it here and not allow things to directly connect to each other, so render pipeline stays under control
            Phase_AxisLimits(cc2 => cc2 is IRequireDataSourceUpdates irdsu2 && irdsu2.UpdateSourceName == ds.Name && cc2 is IProvideValueExtents);
            // Phase IV: render non-axis components (IRequireRender)
            // trigger render on other components since values they track may have changed
            foreach (IRequireRender irr in Render_NotAnAxis.Items.Where(cc2 => !(cc2 is IRequireDataSourceUpdates irdsu2 && irdsu2.UpdateSourceName == ds.Name)) /*Components.Where((cc2) => !(cc2 is IChartAxis) && !(cc2 is IRequireDataSourceUpdates irdsu2 && irdsu2.UpdateSourceName == ds.Name) && (cc2 is IRequireRender))*/)
            {
                var ctx = ls.RenderFor(irr as ChartComponent, Surface, Components, DataContext);
                irr.Render(ctx);
            }
            Phase_AxisLimits(cc2 => !(cc2 is IRequireDataSourceUpdates irdsu2 && irdsu2.UpdateSourceName == ds.Name) && cc2 is IProvideValueExtents);
            // Phase V: axis-finalized
            Phase_AxesFinalized(ls);
            // Phase VI: render axes
            Phase_RenderAxes(ls);
            // Phase VII: transforms
            Phase_Transforms(ls);
        }
 /// <summary>
 /// Render for single component.
 /// </summary>
 /// <param name="ls">Layout state.</param>
 /// <param name="rrea">Refresh request.</param>
 protected void ComponentRender(LayoutState ls, RefreshRequestEventArgs rrea)
 {
     if (rrea.Component is IRequireRender irr)
     {
         var rect = ls.Layout.For(rrea.Component);
         _trace.Verbose($"component-render {rrea.Component} {rrea.Axis} {rect}");
         if (rrea.Axis != AxisUpdateState.None)
         {
             // put axis limits into correct state for IRequireRender components
             Phase_ResetAxes();
             //Phase_AxisLimits((cc2) => cc2 is DataSeries && (cc2 is IProvideValueExtents));
             Phase_AxisLimits(ValueExtents_DataSeries.Items);
         }
         var ctx = new DefaultRenderContext(Surface, Components, ls.LayoutDimensions, rect, ls.Layout.RemainingRect, DataContext)
         {
             Type = RenderType.Component
         };
         irr.Render(ctx);
         if (rrea.Axis != AxisUpdateState.None)
         {
             // axes MUST be re-evaluated because this thing changed.
             //Phase_AxisLimits((cc2) => !(cc2 is DataSeries) && (cc2 is IProvideValueExtents));
             Phase_AxisLimits(ValueExtents_NotDataSeries.Items);
             Phase_AxesFinalized(ls);
             Phase_RenderPostAxesFinalized(ls);
             Phase_RenderAxes(ls);
             Phase_Transforms(ls);
         }
         else
         {
             if (rrea.Component is IRequireTransforms irt)
             {
                 irt.Transforms(ctx);
             }
         }
     }
 }