Visualize Data
Depending on your needs your might want to obtain more than just 'pure numbers' after you solved a model. Sometimes you want to present your results to the management (in a way that they can understand and make informed decisions based upon it). Sometimes you may want to publish your findings in a paper. Sometimes you just want a 'clear' view of the results just for yourself. Concise, you want to visualize your data and results.
Natively, OPTANO.Modeling does not provide such functionality. In order to produce plots of your data, you need to fall back upon other libraries. In this section we will briefly introduce OxyPlot, a cross platform plotting library, available on NuGet. We recommend using the latest pre-release ('unstable') version, since this version targets the .Net Standard 1.0, and thus will be compatible with your existing Optano.Modeling 2.x Project.
From oxyplot.org:
OxyPlot is a cross-platform plotting library for .NET.
The code is licensed under the MIT license. This is a very permissive and corporate friendly license. See oxyplot.org and tl;dr for more details.
The core library is a Portable Class Library [remark: Slightly outdated, since OxyPlot 2.x comes as a .Net Standard Library] that can be used on different platforms. Custom controls are implemented for WPF, Windows 8, Windows Phone, Windows Phone Silverlight, Windows Forms, Silverlight, GTK#, Xwt, Xamarin.iOS, Xamarin.Android, Xamarin.Forms and Xamarin.Mac.
Brief Example
In the covered example, we will visualize the solution for the Capacitated Lot-Sizing Problem from the example collection.
Preparation
- Setup a model that you'd like to visualize, e.g. one of our examples.
Install the OxyPlot package just as you did with the OPTANO.Modeling package:
dotnet add package OxyPlot.Core --version 2.0.0-unstable1013
Create a Plot
Presumably, you may want to visualize/plot the solution for your model. The first task is to decide what information you want to display, and how it can be presented. This is highly dependant on the problem/model that you're working on.
For the example problem, we'd like to plot the produced amounts and the state of the storage for every period. Additionally, we'd like to visualize the satisfied demand, broken down by origin, i.e. "From Inventory" or "From (current) Production".
To get started, we need to create a plotting canvas.
private static PlotModel CreatePlottingCanvas()
{
var plotModel = new PlotModel()
{
Title = "Demand Satisfaction",
LegendBorderThickness = 0,
LegendOrientation = LegendOrientation.Horizontal,
LegendPlacement = LegendPlacement.Outside,
LegendPosition = LegendPosition.BottomCenter,
};
var xAxis = new CategoryAxis();
xAxis.Labels.AddRange(Enumerable.Range(1, 8).Select(i => $"Period {i}"));
plotModel.Axes.Add(xAxis);
var yAxis = new LinearAxis
{
AbsoluteMinimum = 0,
MaximumPadding = 0.06,
MinimumPadding = 0,
MajorGridlineStyle = LineStyle.LongDash,
MajorStep = 10,
MinorGridlineStyle = LineStyle.Dot,
MinorStep = 5,
Title = "Units"
};
plotModel.Axes.Add(yAxis);
return plotModel;
}
The canvas then needs to be populated with data series to display. We'll do this in three steps.
First, the produced amount is added to the plot.
private static void AddProducedAmountToPlot(CapacitatedLotsizingModel clspModel, PlotModel oxyPlot)
{
var currentProduction = clspModel.PeriodInformation.Select(pi => new ColumnItem(clspModel.x[pi].Value)).ToList();
var productionSeries = new ColumnSeries()
{
Title = "Current Production",
FillColor = OxyColor.FromRgb(byte.Parse("165"), byte.Parse("185"), byte.Parse("79"))
};
productionSeries.Items.AddRange(currentProduction);
oxyPlot.Series.Add(productionSeries);
}
Then, the satisfied demands are added. Since the demands should be plotted broken down by origin, this requires some additional computation. We need to determine the amount of units taken from the inventory, and the amount taken from the current production. If possible, demands are satisfied from existing inventory, and only demands exceeding the existing inventory will be covered with the current production.
The resulting bar series will be stacked, since the satisfied demand is equal to the sum of the two individual sources.
private static void AddDemandSatisfactionToPlot(CapacitatedLotsizingModel clspModel, PlotModel oxyPlot)
{
var usedStorage = new List<ColumnItem>();
var usedProduction = new List<ColumnItem>();
foreach (var currentPeriod in clspModel.PeriodInformation)
{
var endStorage = clspModel.s[currentPeriod].Value;
var producedAmount = clspModel.x[currentPeriod].Value;
// basically represents the flow conservation constraint
var initialStorage = -producedAmount + endStorage + currentPeriod.Demand;
// split bar item in 2 sections.
var demandFromStorage = Math.Min(initialStorage, currentPeriod.Demand);
var demandFromProduction = Math.Min(currentPeriod.Demand - demandFromStorage, producedAmount);
usedStorage.Add(new ColumnItem(demandFromStorage));
usedProduction.Add(new ColumnItem(demandFromProduction));
}
var storageSeries = new ColumnSeries()
{
Title = "Demand from Inventory",
IsStacked = true,
StackGroup = "DemandSatisfaction",
FillColor = OxyColor.FromRgb(byte.Parse("132"), byte.Parse("122"), byte.Parse("178"))
};
storageSeries.Items.AddRange(usedStorage);
var productionSeries = new ColumnSeries()
{
Title = "Demand from Production",
IsStacked = true,
StackGroup = "DemandSatisfaction",
FillColor = OxyColor.FromRgb(byte.Parse("245"), byte.Parse("174"), byte.Parse("78"))
};
productionSeries.Items.AddRange(usedProduction);
oxyPlot.Series.Add(storageSeries);
oxyPlot.Series.Add(productionSeries);
}
Finally, we also want to display the amount of stored units at the end of every period.
private static void AddRemainStorageToPlot(CapacitatedLotsizingModel clspModel, PlotModel oxyPlot)
{
var dataPoints = clspModel.PeriodInformation.Select(pi => new ColumnItem(clspModel.s[pi].Value)).ToList();
var series = new ColumnSeries()
{
Title = "Remaining Inventory",
FillColor = OxyColor.FromRgb(byte.Parse("82"), byte.Parse("35"), byte.Parse("152"))
};
series.Items.AddRange(dataPoints);
oxyPlot.Series.Add(series);
}
We're now all set to plot/export the chart.
Export a Plot
OxyPlot offers a multitude of options for export, suiting (almost) all needs. The OxyPlot.Core package supports the export as *.pdf, *.svg and HTML5 SVG. For exporting *.png, you need to import one of the libraries built for your target platform. Unfortunately, the png-export currently only is available for Windows.
Additional information concerning the different export options and of how to use them can be found in the OxyPlot documentation.
In order to export our plot, all it takes is to use a file stream, create an OxyPlot Exporter and tell it it export the chart.
private static void ExportPlot(PlotModel plotModel)
{
using (var fileStream = File.Create("clspPlot.svg"))
{
var svgExporter = new SvgExporter() { Width = 600, Height = 400 };
svgExporter.Export(plotModel, fileStream);
}
using (var fileStream = File.Create("clspPlot.pdf"))
{
var pdfExporter = new PdfExporter() { Width = 600, Height = 400 };
pdfExporter.Export(plotModel, fileStream);
}
}
Piecing it all together:
Just call the functions in order and enjoy the result...
public static void CreateAndExportLotSizingPlot(CapacitatedLotsizingModel solvedClspModel)
{
var plotModel = CreatePlottingCanvas();
AddProducedAmountToPlot(solvedClspModel, plotModel);
AddDemandSatisfactionToPlot(solvedClspModel, plotModel);
AddRemainStorageToPlot(solvedClspModel, plotModel);
ExportPlot(plotModel);
}
Here you can find the complete PlottingUtils.cs source code. The resulting output should look like this: