/// <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); } } } }