From 7633e478514172ab89e2c383f1924f222e0f9029 Mon Sep 17 00:00:00 2001 From: Justin Fainges Date: Mon, 27 Jul 2015 10:10:33 +1000 Subject: [PATCH 1/4] Added missing forestry view and presenter. --- .../StaticForestrySystemPresenter.cs | 151 ++++ .../StaticForestrySystemView.Designer.cs | 178 ++++ .../Views/StaticForestrySystemView.cs | 854 ++++++++++++++++++ 3 files changed, 1183 insertions(+) create mode 100644 UserInterface/Presenters/StaticForestrySystemPresenter.cs create mode 100644 UserInterface/Views/StaticForestrySystemView.Designer.cs create mode 100644 UserInterface/Views/StaticForestrySystemView.cs diff --git a/UserInterface/Presenters/StaticForestrySystemPresenter.cs b/UserInterface/Presenters/StaticForestrySystemPresenter.cs new file mode 100644 index 0000000000..543c478043 --- /dev/null +++ b/UserInterface/Presenters/StaticForestrySystemPresenter.cs @@ -0,0 +1,151 @@ +namespace UserInterface.Presenters +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Windows.Forms; + using System.Linq; + using System.Text; + using Models.Core; + using Models.Soils; + using Models; + using Views; + + public class StaticForestrySystemPresenter : IPresenter, IExportable + { + private StaticForestrySystem ForestryModel; + private StaticForestrySystemView ForestryViewer; + + public double[] SoilMidpoints; + + public void Attach(object model, object view, ExplorerPresenter explorerPresenter) + { + ForestryModel = model as StaticForestrySystem; + ForestryViewer = view as StaticForestrySystemView; + + AttachData(); + ForestryViewer.OnCellEndEdit += OnCellEndEdit; + } + + public void Detach() + { + SaveTable(); + ForestryViewer.OnCellEndEdit -= OnCellEndEdit; + } + + private void SaveTable() + { + ForestryModel.NDemand = ForestryViewer.NDemand; + ForestryModel.RootRadius = ForestryViewer.RootRadius; + + DataTable table = ForestryViewer.GetTable(); + + if (table == null) + return; + + for (int i = 0; i < ForestryModel.Table[1].Count; i++) + for (int j = 2; j < table.Columns.Count + 1; j++) + { + ForestryModel.Table[j][i] = table.Rows[i].Field(j-1); + } + } + + public string ConvertToHtml(string folder) + { + // TODO: Implement + return string.Empty; + } + + private void OnCellEndEdit(object sender, EventArgs e) + { + SaveTable(); + AttachData(); + } + + public void AttachData() + { + ForestryViewer.NDemand = ForestryModel.NDemand; + ForestryViewer.RootRadius = ForestryModel.RootRadius; + + Soil Soil; + List Zones = Apsim.ChildrenRecursively(ForestryModel, typeof(Zone)); + if (Zones.Count == 0) + return; + //get the first soil. For now we're assuming all soils have the same structure. + Soil = Apsim.Find(Zones[0], typeof(Soil)) as Soil; + + ForestryViewer.SoilMidpoints = Soil.DepthMidPoints; + //setup columns + List colNames = new List(); + + colNames.Add("Parameter"); + foreach (Zone z in Zones) + { + if (z is Simulation) + continue; + colNames.Add(z.Name); + } + + if (ForestryModel.Table.Count == 0) + { + ForestryModel.Table = new List>(); + ForestryModel.Table.Add(colNames); + + //setup rows + List rowNames = new List(); + + rowNames.Add("Wind reduction (%)"); + rowNames.Add("Shade (%)"); + rowNames.Add("Root Length Density (cm/cm3)"); + rowNames.Add("Depth (cm)"); + + foreach (string s in Soil.Depth) + { + rowNames.Add(s); + } + + ForestryModel.Table.Add(rowNames); + for (int i = 2; i < colNames.Count + 1; i++) + { + ForestryModel.Table.Add(Enumerable.Range(1, rowNames.Count).Select(x => "0").ToList()); + } + for (int i = 2; i < ForestryModel.Table.Count; i++) //set Depth and RLD rows to empty strings + { + ForestryModel.Table[i][2] = string.Empty; + ForestryModel.Table[i][3] = string.Empty; + } + } + else + { + // add Zones not in the table + IEnumerable except = colNames.Except(ForestryModel.Table[0]); + foreach (string s in except) + ForestryModel.Table.Add(Enumerable.Range(1, ForestryModel.Table[1].Count).Select(x => "0").ToList()); + ForestryModel.Table[0].AddRange(except); + for (int i = 2; i < ForestryModel.Table.Count; i++) //set Depth and RLD rows to empty strings + { + ForestryModel.Table[i][2] = string.Empty; + ForestryModel.Table[i][3] = string.Empty; + } + + // remove Zones from table that don't exist in simulation + except = ForestryModel.Table[0].Except(colNames); + List indexes = new List(); + foreach (string s in except.ToArray()) + { + indexes.Add(ForestryModel.Table[0].FindIndex(x => s == x)); + } + + indexes.Sort(); + indexes.Reverse(); + + foreach (int i in indexes) + { + ForestryModel.Table[0].RemoveAt(i); + ForestryModel.Table.RemoveAt(i + 1); + } + } + ForestryViewer.SetupGrid(ForestryModel.Table); + } + } +} \ No newline at end of file diff --git a/UserInterface/Views/StaticForestrySystemView.Designer.cs b/UserInterface/Views/StaticForestrySystemView.Designer.cs new file mode 100644 index 0000000000..3b0939f7cf --- /dev/null +++ b/UserInterface/Views/StaticForestrySystemView.Designer.cs @@ -0,0 +1,178 @@ +namespace UserInterface.Views +{ + partial class StaticForestrySystemView + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.panel1 = new System.Windows.Forms.Panel(); + this.Scalars = new System.Windows.Forms.DataGridView(); + this.Description = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.Value = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.Grid = new System.Windows.Forms.DataGridView(); + this.pBelowGround = new OxyPlot.WindowsForms.PlotView(); + this.pAboveGround = new OxyPlot.WindowsForms.PlotView(); + this.dataGridViewTextBoxColumn1 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn2 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.panel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.Scalars)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.Grid)).BeginInit(); + this.SuspendLayout(); + // + // panel1 + // + this.panel1.AutoSize = true; + this.panel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.panel1.Controls.Add(this.Scalars); + this.panel1.Controls.Add(this.Grid); + this.panel1.Controls.Add(this.pBelowGround); + this.panel1.Controls.Add(this.pAboveGround); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(527, 563); + this.panel1.TabIndex = 0; + // + // Scalars + // + this.Scalars.AllowUserToAddRows = false; + this.Scalars.AllowUserToDeleteRows = false; + this.Scalars.AllowUserToResizeColumns = false; + this.Scalars.AllowUserToResizeRows = false; + this.Scalars.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells; + this.Scalars.BackgroundColor = System.Drawing.SystemColors.ControlLightLight; + this.Scalars.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.Scalars.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.Description, + this.Value}); + this.Scalars.Location = new System.Drawing.Point(0, 0); + this.Scalars.Name = "Scalars"; + this.Scalars.RowHeadersVisible = false; + this.Scalars.Size = new System.Drawing.Size(165, 272); + this.Scalars.TabIndex = 5; + this.Scalars.CellEndEdit += new System.Windows.Forms.DataGridViewCellEventHandler(this.Scalars_CellEndEdit); + // + // Description + // + this.Description.HeaderText = "Description"; + this.Description.Name = "Description"; + this.Description.ReadOnly = true; + this.Description.Width = 85; + // + // Value + // + this.Value.HeaderText = "Value"; + this.Value.Name = "Value"; + this.Value.Width = 59; + // + // Grid + // + this.Grid.AllowUserToAddRows = false; + this.Grid.AllowUserToDeleteRows = false; + this.Grid.AllowUserToResizeColumns = false; + this.Grid.AllowUserToResizeRows = false; + this.Grid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells; + this.Grid.BackgroundColor = System.Drawing.SystemColors.ControlLightLight; + this.Grid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.Grid.Location = new System.Drawing.Point(171, 0); + this.Grid.Name = "Grid"; + this.Grid.RowHeadersVisible = false; + this.Grid.Size = new System.Drawing.Size(265, 272); + this.Grid.TabIndex = 4; + this.Grid.CellEndEdit += new System.Windows.Forms.DataGridViewCellEventHandler(this.Grid_CellEndEdit); + this.Grid.EditingControlShowing += new System.Windows.Forms.DataGridViewEditingControlShowingEventHandler(this.Grid_EditingControlShowing); + this.Grid.SelectionChanged += new System.EventHandler(this.Grid_SelectionChanged); + this.Grid.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Grid_KeyDown); + this.Grid.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.Grid_PreviewKeyDown); + // + // pBelowGround + // + this.pBelowGround.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.pBelowGround.Location = new System.Drawing.Point(271, 294); + this.pBelowGround.Name = "pBelowGround"; + this.pBelowGround.PanCursor = System.Windows.Forms.Cursors.Hand; + this.pBelowGround.Size = new System.Drawing.Size(256, 269); + this.pBelowGround.TabIndex = 3; + this.pBelowGround.Text = "plot1"; + this.pBelowGround.ZoomHorizontalCursor = System.Windows.Forms.Cursors.SizeWE; + this.pBelowGround.ZoomRectangleCursor = System.Windows.Forms.Cursors.SizeNWSE; + this.pBelowGround.ZoomVerticalCursor = System.Windows.Forms.Cursors.SizeNS; + // + // pAboveGround + // + this.pAboveGround.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.pAboveGround.Location = new System.Drawing.Point(0, 275); + this.pAboveGround.Name = "pAboveGround"; + this.pAboveGround.PanCursor = System.Windows.Forms.Cursors.Hand; + this.pAboveGround.Size = new System.Drawing.Size(265, 288); + this.pAboveGround.TabIndex = 2; + this.pAboveGround.Text = "plot1"; + this.pAboveGround.ZoomHorizontalCursor = System.Windows.Forms.Cursors.SizeWE; + this.pAboveGround.ZoomRectangleCursor = System.Windows.Forms.Cursors.SizeNWSE; + this.pAboveGround.ZoomVerticalCursor = System.Windows.Forms.Cursors.SizeNS; + // + // dataGridViewTextBoxColumn1 + // + this.dataGridViewTextBoxColumn1.HeaderText = "Description"; + this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1"; + this.dataGridViewTextBoxColumn1.ReadOnly = true; + this.dataGridViewTextBoxColumn1.Width = 85; + // + // dataGridViewTextBoxColumn2 + // + this.dataGridViewTextBoxColumn2.HeaderText = "Value"; + this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2"; + this.dataGridViewTextBoxColumn2.Width = 59; + // + // StaticForestrySystemView + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.panel1); + this.Name = "StaticForestrySystemView"; + this.Size = new System.Drawing.Size(527, 563); + this.Resize += new System.EventHandler(this.ForestryView_Resize); + this.panel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.Scalars)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.Grid)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Panel panel1; + private OxyPlot.WindowsForms.PlotView pBelowGround; + private OxyPlot.WindowsForms.PlotView pAboveGround; + private System.Windows.Forms.DataGridView Grid; + private System.Windows.Forms.DataGridView Scalars; + private System.Windows.Forms.DataGridViewTextBoxColumn Description; + private System.Windows.Forms.DataGridViewTextBoxColumn Value; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn2; + } +} diff --git a/UserInterface/Views/StaticForestrySystemView.cs b/UserInterface/Views/StaticForestrySystemView.cs new file mode 100644 index 0000000000..f4bd19f8b3 --- /dev/null +++ b/UserInterface/Views/StaticForestrySystemView.cs @@ -0,0 +1,854 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) APSIM Initiative +// +// ----------------------------------------------------------------------- +namespace UserInterface.Views +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Drawing; + using System.Data; + using System.Windows.Forms; + using Interfaces; + using Models.Graph; + using OxyPlot; + using OxyPlot.Annotations; + using OxyPlot.Axes; + using OxyPlot.Series; + using OxyPlot.WindowsForms; + using EventArguments; + using Classes; + using APSIM.Shared.Utilities; + + /// + /// A view that contains a graph and click zones for the user to allow + /// editing various parts of the graph. + /// + public partial class StaticForestrySystemView : UserControl, Interfaces.IGraphView + { + /// + /// A list to hold all plots to make enumeration easier. + /// + private List plots = new List(); + + /// + /// Overall font size for the graph. + /// + private const double FontSize = 14; + + /// + /// A table to hold tree data which is bound to the grid. + /// + private DataTable table; + + /// + /// Overall font to use. + /// + private new const string Font = "Calibri Light"; + + /// + /// Margin to use + /// + private const int TopMargin = 75; + + /// The smallest date used on any axis. + private DateTime smallestDate = DateTime.MaxValue; + + /// The largest date used on any axis + private DateTime largestDate = DateTime.MinValue; + + /// Current grid cell. + private int[] currentCell = new int[2] {-1, -1}; + + /// + /// Depth midpoints of the soil layers + /// + public double[] SoilMidpoints; + + /// + /// Nitrogen demand across all Zones + /// + public double NDemand; + + /// + /// Root radius in cm + /// + public double RootRadius; + + /// + /// Initializes a new instance of the class. + /// + public StaticForestrySystemView() + { + this.InitializeComponent(); + this.pAboveGround.Model = new PlotModel(); + this.pBelowGround.Model = new PlotModel(); + plots.Add(pAboveGround); + plots.Add(pBelowGround); + smallestDate = DateTime.MaxValue; + largestDate = DateTime.MinValue; + } + + /// + /// Invoked when the user clicks on the plot area (the area inside the axes) + /// + public event EventHandler OnPlotClick + { + add { } + remove { } + } + + /// + /// Invoked when the user clicks on an axis. + /// + public event ClickAxisDelegate OnAxisClick + { + add { } + remove { } + } + + /// + /// Invoked when the user finishes editing a cell. + /// + public event EventHandler OnCellEndEdit; + + /// + /// Invoked when the user clicks on a legend. + /// + public event EventHandler OnLegendClick + { + add { } + remove { } + } + + /// + /// Invoked when the user clicks on the graph title. + /// + public event EventHandler OnTitleClick + { + add { } + remove { } + } + + /// + /// Invoked when the user clicks on the graph caption. + /// + public event EventHandler OnCaptionClick + { + add { } + remove { } + } + + /// + /// Invoked when the user hovers over a series point. + /// + public event EventHandler OnHoverOverPoint; + + /// + /// Left margin in pixels. + /// + public int LeftRightPadding { get; set; } + + /// + /// Clear the graphs of everything. + /// + public void Clear() + { + foreach (PlotView p in plots) + { + p.Model.Series.Clear(); + p.Model.Axes.Clear(); + p.Model.Annotations.Clear(); + } + } + + /// + /// Refresh the graph. + /// + public override void Refresh() + { + foreach (PlotView p in plots) + { + p.Model.DefaultFont = Font; + p.Model.DefaultFontSize = FontSize; + + p.Model.PlotAreaBorderThickness = new OxyThickness(0); + p.Model.LegendBorder = OxyColors.Transparent; + p.Model.LegendBackground = OxyColors.White; + p.Model.InvalidatePlot(true); + + if (this.LeftRightPadding != 0) + this.Padding = new Padding(this.LeftRightPadding, 0, this.LeftRightPadding, 0); + + foreach (OxyPlot.Axes.Axis axis in p.Model.Axes) + { + this.FormatAxisTickLabels(axis); + } + + p.Model.InvalidatePlot(true); + } + } + + /// + /// Draw a line and markers series with the specified arguments. + /// + /// The series title + /// The x values for the series + /// The y values for the series + /// The axis type the x values are related to + /// The axis type the y values are related to + /// The series color + /// The type of series line + /// The type of series markers + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed.")] + public void DrawLineAndMarkers( + string title, + IEnumerable x, + IEnumerable y, + Models.Graph.Axis.AxisType xAxisType, + Models.Graph.Axis.AxisType yAxisType, + Color colour, + Models.Graph.Series.LineType lineType, + Models.Graph.Series.MarkerType markerType, + bool showOnLegend) + { + if (x != null && y != null) + { + Utility.LineSeriesWithTracker series = new Utility.LineSeriesWithTracker(); + series.OnHoverOverPoint += OnHoverOverPoint; + if (showOnLegend) + series.Title = title; + series.Color = ConverterExtensions.ToOxyColor(colour); + series.ItemsSource = this.PopulateDataPointSeries(x, y, xAxisType, yAxisType); + series.XAxisKey = xAxisType.ToString(); + series.YAxisKey = yAxisType.ToString(); + series.CanTrackerInterpolatePoints = false; + + bool filled = false; + string oxyMarkerName = markerType.ToString(); + if (oxyMarkerName.StartsWith("Filled")) + { + oxyMarkerName = oxyMarkerName.Remove(0, 6); + filled = true; + } + + // Line type. + LineStyle oxyLineType; + if (Enum.TryParse(lineType.ToString(), out oxyLineType)) + { + series.LineStyle = oxyLineType; + } + + // Marker type. + MarkerType type; + if (Enum.TryParse(oxyMarkerName, out type)) + { + series.MarkerType = type; + } + + series.MarkerSize = 7.0; + series.MarkerStroke = ConverterExtensions.ToOxyColor(colour); + if (filled) + { + series.MarkerFill = ConverterExtensions.ToOxyColor(colour); + series.MarkerStroke = OxyColors.White; + } + + if (title.Equals("Above Ground")) + pAboveGround.Model.Series.Add(series); + else + pBelowGround.Model.Series.Add(series); + } + } + + /// + /// Draw a bar series with the specified arguments. + /// + /// The series title + /// The x values for the series + /// The y values for the series + /// The axis type the x values are related to + /// The axis type the y values are related to + /// The series color + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed.")] + public void DrawBar( + string title, + IEnumerable x, + IEnumerable y, + Models.Graph.Axis.AxisType xAxisType, + Models.Graph.Axis.AxisType yAxisType, + Color colour, + bool showOnLegend) + { + } + + /// + /// Draw an area series with the specified arguments. A filled polygon is + /// drawn with the x1, y1, x2, y2 coordinates. + /// + /// The series title + /// The x1 values for the series + /// The y1 values for the series + /// The x2 values for the series + /// The y2 values for the series + /// The axis type the x values are related to + /// The axis type the y values are related to + /// The series color + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed.")] + public void DrawArea( + string title, + IEnumerable x1, + IEnumerable y1, + IEnumerable x2, + IEnumerable y2, + Models.Graph.Axis.AxisType xAxisType, + Models.Graph.Axis.AxisType yAxisType, + Color colour, + bool showOnLegend) + { + } + + /// + /// Draw text on the graph at the specified coordinates. + /// + /// The text to put on the graph + /// The x position in graph coordinates + /// The y position in graph coordinates + /// The axis type the x value relates to + /// The axis type the y value are relates to + /// The color of the text + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed.")] + public void DrawText( + string text, + double x, + double y, + Models.Graph.Axis.AxisType xAxisType, + Models.Graph.Axis.AxisType yAxisType, + Color colour) + { + } + + /// + /// Format the specified axis. + /// + /// The axis type to format + /// The axis title. If null then a default axis title will be shown + /// Invert the axis? + /// Minimum axis scale + /// Maximum axis scale + /// Axis scale interval + public void FormatAxis( + Models.Graph.Axis.AxisType axisType, + string title, + bool inverted, + double minimum, + double maximum, + double interval) + { + OxyPlot.Axes.Axis oxyAxis = this.GetAxis(axisType); + if (oxyAxis != null) + { + oxyAxis.Title = title; + oxyAxis.MinorTickSize = 0; + oxyAxis.AxislineStyle = LineStyle.Solid; + oxyAxis.AxisTitleDistance = 10; + if (inverted) + { + oxyAxis.StartPosition = 1; + oxyAxis.EndPosition = 0; + } + else + { + oxyAxis.StartPosition = 0; + oxyAxis.EndPosition = 1; + } + if (!double.IsNaN(minimum)) + oxyAxis.Minimum = minimum; + if (!double.IsNaN(maximum)) + oxyAxis.Maximum = maximum; + if (!double.IsNaN(interval) && interval > 0) + oxyAxis.MajorStep = interval; + } + } + + /// + /// Format the legend. + /// + /// Position of the legend + public void FormatLegend(Models.Graph.Graph.LegendPositionType legendPositionType) + { + LegendPosition oxyLegendPosition; + if (Enum.TryParse(legendPositionType.ToString(), out oxyLegendPosition)) + { + foreach (PlotView p in plots) + { + p.Model.LegendFont = Font; + p.Model.LegendFontSize = FontSize; + p.Model.LegendPosition = oxyLegendPosition; + p.Model.LegendSymbolLength = 30; + } + } + } + + /// + /// Format the title. + /// + /// Text of the title + public void FormatTitle(string text) + { + } + + /// + /// Format the footer. + /// + /// The text for the footer + public void FormatCaption(string text) + { + } + + /// + /// Show the specified editor. + /// + /// The editor to show + public void ShowEditorPanel(UserControl editor) + { + } + + /// + /// Export the graph to the specified 'bitmap' + /// + /// Bitmap to write to + public void Export(Bitmap bitmap) + { + int i = 0; + foreach (PlotView p in plots) + { + p.Dock = DockStyle.None; + p.Width = bitmap.Width; + p.Height = bitmap.Height / 2; + p.DrawToBitmap(bitmap, new Rectangle(0, p.Height * i, bitmap.Width, bitmap.Height / 2)); + p.Dock = DockStyle.Fill; + i++; + } + } + + /// + /// Add an action (on context menu) on the memo. + /// + /// Text for button + /// Event handler for button click + public void AddContextAction(string buttonText, System.EventHandler onClick) + { + } + + /// + /// Event handler for when user clicks close + /// + /// Event sender + /// Event arguments + private void OnCloseEditorPanel(object sender, EventArgs e) + { + } + + /// + /// Format axis tick labels so that there is a leading zero on the tick + /// labels when necessary. + /// + /// The axis to format + private void FormatAxisTickLabels(OxyPlot.Axes.Axis axis) + { + axis.IntervalLength = 100; + + if (axis is DateTimeAxis) + { + DateTimeAxis dateAxis = axis as DateTimeAxis; + + int numDays = (largestDate - smallestDate).Days; + if (numDays < 100) + dateAxis.IntervalType = DateTimeIntervalType.Days; + else if (numDays <= 366) + { + dateAxis.IntervalType = DateTimeIntervalType.Months; + dateAxis.StringFormat = "dd-MMM"; + } + else + dateAxis.IntervalType = DateTimeIntervalType.Years; + } + + if (axis is LinearAxis && + (axis.ActualStringFormat == null || !axis.ActualStringFormat.Contains("yyyy"))) + { + // We want the axis labels to always have a leading 0 when displaying decimal places. + // e.g. we want 0.5 rather than .5 + + // Use the current culture to format the string. + string st = axis.ActualMajorStep.ToString(System.Globalization.CultureInfo.InvariantCulture); + + // count the number of decimal places in the above string. + int pos = st.IndexOfAny(".,".ToCharArray()); + if (pos != -1) + { + int numDecimalPlaces = st.Length - pos - 1; + axis.StringFormat = "F" + numDecimalPlaces.ToString(); + } + } + } + + /// + /// Populate the specified DataPointSeries with data from the data table. + /// + /// The x values + /// The y values + /// The x axis the data is associated with + /// The y axis the data is associated with + /// A list of 'DataPoint' objects ready to be plotted + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed.")] + private List PopulateDataPointSeries( + IEnumerable x, + IEnumerable y, + Models.Graph.Axis.AxisType xAxisType, + Models.Graph.Axis.AxisType yAxisType) + { + return null; + } + + /// + /// Return an axis that has the specified AxisType. Returns null if not found. + /// + /// The axis type to retrieve + /// The axis + private OxyPlot.Axes.Axis GetAxis(Models.Graph.Axis.AxisType axisType) + { + return null; + } + + /// + /// Gets the maximum scale of the specified axis. + /// + public double AxisMaximum(Models.Graph.Axis.AxisType axisType) + { + OxyPlot.Axes.Axis axis = GetAxis(axisType); + if (axis != null) + { + return axis.ActualMaximum; + } + else + return double.NaN; + } + + /// + /// Gets the minimum scale of the specified axis. + /// + public double AxisMinimum(Models.Graph.Axis.AxisType axisType) + { + foreach (PlotView p in plots) + p.Refresh(); + OxyPlot.Axes.Axis axis = GetAxis(axisType); + + if (axis != null) + { + return axis.ActualMinimum; + } + else + return double.NaN; + } + + /// Gets the series names. + /// + public string[] GetSeriesNames() + { + List names = new List(); + foreach (OxyPlot.Series.Series series in this.pAboveGround.Model.Series) + { + names.Add("AG" + series.Title); + } + foreach (OxyPlot.Series.Series series in this.pBelowGround.Model.Series) + { + names.Add("BG" + series.Title); + } + return names.ToArray(); + } + + public void SetupGrid(List> data) + { + // setup scalar variables + Scalars.Rows.Clear(); + Scalars.Rows.Add("Nitrogen demand (kg/ha)", NDemand); + Scalars.Rows.Add("Root radius (cm)", RootRadius); + + table = new DataTable(); + // data[0] holds the column names + foreach (string s in data[0]) + { + table.Columns.Add(new DataColumn(s, typeof(string))); + } + + for (int i = 0; i < data[1].Count; i++) + { + string[] row = new string[table.Columns.Count]; + for (int j = 1; j < table.Columns.Count + 1; j++) + { + row[j - 1] = data[j][i]; + } + table.Rows.Add(row); + } + Grid.DataSource = table; + Grid.Columns[0].ReadOnly = true; //name column + Grid.Rows[2].ReadOnly = true; //RLD title row + Grid.Rows[2].DefaultCellStyle.BackColor = Color.LightGray; + Grid.Rows[3].ReadOnly = true; //Depth title row + Grid.Rows[3].DefaultCellStyle.BackColor = Color.LightGray; + ResizeControls(); + + SetupGraphs(); + } + + private void ResizeControls() + { + //resize Scalars + int width = 0; + int height = 0; + foreach (DataGridViewColumn col in Scalars.Columns) + width += col.Width; + foreach (DataGridViewRow row in Scalars.Rows) + height += row.Height; + Scalars.Width = width + 3; + if (height + 25 > Scalars.Parent.Height / 2) + { + Scalars.Height = Scalars.Parent.Height / 2; + Scalars.Width += 20; //extra width for scrollbar + } + else + Scalars.Height = height + 25; + + //resize Grid + width = 0; + height = 0; + + foreach (DataGridViewColumn col in Grid.Columns) + width += col.Width; + foreach (DataGridViewRow row in Grid.Rows) + height += row.Height; + Grid.Width = width + 3; + if (height + 25 > Grid.Parent.Height / 2) + { + Grid.Height = Grid.Parent.Height / 2; + Grid.Width += 25; //extra width for scrollbar + } + else + Grid.Height = height + 25; + Grid.Location = new Point(Scalars.Width + 10, 0); + + //resize above ground graph + pAboveGround.Width = pAboveGround.Parent.Width / 2; + pAboveGround.Height = pAboveGround.Parent.Height - Grid.Height; + pAboveGround.Location = new Point(0, Grid.Height); + + //resize below ground graph + pBelowGround.Width = pBelowGround.Parent.Width / 2; + pBelowGround.Height = pBelowGround.Parent.Height - Grid.Height; + pBelowGround.Location = new Point(pAboveGround.Width, Grid.Height); + } + + private void SetupGraphs() + { + try + { + pAboveGround.Model.Axes.Clear(); + pAboveGround.Model.Series.Clear(); + pBelowGround.Model.Axes.Clear(); + pBelowGround.Model.Series.Clear(); + pAboveGround.Model.Title = "Above Ground"; + pAboveGround.Model.PlotAreaBorderColor = OxyColors.White; + pAboveGround.Model.LegendBorder = OxyColors.Transparent; + CategoryAxis agxAxis = new CategoryAxis(); + agxAxis.Title = "Zone"; + agxAxis.AxislineStyle = LineStyle.Solid; + agxAxis.AxisDistance = 2; + agxAxis.Position = AxisPosition.Top; + + LinearAxis agyAxis = new LinearAxis(); + agyAxis.Title = "%"; + agyAxis.AxislineStyle = LineStyle.Solid; + agyAxis.AxisDistance = 2; + Utility.LineSeriesWithTracker seriesWind = new Utility.LineSeriesWithTracker(); + Utility.LineSeriesWithTracker seriesShade = new Utility.LineSeriesWithTracker(); + List pointsWind = new List(); + List pointsShade = new List(); + DataRow rowWind = table.Rows[0]; + DataRow rowShade = table.Rows[1]; + DataColumn col = table.Columns[0]; + double[] x = new double[table.Columns.Count - 1]; + double[] yWind = new double[table.Columns.Count - 1]; + double[] yShade = new double[table.Columns.Count - 1]; + + for (int i = 1; i < table.Columns.Count; i++) + { + agxAxis.Labels.Add(table.Columns[i].ColumnName); + } + pAboveGround.Model.Axes.Add(agxAxis); + pAboveGround.Model.Axes.Add(agyAxis); + + for (int i = 1; i < table.Columns.Count; i++) + { + if (rowWind[i].ToString() == "" || rowShade[i].ToString() == "") + return; + yWind[i - 1] = Convert.ToDouble(rowWind[i]); + yShade[i - 1] = Convert.ToDouble(rowShade[i]); + x[i - 1] = i - 1; + } + + for (int i = 0; i < x.Length; i++) + { + pointsWind.Add(new DataPoint(x[i], yWind[i])); + pointsShade.Add(new DataPoint(x[i], yShade[i])); + } + seriesWind.Title = "Wind"; + seriesShade.Title = "Shade"; + seriesWind.ItemsSource = pointsWind; + seriesShade.ItemsSource = pointsShade; + pAboveGround.Model.Series.Add(seriesWind); + pAboveGround.Model.Series.Add(seriesShade); + } + //don't draw the series if the format is wrong + catch (FormatException) + { + pBelowGround.Model.Series.Clear(); + } + + /////////////// Below Ground + try + { + pBelowGround.Model.Title = "Below Ground"; + pBelowGround.Model.PlotAreaBorderColor = OxyColors.White; + pBelowGround.Model.LegendBorder = OxyColors.Transparent; + LinearAxis bgxAxis = new LinearAxis(); + LinearAxis bgyAxis = new LinearAxis(); + List seriesList = new List(); + + bgyAxis.Position = AxisPosition.Left; + bgxAxis.Position = AxisPosition.Top; + bgyAxis.Title = "Depth (mm)"; + + bgxAxis.Title = "Root Length Density (cm/cm3)"; + bgxAxis.Minimum = 0; + bgxAxis.MinorTickSize = 0; + bgxAxis.AxislineStyle = LineStyle.Solid; + bgxAxis.AxisDistance = 2; + pBelowGround.Model.Axes.Add(bgxAxis); + + bgyAxis.StartPosition = 1; + bgyAxis.EndPosition = 0; + bgyAxis.MinorTickSize = 0; + bgyAxis.AxislineStyle = LineStyle.Solid; + pBelowGround.Model.Axes.Add(bgyAxis); + + for (int i = 1; i < table.Columns.Count; i++) + { + Utility.LineSeriesWithTracker series = new Utility.LineSeriesWithTracker(); + series.Title = table.Columns[i].ColumnName; + double[] data = new double[table.Rows.Count - 4]; + for (int j = 4; j < table.Rows.Count; j++) + { + data[j - 4] = Convert.ToDouble(table.Rows[j].Field(i)); + } + + List points = new List(); + + for (int j = 0; j < data.Length; j++) + { + points.Add(new DataPoint(data[j], SoilMidpoints[j])); + } + series.ItemsSource = points; + pBelowGround.Model.Series.Add(series); + } + } + //don't draw the series if the format is wrong + catch (FormatException) + { + pBelowGround.Model.Series.Clear(); + } + finally + { + pAboveGround.InvalidatePlot(true); + pBelowGround.InvalidatePlot(true); + } + } + + + public DataTable GetTable() + { + return table; + } + + private void ForestryView_Resize(object sender, EventArgs e) + { + ResizeControls(); + } + + private void Grid_CellEndEdit(object sender, DataGridViewCellEventArgs e) + { + if (!Grid.Rows[e.RowIndex].Cells[e.ColumnIndex].Selected) + return; + Invoke(OnCellEndEdit); + } + + private void Scalars_CellEndEdit(object sender, DataGridViewCellEventArgs e) + { + double val; + if (double.TryParse(Scalars.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as string, out val)) + { + NDemand = Convert.ToDouble(Scalars.Rows[0].Cells[1].Value); + RootRadius = Convert.ToDouble(Scalars.Rows[1].Cells[1].Value); + } + } + + private void Grid_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyData == Keys.Enter) + { + e.Handled = true; + } + } + + private void Grid_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) + { + TextBox box = (TextBox)e.Control; + box.PreviewKeyDown += Grid_PreviewKeyDown; + } + + private void Grid_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) + { + if (e.KeyData == Keys.Enter) + { + int col = Grid.CurrentCell.ColumnIndex; + int row = Grid.CurrentCell.RowIndex; + + if (row < Grid.RowCount - 1) + { + row++; + } + else + { + row = 0; + col++; + } + + if (col < Grid.ColumnCount) + currentCell = new int[2] {col, row}; + } + } + + private void Grid_SelectionChanged(object sender, EventArgs e) + { + if (currentCell[0] != -1) + { + Grid.CurrentCell = Grid[currentCell[0], currentCell[1]]; + currentCell = new int[2] {-1,-1}; + } + } + } +} \ No newline at end of file From 264c81dc83b4471fa4fb0e107ad9d870a93d995c Mon Sep 17 00:00:00 2001 From: Justin Fainges Date: Tue, 28 Jul 2015 12:05:51 +1000 Subject: [PATCH 2/4] Addition of wind equation into simple forestry system. --- Models/Agroforestry/LocalMicroClimate.cs | 8 +- Models/Agroforestry/StaticForestrySystem.cs | 52 +- Prototypes/Agroforestry/Agroforestry.apsimx | 570 ++++++++++-------- .../StaticForestrySystemPresenter.cs | 7 + UserInterface/UserInterface.csproj | 18 +- .../StaticForestrySystemView.Designer.cs | 49 +- .../Views/StaticForestrySystemView.cs | 215 ++++--- 7 files changed, 589 insertions(+), 330 deletions(-) diff --git a/Models/Agroforestry/LocalMicroClimate.cs b/Models/Agroforestry/LocalMicroClimate.cs index 613ec3abcf..bc574c158c 100644 --- a/Models/Agroforestry/LocalMicroClimate.cs +++ b/Models/Agroforestry/LocalMicroClimate.cs @@ -14,9 +14,11 @@ public class LocalMicroClimate : Model, IWeather { [Link] - Weather weather=null; // parent weather. + Weather weather = null; // parent weather. [Link] - StaticForestrySystem ParentSystem=null; + StaticForestrySystem ParentSystem = null; + [Link] + Clock clock = null; /// Gets the start date of the weather file public DateTime StartDate { get { return weather.StartDate; } } @@ -42,7 +44,7 @@ public class LocalMicroClimate : Model, IWeather /// /// Gets or sets the wind value found in weather file or zero if not specified. /// - public double Wind { get { return weather.Wind * (1 - ParentSystem.GetWindReduction(Parent as Zone) / 100); } } + public double Wind { get { return weather.Wind * ParentSystem.GetWindReduction(Parent as Zone, clock.Today); } } /// /// Gets or sets the CO2 level. If not specified in the weather file the default is 350. diff --git a/Models/Agroforestry/StaticForestrySystem.cs b/Models/Agroforestry/StaticForestrySystem.cs index 0bf7dcd6fd..f1578cb114 100644 --- a/Models/Agroforestry/StaticForestrySystem.cs +++ b/Models/Agroforestry/StaticForestrySystem.cs @@ -3,6 +3,7 @@ using System.Data; using System.Linq; using System.Text; +using MathNet.Numerics.Interpolation; using Models.Core; using System.Xml.Serialization; using Models.Interfaces; @@ -43,6 +44,18 @@ public class StaticForestrySystem : Zone,IUptake [XmlIgnore] public List ZoneInfoList; + /// + /// Date list for tree heights over lime + /// + [Summary] + public DateTime[] dates { get; set; } + + /// + /// Tree heights + /// + [Summary] + public double[] heights { get; set; } + /// /// Return the distance from the tree for a given zone /// @@ -63,7 +76,7 @@ public double GetDistanceFromTrees(Zone z) return D; } - throw new ApsimXException(this, "Could not find a shade value for zone called " + z.Name); + throw new ApsimXException(this, "Could not find zone called " + z.Name); } /// /// Return the %Shade for a given zone @@ -76,18 +89,47 @@ public double GetShade(Zone z) if (zi.zone == z) return zi.Shade; throw new ApsimXException(this, "Could not find a shade value for zone called " + z.Name); + + } /// /// Return the %Wind Reduction for a given zone /// /// Zone + /// The current date /// %Wind Reduction - public double GetWindReduction(Zone z) + public double GetWindReduction(Zone z, DateTime Today) { foreach (ZoneInfo zi in ZoneInfoList) if (zi.zone == z) - return zi.WindReduction; - throw new ApsimXException(this, "Could not find a shade value for zone called " + z.Name); + { + double UrelMin = Math.Max(0.0, 1.14 * 0.5 - 0.16); // 0.5 is porosity, will be dynamic in the future + double Urel; + double H; + bool didInterp; + double[] OADates = new double[dates.Count()]; + double heightToday; + + for (int i = 0; i < dates.Count(); i++) + OADates[i] = dates[i].ToOADate(); + + heightToday = MathUtilities.LinearInterpReal(Today.ToOADate(), OADates, heights, out didInterp); + + if (heightToday < 1000) + Urel = 1; + else + { + H = GetDistanceFromTrees(z) / (heightToday / 1000); + if (H < 6) + Urel = UrelMin + (1 - UrelMin) / 2 - H / 6 * (1 - UrelMin) / 2; + else if (H < 6.1) + Urel = UrelMin; + else + Urel = UrelMin + (1 - UrelMin) / (1 + 0.000928 * Math.Exp(Math.Pow(12.9372 * (H - 6), -0.26953))); + } + return Urel; + } + throw new ApsimXException(this, "Could not find zone called " + z.Name); } /// /// Return the area of the zone. @@ -131,7 +173,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) for (int i = 2; i < Table.Count; i++) { ZoneInfo newZone = new ZoneInfo(); - newZone.zone = Apsim.Child(this,Table[0][i - 1]) as Zone; + newZone.zone = Apsim.Child(this, Table[0][i - 1]) as Zone; newZone.WindReduction = Convert.ToDouble(Table[i][0]); newZone.Shade = Convert.ToDouble(Table[i][1]); newZone.RLD = new double[Table[1].Count - 4]; diff --git a/Prototypes/Agroforestry/Agroforestry.apsimx b/Prototypes/Agroforestry/Agroforestry.apsimx index 6b4d027268..0d76702823 100644 --- a/Prototypes/Agroforestry/Agroforestry.apsimx +++ b/Prototypes/Agroforestry/Agroforestry.apsimx @@ -1,5 +1,5 @@ - + Simulations Windbreak Example @@ -22,6 +22,113 @@ WindbreakSystem SowingRule + + SowingRule + + Zones; + private Plant15 Wheat; + private Soil Soil; + + [EventSubscribe("Commencing")] + private void OnSimulationCommencing(object sender, EventArgs e) + { + Zones = Apsim.FindAll(this, typeof(RectangularZone)); + foreach (Model m in Zones) + { + if(m.Name == ESWZoneName) + { + ESWZone = m as Zone; + break; + } + } + accumulatedRain = new Accumulator(this, "[Weather].Rain", RainDays); + } + + [EventSubscribe("DoManagement")] + private void OnDoManagement(object sender, EventArgs e) + { + accumulatedRain.Update(); + + Soil ESWSoil = Apsim.Find(ESWZone, typeof(Soil)) as Soil; + + foreach (Zone z in Zones) + { + Wheat = Apsim.Find(z, typeof(Plant15)) as Plant15; + Soil = Apsim.Find(z, typeof(Soil)) as Soil; + + if (Wheat != null && DateUtilities.WithinDates(StartDate,Clock.Today,EndDate) && + Wheat.plant_status=="out" && + ESWSoil.SoilWater.ESW > MinESW && + accumulatedRain.Sum > MinRain) + { + Wheat.Sow(population:Population, cultivar:CultivarName, depth:SowingDepth, rowSpacing:RowSpacing); + Fertiliser Fertiliser = Apsim.Find(Wheat.Parent, typeof(Fertiliser)) as Fertiliser; + Fertiliser.Apply(Amount: Amount, Type: Fertiliser.Types.NO3N); + } + } + } + } +} +]]> +