// Copyright (C) Stichting Deltares 2017. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using Core.Common.Base; using Core.Components.Chart.Data; using Core.Components.Chart.Forms; using Core.Components.OxyPlot.DataSeries.Chart; using OxyPlot.Series; namespace Core.Components.OxyPlot.Forms { /// /// This class describes a plot view with configured representation of axes. /// public sealed class ChartControl : Control, IChartControl { private readonly RecursiveObserver chartDataCollectionObserver; private readonly IList drawnChartDataList = new List(); private LinearPlotView plotView; private DynamicPlotController plotController; private ChartDataCollection data; /// /// Creates a new instance of . /// public ChartControl() { InitializePlotView(); MinimumSize = new Size(100, 100); chartDataCollectionObserver = new RecursiveObserver(HandleChartDataCollectionChange, cdc => cdc.Collection); } public ChartDataCollection Data { get { return data; } set { if (data != null) { ClearChartData(); } data = value; chartDataCollectionObserver.Observable = data; if (data != null) { DrawInitialChartData(); } } } public string ChartTitle { get { return plotView.ModelTitle; } set { plotView.ModelTitle = value; } } public string BottomAxisTitle { get { return plotView.BottomAxisTitle; } set { plotView.BottomAxisTitle = value; } } public string LeftAxisTitle { get { return plotView.LeftAxisTitle; } set { plotView.LeftAxisTitle = value; } } public bool IsPanningEnabled { get { return plotController.IsPanningEnabled; } } public bool IsRectangleZoomingEnabled { get { return plotController.IsRectangleZoomingEnabled; } } public void TogglePanning() { plotController.TogglePanning(); } public void ToggleRectangleZooming() { plotController.ToggleRectangleZooming(); } public void ZoomToAllVisibleLayers() { ZoomToAllVisibleLayers(Data); } public void ZoomToAllVisibleLayers(ChartData layerData) { Extent extent = CreateEnvelopeForAllVisibleLayers(layerData); if (!extent.IsNaN) { Extent extentWithPadding = extent.AddPadding(0.01); plotView.SetExtent(extentWithPadding); plotView.Refresh(); } } protected override void Dispose(bool disposing) { plotView.Dispose(); chartDataCollectionObserver.Dispose(); base.Dispose(disposing); } /// /// Defines the area taken up by the visible chart data based on the provided chart data. /// /// The data to determine the visible extent for. /// The area definition. /// Thrown when is /// not part of the drawn chart data. private Extent CreateEnvelopeForAllVisibleLayers(ChartData chartData) { var collection = chartData as ChartDataCollection; if (collection != null) { return CreateEnvelopeForAllVisibleLayers(collection); } DrawnChartData drawnChartData = drawnChartDataList.FirstOrDefault(dmd => dmd.ChartData.Equals(chartData)); if (drawnChartData == null) { throw new ArgumentException($@"Can only zoom to {typeof(ChartData).Name} that is part of this {typeof(ChartControl).Name}s drawn {nameof(chartData)}.", nameof(chartData)); } var extent = new Extent(); ChartData chartDataDrawn = drawnChartData.ChartData; if (chartDataDrawn.IsVisible && chartDataDrawn.HasData) { extent.ExpandToInclude(CreateExtentFor(drawnChartData.ChartDataSeries as XYAxisSeries)); } return extent; } private static Extent CreateExtentFor(XYAxisSeries chartData) { return new Extent ( chartData.MinX, chartData.MaxX, chartData.MinY, chartData.MaxY ); } /// /// Defines the area taken up by the visible chart data based on the provided chart data. /// /// The data to determine the visible extent for. /// The area definition. /// Thrown when or /// any of its children is not part of the drawn chart data. private Extent CreateEnvelopeForAllVisibleLayers(ChartDataCollection chartDataCollection) { var envelope = new Extent(); foreach (ChartData childChartData in chartDataCollection.Collection) { envelope.ExpandToInclude(CreateEnvelopeForAllVisibleLayers(childChartData)); } return envelope; } private void InitializePlotView() { plotController = new DynamicPlotController(); plotView = new LinearPlotView { BackColor = Color.White, Model = { IsLegendVisible = false }, Controller = plotController }; Controls.Add(plotView); } private void HandleChartDataCollectionChange() { List chartDataThatShouldBeDrawn = Data.GetChartDataRecursively().ToList(); Dictionary drawnChartDataLookup = drawnChartDataList.ToDictionary(dcd => dcd.ChartData, dcd => dcd); DrawMissingChartDataOnCollectionChange(chartDataThatShouldBeDrawn, drawnChartDataLookup); RemoveRedundantChartDataOnCollectionChange(chartDataThatShouldBeDrawn, drawnChartDataLookup); drawnChartDataLookup = drawnChartDataList.ToDictionary(dcd => dcd.ChartData, dcd => dcd); ReorderChartDataOnCollectionChange(chartDataThatShouldBeDrawn, drawnChartDataLookup); plotView.InvalidatePlot(true); } private void DrawMissingChartDataOnCollectionChange(IEnumerable chartDataThatShouldBeDrawn, IDictionary drawnChartDataLookup) { foreach (ChartData chartDataToDraw in chartDataThatShouldBeDrawn.Where(chartDataToDraw => !drawnChartDataLookup.ContainsKey(chartDataToDraw))) { DrawChartData(chartDataToDraw); } } private void RemoveRedundantChartDataOnCollectionChange(IEnumerable chartDataThatShouldBeDrawn, IDictionary drawnChartDataLookup) { foreach (ChartData chartData in drawnChartDataLookup.Keys.Except(chartDataThatShouldBeDrawn)) { RemoveChartData(drawnChartDataLookup[chartData]); } } private void ReorderChartDataOnCollectionChange(IEnumerable chartDataThatShouldBeDrawn, IDictionary drawnChartDataLookup) { plotView.Model.Series.Clear(); foreach (ChartData chartData in chartDataThatShouldBeDrawn) { plotView.Model.Series.Add((Series) drawnChartDataLookup[chartData].ChartDataSeries); } } private void RemoveChartData(DrawnChartData drawnChartDataToRemove) { drawnChartDataToRemove.Observer.Dispose(); drawnChartDataList.Remove(drawnChartDataToRemove); plotView.Model.Series.Remove((Series) drawnChartDataToRemove.ChartDataSeries); } private void DrawInitialChartData() { foreach (ChartData chartData in Data.GetChartDataRecursively()) { DrawChartData(chartData); } plotView.InvalidatePlot(true); } private void DrawChartData(ChartData chartData) { IChartDataSeries chartDataSeries = ChartDataSeriesFactory.Create(chartData); var drawnChartData = new DrawnChartData { ChartData = chartData, ChartDataSeries = chartDataSeries }; drawnChartData.Observer = new Observer(() => { drawnChartData.ChartDataSeries.Update(); plotView.InvalidatePlot(true); }) { Observable = chartData }; drawnChartDataList.Add(drawnChartData); plotView.Model.Series.Add((Series) chartDataSeries); } private void ClearChartData() { foreach (DrawnChartData drawnChartData in drawnChartDataList) { drawnChartData.Observer.Dispose(); } drawnChartDataList.Clear(); plotView.Model.Series.Clear(); } /// /// Lookup class for administration related to drawn chart data series. /// private class DrawnChartData { /// /// The chart data which the drawn is based upon. /// public ChartData ChartData { get; set; } /// /// The drawn chart data series. /// public IChartDataSeries ChartDataSeries { get; set; } /// /// The observer attached to and responsible for updating . /// public Observer Observer { get; set; } } } }