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