protected override void OnRender(DrawingContext drawingContext) { if (double.IsNaN(ActualWidth) || double.IsNaN(ActualHeight)) { return; } if (!isRenderingNeeded && oldActualWidth == ActualWidth && oldActualHeight == ActualHeight) { return; } //Size has changed. Recreate all Visuals oldActualWidth = ActualWidth; oldActualHeight = ActualHeight; isRenderingNeeded = false; Visuals.Clear(); Visuals.Add(crerateBackGroundVisual()); foreach (Renderer renderer in renderers) { //////var xGridLineRenderer = renderer as XGridLineRenderer; //////if (xGridLineRenderer!=null) { ////// if (double.IsNaN(xGridLineRenderer.MinDisplayValueY) || double.IsNaN(xGridLineRenderer.MaxDisplayValueY)){ ////// xGridLineRenderer.SetDisplayValueRangeY(xGridLineRenderer.YLegendScroller.MinDisplayValue, xGridLineRenderer.YLegendScroller.MaxDisplayValue); ////// } //////} Visuals.Add(renderer.CreateVisual(ActualWidth, ActualHeight)); } TraceWpf.Line("PlotArea.OnRender: " + renderers.Count + " Renderer Visuals recreated"); }
// ---------------- protected override void OnProvideDefaultValues(out double displayValue, out double displayValueRange) { TraceWpf.Line(">>>>> LegendXDate.OnProvideDefaultValues()"); DisplayDate = DateTime.Now.AddDays(-7); displayValue = DisplayDate.ToDouble(); DisplayDateRange = TimeSpan.FromDays(1); displayValueRange = DisplayDateRange.ToDouble(); }
public Chart2Plots1X2YLegendsWindow() { InitializeComponent(); TestChart2Plots1X2YLegendsTraced.PlotAreaLower.Background = Brushes.LightGoldenrodYellow; TraceWpf.Line(">>>>> Chart2Plots1X2YLegendsWindow.fillDataSeries()"); fillDataSeries(); }
public Chart4Plots1X4YLegendsWindow() { InitializeComponent(); TestChart4Plots1X4YLegendsTraced.PlotArea1.Background = Brushes.SeaShell; TestChart4Plots1X4YLegendsTraced.PlotArea2.Background = Brushes.Cornsilk; TestChart4Plots1X4YLegendsTraced.PlotArea3.Background = Brushes.Honeydew; TraceWpf.Line(">>>>> Chart4Plots1X4YLegendsWindow.fillDataSeries()"); fillDataSeries(); }
/// <summary> /// Removes all Renderers from PlotArea /// </summary> public void ClearRenderers() { renderers.Clear(); if (Visuals.Count > firstRendererVisual) { TraceWpf.Line("PlotArea.ClearRenderers(), remove Visuals"); //remove all renderer visuals, but leave background visual Visuals.RemoveRange(firstRendererVisual, Visuals.Count - firstRendererVisual); } else { TraceWpf.Line("PlotArea.ClearRenderers(), no Visuals"); } }
/// <summary> /// Adds one renderer to PlotArea /// </summary> /// <param name="renderer"></param> public void AddRenderer(Renderer renderer) { TraceWpf.Line("PlotArea.AddRenderer()"); renderers.Add(renderer); renderer.RenderingRequested += renderer_RenderingRequested; //When a new renderer gets added, first the legends has to be calculated again, which might change the width of the legend and //in consequence also the width of the Plot-area isRenderingNeeded = true; InvalidateVisual(); //////if (Visuals.Count<firstRendererVisual) { ////// //Background not added yet => onRender will get executed later, which will add the visuals for the Renderer; ////// //nothing to do now //////} else { ////// //Background Visual exists already. Other Visuals can be added. ////// Visuals.Add(renderer.Render(ActualWidth, ActualHeight)); //////} RendererAdded?.Invoke(renderer); }
/// <summary> /// Replaces the old Visual created by this Renderer with a new one /// </summary> void renderer_RenderingRequested(Renderer renderer) { if (Visuals.Count <= firstRendererVisual) { TraceWpf.Line("PlotArea.RenderingRequested(" + renderer.RendererId + "): delayed Visual"); //Background not added yet => onRender will get executed later, which will add the visuals for the Renderer; //nothing to do now } else { TraceWpf.Line("PlotArea.RenderingRequested(" + renderer.RendererId + "): Visual updated"); int rendererIndex = renderers.IndexOf(renderer); if (rendererIndex == -1) { throw new Exception("RenderingRequested: renderer '" + renderer + "' not found in renderers (Count: " + renderers.Count + ")."); } int visualIndex = firstRendererVisual + rendererIndex; Visuals.RemoveAt(visualIndex); Visuals.Insert(visualIndex, renderer.CreateVisual(ActualWidth, ActualHeight)); } }
protected override bool OnIsRecalculationNeeded(Size renderContentSize) { TraceWpf.Line(">>>>> LegendxDate.OnIsRecalculationNeeded()"); //check first if DisplayValue has changed, which most likely comes from LegendScroller bool hasDisplayDateChanged = false; if (displayValueTracked != DisplayValue) { displayValueTracked = DisplayValue; displayDateTracked = DisplayDate = DisplayValue.ToDateTime(); hasDisplayDateChanged = true; } if (displayValueRangeTracked != DisplayValueRange) { displayValueRangeTracked = DisplayValueRange; displayDateRangeTracked = DisplayDateRange = displayValueRangeTracked.ToTimeSpan(); hasDisplayDateChanged = true; } if (!hasDisplayDateChanged) { //if DisplayValue (LegendScroller) hasn't changed, check if DisplayDate has been directly changed if (displayDateTracked != DisplayDate) { displayDateTracked = DisplayDate; displayValueTracked = DisplayValue = displayDateTracked.ToDouble(); hasDisplayDateChanged = true; } if (displayDateRangeTracked != DisplayDateRange) { displayDateRangeTracked = DisplayDateRange; displayValueRangeTracked = DisplayValueRange = displayDateRangeTracked.ToDouble(); hasDisplayDateChanged = true; } } return(base.OnIsRecalculationNeeded(renderContentSize) || hasDisplayDateChanged);//OnIsRecalculationNeeded needs to come first to guarantee its execution }
private Size doArrangeOverride(Size arrangeBounds) { if (double.IsNaN(arrangeBounds.Width) || double.IsInfinity(arrangeBounds.Width) || arrangeBounds.Width < 0 || double.IsNaN(arrangeBounds.Height) || double.IsInfinity(arrangeBounds.Height) || arrangeBounds.Height < 0) { //just curious if this ever happens. throw new Exception("Illegal dimensions of arrangeBounds: " + arrangeBounds); } Size adjustedBounds = arrangeBounds; if (isWidthInfinity || isHeightInfinity) { DependencyObject current = this; do { current = VisualTreeHelper.GetParent(current); if (current is UIElement parentUIElement) { double adjustedWidth, adjustedHeigth; adjustedWidth = isWidthInfinity ? parentUIElement.DesiredSize.Width : adjustedBounds.Width; adjustedHeigth = isHeightInfinity ? parentUIElement.DesiredSize.Height : adjustedBounds.Height; adjustedBounds = new Size(adjustedWidth, adjustedHeigth); break; } } while (current != null); } calculateBorderWidth(adjustedBounds, BorderPenThickness, Padding, out var borderWidth, out var contentAvailableWidth); calculateBorderHeight(adjustedBounds, BorderPenThickness, Padding, out var borderHeight, out var contentAvailableHeight); //arrange content within the size left, which might be 0. Size contentRequiredSize = ArrangeVisualsOverride(new Size(contentAvailableWidth, contentAvailableHeight)); //draw background and border if necessary if (actualArrangeBounds != adjustedBounds || actualBackground != Background || actualBorderBrush != BorderBrush || actualBorderPenThickness != BorderPenThickness) { //border and/or FrameWorkElementSize have changed. Redraw background actualArrangeBounds = adjustedBounds; actualBackground = Background; actualBorderBrush = BorderBrush; actualBorderPenThickness = BorderPenThickness; //remove old BackgroundDrawingVisual if (visuals.Count > 0 && ((DrawingVisual)visuals[0]) == backgroundDrawingVisual) { visuals.RemoveAt(0); } if (Background == null && BorderPenThickness <= 0 && Padding == thickness0) { //no BackgroundDrawingVisual needed backgroundDrawingVisual = null; hasBackgroundVisual = false; } else { //create updated background backgroundDrawingVisual = new DrawingVisual(); using (DrawingContext drawingContext = backgroundDrawingVisual.RenderOpen()) { if (BorderPenThickness > 0) { if (TraceWpf.IsTracing) { TraceWpf.Line(this, "DrawBackgroundBorder(Width: " + actualArrangeBounds.Width + ", Height: " + actualArrangeBounds.Height + ", Border: " + BorderPenThickness + ")"); } double halfBorderWidth = BorderPenThickness / 2.0; var drawingRect = new Rect(halfBorderWidth, halfBorderWidth, actualArrangeBounds.Width - BorderPenThickness, actualArrangeBounds.Height - BorderPenThickness); drawingContext.DrawRectangle(Background, new Pen(BorderBrush, BorderPenThickness), drawingRect); } else { if (TraceWpf.IsTracing) { TraceWpf.Line(this, "DrawBackground(Width: " + actualArrangeBounds.Width + ", Height: " + actualArrangeBounds.Height + ")"); } drawingContext.DrawRectangle(Background, null, new Rect(0, 0, actualArrangeBounds.Width, actualArrangeBounds.Height)); } } visuals.Insert(0, backgroundDrawingVisual); hasBackgroundVisual = true; } } //update DrawingVisual Offsets if necessary double offsetLeft = Math.Min(BorderPenThickness + Padding.Left, adjustedBounds.Width); double offsetTop = Math.Min(BorderPenThickness + Padding.Top, adjustedBounds.Height); if (actualOffsetLeft != offsetLeft || actualOffsetTop != offsetTop) { //border size has changed => calculate new offset actualOffsetLeft = offsetLeft; actualOffsetTop = offsetTop; offsetVector = new Vector(offsetLeft, offsetTop); for (int visualIndex = 0; visualIndex < visuals.Count; visualIndex++) { if (visualIndex == 0 && hasBackgroundVisual) { continue; } DrawingVisual updateVisual = (DrawingVisual)visuals[visualIndex]; if (updateVisual != null) { updateVisual.Offset = offsetVector; } } } ContentSize = new Size(contentAvailableWidth, contentAvailableHeight); if (actualContentSize != ContentSize) { actualContentSize = ContentSize; if (ContentSizeChanged != null) { hasContentSizeChanged = true; } } //report to the parent that at most arrangeBounds size was used. return(new Size(Math.Min(borderWidth + contentRequiredSize.Width, adjustedBounds.Width), Math.Min(borderHeight + contentRequiredSize.Height, adjustedBounds.Height))); }
/// <summary> /// Renders the chart graph to a Visual. The graphic gets scaled to the available height and width displaying only /// values between minValueDisplayX and minValueDisplayX. The actual values get cropped between minDisplayValueY /// and maxDisplayValueY. /// </summary> public Visual CreateVisual(double width, double height) { DrawingVisual drawingVisual = new DrawingVisual(); bool areMinMaxDefined = !double.IsNaN(MinDisplayValues[0]) && !double.IsNaN(MaxDisplayValues[0]); if (MinDisplayValues.Length > 1) { areMinMaxDefined = areMinMaxDefined && !double.IsNaN(MinDisplayValues[0]) && !double.IsNaN(MaxDisplayValues[0]); } if (!areMinMaxDefined || double.IsNaN(width) || double.IsNaN(height) || width <= 0 || height <= 0) { string message = "Renderer" + RendererId + "(): empty Visual returned"; if (DimensionX < MinDisplayValues.Length) { message += ", MinDisplayValueX: " + MinDisplayValues[DimensionX] + ", MaxDisplayValueX: " + MaxDisplayValues[DimensionX]; } if (DimensionY < MinDisplayValues.Length) { message += ", MinDisplayValueY: " + MinDisplayValues[DimensionY] + ", MaxDisplayValueY: " + MaxDisplayValues[DimensionY] + ""; } TraceWpf.Line(message); return(drawingVisual); //return an empty Visual, not null } if (DimensionMap[0] == DimensionX) { //if there is a DimensionX in DimensionMap, it must be the first entry per convention dimensionXIndex = 0; double differenceX = MaxDisplayValues[DimensionX] - MinDisplayValues[DimensionX]; if (differenceX == 0) { ScaleX = width; } else { ScaleX = width / differenceX; } } else { //DimensionX not used dimensionXIndex = int.MinValue; ScaleX = double.NaN; } if (DimensionMap[0] == DimensionY) { //if the DimensionMap has only 1 entry, only this one has to be checked dimensionYIndex = 0; } else if (DimensionMap.Length > 1 && DimensionMap[1] == DimensionY) { //if the DimensionMap has more than 1 entry, DimensionY is by convention the second entry in DimensionMap dimensionYIndex = 1; } if (dimensionYIndex > int.MinValue) { double differenceY = MaxDisplayValues[dimensionYIndex] - MinDisplayValues[dimensionYIndex]; if (differenceY == 0) { ScaleY = height; } else { ScaleY = height / differenceY; } } else { //DimensionY not used dimensionYIndex = int.MinValue; ScaleY = double.NaN; } using (DrawingContext drawingContext = drawingVisual.RenderOpen()) { OnCreateVisual(drawingContext, width, height, drawingVisual); } Rendered?.Invoke(this); return(drawingVisual); }
/// <summary> /// Updates the scrollbar to their values. Inheritors should call /// CalculateScrollBarValues latest during Arrange() /// </summary> protected void CalculateScrollBarValues() { if (double.IsInfinity(MinValue) || double.IsInfinity(MaxValue) || double.IsInfinity(DisplayValue) || double.IsInfinity(DisplayValueRange)) { if (isExceptionThrown) { return; } isExceptionThrown = true; throw new ApplicationException("Infinity is not supported: MinValue " + MinValue + ", MaxValue " + MaxValue + ", DisplayValue " + DisplayValue + ", DisplayRange " + DisplayValueRange + "."); } //if max value range is not set (yet), use some default values to display something, in case it remains empty. It helps the //user to understand the GUI better, if he can see the legend even he has no data selected yet to be displayed if (MinValue == minValueInit || MaxValue == maxValueInit) { //there are no values. Use some default values MinValue = 0; MaxValue = 10; } double maxValueRange = MaxValue - MinValue; if (maxValueRange < 0) { if (isExceptionThrown) { return; } isExceptionThrown = true; throw new ApplicationException("MaxValue " + MaxValue + " - MinValue " + MinValue + " should be greater 0, but was " + maxValueRange + "."); } if (maxValueRange == 0) { //Min and Max are the same. This happens when the graphic displays only 1 record. In this case, the //range must be increased, otherwise nothing would get displayed. if (MinValue == 0) { MinValue = -1; MaxValue = 1; } else if (MinValue > 0) { MaxValue = MinValue * 2; MinValue = 0; } else { MinValue *= 2; MaxValue = 0; } maxValueRange = MaxValue - MinValue; } //ensure that DisplayValue and DisplayValueRange define a range between MinValue and MaxValue, otherwise //correct them accordingly. This correction is done without error message, since scrolling and zooming can //lead to illegal values if (DisplayValue == displayValueInit || DisplayValueRange == displayValueRangeInit || DisplayValueRange <= 0) { DisplayValue = MinValue; DisplayValueRange = maxValueRange; } if (DisplayValue < MinValue) { DisplayValue = MinValue; } if (DisplayValueRange >= (maxValueRange) * 0.99) { //when nearly everything needs to be displayed, display everything DisplayValueRange = maxValueRange; } bool canZoomIn; bool canZoomOut; if (DisplayValueRange >= maxValueRange) { DisplayValueRange = maxValueRange; canZoomOut = false; canZoomIn = true; } else { canZoomOut = true; if (DisplayValueRange < (maxValueRange / ZoomInLimit)) { //limit zooming in to 1 million times DisplayValueRange = maxValueRange / ZoomInLimit; canZoomIn = false; } else { canZoomIn = true; } } if (DisplayValue + DisplayValueRange > MaxValue) { DisplayValue = MaxValue - DisplayValueRange; } updateZoomState(canZoomIn, canZoomOut); isExceptionThrown = false; if (!areNewMinMax && minValueTracked == MinValue && displayValueTracked == DisplayValue && displayValueRangeTracked == DisplayValueRange && maxValueTracked == MaxValue) { return; //nothing to do } //update the tracked values, they might have changed minValueTracked = MinValue; displayValueTracked = DisplayValue; displayValueRangeTracked = DisplayValueRange; maxValueTracked = MaxValue; //set scrollbar values isArranging = true; ScrollBar.Minimum = MinValue; ScrollBar.Maximum = MaxValue - DisplayValueRange; if (ScrollBar.Orientation == Orientation.Horizontal) { ScrollBar.Value = DisplayValue; } else { ScrollBar.Value = ScrollBar.Minimum + ScrollBar.Maximum - DisplayValue; } double largeChange = DisplayValueRange; if (ScrollBar.LargeChange != largeChange) { ScrollBar.LargeChange = largeChange; ScrollBar.SmallChange = largeChange / largeSmallChangeRatio; ScrollBar.ViewportSize = largeChange; } isArranging = false; areNewMinMax = false; TraceWpf.Line(">>>>> LegendScroller.CalculateScrollBarValues(): Copy to legend.MinValue"); legend.MinValue = minValueTracked; legend.DisplayValue = displayValueTracked; legend.DisplayValueRange = displayValueRangeTracked; legend.MaxValue = maxValueTracked; }