/// <summary> /// Create a new <see cref="ExportedSeries"/> instance. /// </summary> /// <param name="series">The actual series.</param> /// <param name="xAxisRequirements">X axis requirements of the series.</param> /// <param name="yAxisRequirements">Y axis requirements of the series.</param> /// <param name="labels">Axis labels required by the series.</param> public ExportedSeries(Series series, AxisRequirements xAxisRequirements, AxisRequirements yAxisRequirements, AxisLabelCollection labels) { Result = series; XAxisRequirements = xAxisRequirements; YAxisRequirements = yAxisRequirements; AxisLabels = labels; }
/// <summary> /// Export the line series to an oxyplot series. /// </summary> /// <param name="series">The line series to be exported.</param> /// <param name="labels">Existing axis labels.</param> protected override (Series, AxisLabelCollection) Export(LineSeries series, AxisLabelCollection labels) { LineSeriesWithTracker result = new LineSeriesWithTracker(); DataPointCollection data = GetDataPoints(series.X, series.Y, labels); result.ItemsSource = data.Points; if (series.ShowOnLegend) { result.Title = series.Title; } // Line style/thickness result.LineStyle = series.LineConfig.Type.ToOxyPlotLineStyle(); result.StrokeThickness = series.LineConfig.Thickness.ToOxyPlotThickness(); // tbi: line colour configuration // result.Stroke = series.LineConfig.Colour.ToOxyPlotColour(); // Marker type/thickness result.MarkerType = series.MarkerConfig.Type.ToOxyPlotMarkerType(); result.MarkerSize = series.MarkerConfig.Size.ToOxyPlotMarkerSize() * series.MarkerConfig.SizeModifier; if (series.MarkerConfig.IsFilled()) { result.MarkerFill = series.Colour.ToOxyColour(); } else { result.MarkerFill = OxyColors.Undefined; } // Colour result.Color = series.Colour.ToOxyColour(); return(result, data.Labels); }
/// <summary> /// Export the error series to an oxyplot series. /// </summary> /// <param name="series">The error series to be exported.</param> /// <param name="labels">Existing axis labels on the graph.</param> protected override (Series, AxisLabelCollection) Export(ErrorSeries series, AxisLabelCollection labels) { var result = new OxyPlot.Series.ScatterErrorSeries(); (result.ItemsSource, labels) = GetErrorDataPoints(series.X, series.Y, series.XError, series.YError, labels); if (series.ShowOnLegend) { result.Title = series.Title; } // Line style/thickness // tbi: line colour configuration // result.Stroke = series.LineConfig.Colour.ToOxyPlotColour(); // Marker type/thickness // fixme - this is all duplicated from LineSeries. result.MarkerType = series.MarkerConfig.Type.ToOxyPlotMarkerType(); result.MarkerSize = series.MarkerConfig.Size.ToOxyPlotMarkerSize() * series.MarkerConfig.SizeModifier; if (series.MarkerConfig.IsFilled()) { result.MarkerFill = series.Colour.ToOxyColour(); } result.ErrorBarStrokeThickness = series.BarThickness.ToOxyPlotThickness(); // TBI: stopper thickness // Colour result.ErrorBarColor = series.Colour.ToOxyColour(); return(result, labels); }
/// <summary> /// Export the series to an oxyplot series. /// </summary> /// <remarks> /// When dealing with string data, the returned data points are ints /// which are indices into the axis labels list. Therefore we /// need to know about any existing axis labels. /// </remarks> /// <param name="series">The series to be exported.</param> /// <param name="existingAxisLabels">Existing axis labels on the graph.</param> public ExportedSeries Export(ISeries series, AxisLabelCollection existingAxisLabels) { (Series oxyPlotSeries, AxisLabelCollection labels) = Export((T)series, existingAxisLabels); AxisType?xAxisType = GetRequiredAxisType(series.X.FirstOrDefault()); AxisType?yAxisType = GetRequiredAxisType(series.Y.FirstOrDefault()); AxisRequirements xAxisRequirements = new AxisRequirements(xAxisType, series.XFieldName); AxisRequirements yAxisRequirements = new AxisRequirements(yAxisType, series.YFieldName); return(new ExportedSeries(oxyPlotSeries, xAxisRequirements, yAxisRequirements, labels)); }
/// <summary> /// Export the bar series to an oxyplot series. /// </summary> /// <param name="series">The bar series to be exported.</param> /// <param name="labels">Existing axis labels.</param> protected override (Series, AxisLabelCollection) Export(BarSeries series, AxisLabelCollection labels) { ColumnXYSeries result = new ColumnXYSeries(); if (series.ShowOnLegend) { result.Title = series.Title; } result.FillColor = series.FillColour.ToOxyColour(); result.StrokeColor = series.Colour.ToOxyColour(); DataPointCollection data = GetDataPoints(series.X, series.Y, labels); result.ItemsSource = data.Points; return(result, data.Labels); }
private (IList <BoxPlotItem>, AxisLabelCollection) GetBoxPlotItems(BoxWhiskerSeries series, AxisLabelCollection labels) { List <string> yLabels = labels.YLabels.ToList(); IEnumerable <double> y = series.Y.Select(yi => GetDataPointValue(yi, yLabels)); labels = new AxisLabelCollection(labels.XLabels, yLabels); double[] fiveNumberSummary = y.FiveNumberSummary(); double min = fiveNumberSummary[0]; double lowerQuartile = fiveNumberSummary[1]; double median = fiveNumberSummary[2]; double upperQuartile = fiveNumberSummary[3]; double max = fiveNumberSummary[4]; // fixme - this won't work with multiple box plot series on the same graph. double x = 0; IList <BoxPlotItem> items = new List <BoxPlotItem>() { new BoxPlotItem(x, min, lowerQuartile, median, upperQuartile, max) }; return(items, labels); }
/// <summary> /// Export the box and whisker series to an oxyplot series. /// </summary> /// <param name="series">The box and whisker series to be exported.</param> /// <param name="labels">Existing axis labels.</param> /// <param name="labels">Existing axis labels.</param> protected override (Series, AxisLabelCollection) Export(BoxWhiskerSeries series, AxisLabelCollection labels) { BoxPlotSeries result = new BoxPlotSeries(); (result.Items, labels) = GetBoxPlotItems(series, labels); if (series.ShowOnLegend) { result.Title = series.Title; } // Line style/thickness result.LineStyle = series.LineConfig.Type.ToOxyPlotLineStyle(); result.StrokeThickness = series.LineConfig.Thickness.ToOxyPlotThickness(); // tbi: line colour configuration // result.Stroke = series.LineConfig.Colour.ToOxyPlotColour(); // Marker type/thickness result.OutlierType = series.MarkerConfig.Type.ToOxyPlotMarkerType(); result.OutlierSize = series.MarkerConfig.Size.ToOxyPlotMarkerSize() * series.MarkerConfig.SizeModifier; // Colour result.Stroke = OxyColors.Transparent; result.Fill = series.Colour.ToOxyColour(); // todo: need to account for the possibility of string datatypes here. return(result, labels); }
/// <summary> /// Convert the given apsim graph to an oxyplot <see cref="PlotModel"/>. /// </summary> /// <param name="graph">The graph to be converted.</param> public IPlotModel ToPlotModel(IGraph graph) { if (graph.XAxis == null) { throw new NullReferenceException("Graph has no x-axis"); } if (graph.YAxis == null) { throw new NullReferenceException("Graph has no y-axis"); } if (graph.Legend == null) { throw new NullReferenceException("Graph has no legend configuration"); } if (graph.Series == null) { throw new NullReferenceException("Graph has no series"); } PlotModel plot = new PlotModel(); // Add series to graph. AxisLabelCollection labels = AxisLabelCollection.Empty(); ExportedSeries previous = null; AxisRequirements xAxisRequirements = null; AxisRequirements yAxisRequirements = null; foreach (Series graphSeries in graph.Series) { ExportedSeries series = graphSeries.ToOxyPlotSeries(labels); labels = series.AxisLabels; plot.Series.Add(series.Result); if (previous == null) { previous = series; } else { previous.ThrowIfIncompatibleWith(series); previous = series; } if (series.XAxisRequirements.AxisKind != null) { xAxisRequirements = series.XAxisRequirements; } if (series.YAxisRequirements.AxisKind != null) { yAxisRequirements = series.YAxisRequirements; } } // Axes (don't add them if there are no series to display on the graph). if (xAxisRequirements?.AxisKind != null) { plot.Axes.Add(graph.XAxis.ToOxyPlotAxis(xAxisRequirements, labels.XLabels)); } if (yAxisRequirements?.AxisKind != null) { plot.Axes.Add(graph.YAxis.ToOxyPlotAxis(yAxisRequirements, labels.YLabels)); } // Legend plot.Legends.Add(new Legend() { LegendOrientation = graph.Legend.Orientation.ToOxyPlotLegendOrientation(), LegendPosition = graph.Legend.Position.ToOxyPlotLegendPosition(), LegendPlacement = graph.Legend.InsideGraphArea ? OxyLegendPlacement.Inside : OxyLegendPlacement.Outside, Font = font, }); // Apply font plot.TitleFont = font; plot.SetLegendFont(font); plot.PlotAreaBorderThickness = new OxyThickness(0); plot.Title = graph.Title; return(plot); }
/// <summary> /// Get error points. /// </summary> /// <param name="x">X data.</param> /// <param name="y">Y data.</param> /// <param name="xError">X error data.</param> /// <param name="yError">Y error data.</param> /// <param name="labels">Existing axis labels on the graph.</param> private (IEnumerable <ScatterErrorPoint>, AxisLabelCollection) GetErrorDataPoints(IEnumerable <object> x, IEnumerable <object> y, IEnumerable <object> xError, IEnumerable <object> yError, AxisLabelCollection labels) { List <string> xLabels = labels.XLabels.ToList(); List <string> yLabels = labels.YLabels.ToList(); List <double> xValues = x.Select(xi => GetDataPointValue(xi, xLabels)).ToList(); List <double> yValues = y.Select(yi => GetDataPointValue(yi, yLabels)).ToList(); List <double> xErrorValues = xError.Select(xi => GetDataPointValue(xi, xLabels)).ToList(); List <double> yErrorValues = yError.Select(yi => GetDataPointValue(yi, yLabels)).ToList(); labels = new AxisLabelCollection(xLabels, yLabels); if (xValues.Count == yValues.Count) { if (xValues.Count == xErrorValues.Count && xValues.Count == yErrorValues.Count) { // We have error data for both x and y series. List <ScatterErrorPoint> points = new List <ScatterErrorPoint>(); for (int i = 0; i < xValues.Count; i++) { if (!double.IsNaN(xValues[i]) && !double.IsNaN(yValues[i]) && !double.IsNaN(xErrorValues[i]) && !double.IsNaN(yErrorValues[i])) { points.Add(new ScatterErrorPoint(xValues[i], yValues[i], xErrorValues[i], yErrorValues[i], 0)); } } return(points, labels); } else if (xValues.Count == xErrorValues.Count) { if (yErrorValues.Count != 0) { throw new ArgumentException($"Number of y error values ({yErrorValues.Count}) does not match number of datapoints or x error values ({xValues.Count})"); } // We have error data for x series. List <ScatterErrorPoint> points = new List <ScatterErrorPoint>(); for (int i = 0; i < xValues.Count; i++) { if (!double.IsNaN(xValues[i]) && !double.IsNaN(yValues[i]) && !double.IsNaN(xErrorValues[i])) { points.Add(new ScatterErrorPoint(xValues[i], yValues[i], xErrorValues[i], 0, 0)); } } return(points, labels); } else if (yValues.Count == yErrorValues.Count) { if (xErrorValues.Count != 0) { throw new ArgumentException($"Number of x error values ({xErrorValues.Count}) does not match number of datapoints or y error values ({xValues.Count})"); } // We have error data for y series. List <ScatterErrorPoint> points = new List <ScatterErrorPoint>(); for (int i = 0; i < xValues.Count; i++) { if (!double.IsNaN(xValues[i]) && !double.IsNaN(yValues[i]) && !double.IsNaN(yErrorValues[i])) { points.Add(new ScatterErrorPoint(xValues[i], yValues[i], 0, yErrorValues[i], 0)); } } return(points, labels); } else { // Throw error if we have nonzero x or y error data, // but the number of items doesn't match. if (xErrorValues.Count != 0) { if (yErrorValues.Count != 0) { throw new ArgumentException($"Number of x/y pairs ({xValues.Count}) does not match number of x error values ({xErrorValues.Count}) or number of y error values ({yErrorValues.Count})"); } throw new ArgumentException($"Number of x/y pairs ({xValues.Count}) does not match number of x error values ({xErrorValues.Count})"); } if (yErrorValues.Count != 0) { throw new ArgumentException($"Number of x/y pairs ({xValues.Count}) does not match number of y error values ({yErrorValues.Count})"); } // If we reached here, there is no x or y error values. // This raises the question of why this series is an error series // given that there's no error data. The most likely cause is a // programming error. However, we might as well just treat it as a // normal series and plot the x/y data anyway. IEnumerable <ScatterErrorPoint> points = xValues.Zip(yValues, (xi, yi) => new ScatterErrorPoint(xi, yi, 0, 0)); return(points, labels); } } else { throw new ArgumentException($"X and Y series are of different lengths ({xValues.Count} vs {yValues.Count})"); } }
/// <summary> /// fixme: use classes for different data types /// </summary> /// <remarks> /// When dealing with string data, the returned data points are ints /// which are indices into the axis labels list. Therefore we /// need to know about any existing axis labels. /// </remarks> /// <param name="x"></param> /// <param name="y"></param> protected DataPointCollection GetDataPoints(IEnumerable <object> x, IEnumerable <object> y, AxisLabelCollection existingAxisLabels) { if (x == null) { throw new ArgumentNullException("x data is null"); } if (y == null) { throw new ArgumentNullException("y data is null"); } List <DataPoint> data = new List <DataPoint>(); List <string> xAxisLabels = existingAxisLabels.XLabels.ToList(); List <string> yAxisLabels = existingAxisLabels.YLabels.ToList(); foreach ((object xi, object yi) in x.Zip(y)) { data.Add(new DataPoint(GetDataPointValue(xi, xAxisLabels), GetDataPointValue(yi, yAxisLabels))); } return(new DataPointCollection(data, xAxisLabels, yAxisLabels)); }
/// <summary> /// Export the series to an oxyplot series. /// </summary> /// <param name="series">The series to be exported.</param> protected abstract (Series, AxisLabelCollection) Export(T series, AxisLabelCollection existingAxisLabels);
/// <summary> /// Create a new <see cref="DataPointCollection"/> instance. /// </summary> /// <param name="dataPoints">The data points.</param> /// <param name="xLabels">The x-axis labels.</param> /// <param name="yLabels">The y-axis labels.</param> public DataPointCollection(IEnumerable <DataPoint> dataPoints, IEnumerable <string> xLabels, IEnumerable <string> yLabels) { Points = dataPoints; Labels = new AxisLabelCollection(xLabels, yLabels); }
/// <summary> /// Convert an apsim series to an oxyplot series. /// </summary> /// <remarks> /// When dealing with string data, the returned data points are ints /// which are indices into the axis labels list. Therefore we /// need to know about any existing axis labels. /// </remarks> /// <param name="series">The series to be converted.</param> /// <param name="labels">Existing axis labels on the graph.</param> public static ExportedSeries ToOxyPlotSeries(this APSIM.Shared.Graphing.Series series, AxisLabelCollection labels) { ISeriesExporter exporter = FindSeriesExporter(series); return(exporter.Export(series, labels)); }
/// <summary> /// Export the region series to an oxyplot series. /// </summary> /// <param name="series">The region series to be exported.</param> /// <param name="labels">Existing axis labels.</param> protected override (Series, AxisLabelCollection) Export(RegionSeries series, AxisLabelCollection labels) { throw new NotImplementedException(); }