Created
January 29, 2018 11:46
-
-
Save dodikk/02e85b3f38dc4bd87cf96a489a0e2983 to your computer and use it in GitHub Desktop.
[solved] OxyPlot columns and line series together Raw
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace PracticeDashboard.PlotBuilders.SalesPipeline | |
| { | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using Helpers; | |
| using global::OxyPlot; | |
| using global::OxyPlot.Axes; | |
| using global::OxyPlot.Series; | |
| using PracticeDashboard.DAL.Models.SalesPipeline; | |
| using Money = System.Decimal; | |
| using System.Diagnostics; | |
| using PracticeDashboard.Models; | |
| public class SalesPipelineNormalizedPlotBuilder | |
| { | |
| // http://docs.oxyplot.org/en/latest/guidelines/design.html | |
| // oxyplot guidelines | |
| // | |
| private readonly SalesPipelinePlotTheme currentTheme = SalesPipelinePlotTheme.DefaultTheme(); | |
| private SalesPipelineChartModel pipelineData; | |
| private SalesPipelineModelNormalizationHelper normalizedData; | |
| public SalesPipelineNormalizedPlotBuilder() | |
| { | |
| } | |
| #region ISalesPipelinePlotBuilder | |
| public PlotModel ResultModel { get; private set; } | |
| public SalesPipelineNormalizedPlotBuilder WithReport(SalesPipelineChartModel pipelineData) | |
| { | |
| Debug.Assert(null == this.pipelineData); | |
| this.pipelineData = pipelineData; | |
| this.GenerateNormalizedData(); | |
| return this; | |
| } | |
| public SalesPipelineNormalizedPlotBuilder Build() | |
| { | |
| Debug.Assert(null != this.pipelineData); | |
| this.ImplBuildPlot(); | |
| Debug.Assert(null != this.ResultModel); | |
| return this; | |
| } | |
| #endregion ISalesPipelinePlotBuilder | |
| private void ImplBuildPlot() | |
| { | |
| PlotModel result = new PlotModel(); | |
| this.AddAxisForBarsToPlot(result); | |
| this.AddVerticalAxisToPlot(result); | |
| this.AddCountLineToPlot(result); | |
| this.AddAmountLineSeriesToPlot(result); | |
| this.AddCountBarsToPlot(result); | |
| this.ResultModel = result; | |
| } | |
| #region logic | |
| private void GenerateNormalizedData() | |
| { | |
| Debug.Assert(null != this.pipelineData); | |
| this.normalizedData = new SalesPipelineModelNormalizationHelper(); | |
| this.normalizedData.Normalize( | |
| salesPipelineDataset: this.pipelineData, | |
| majorTickCount: this.currentTheme.numberOfSquaresInGrid); | |
| } | |
| #endregion logic | |
| #region series | |
| /** | |
| * | |
| * Adds "total count" data as it is. | |
| * | |
| */ | |
| private void AddCountLineToPlot(PlotModel result) | |
| { | |
| var dataset = this.pipelineData.ValuesByMonth; | |
| // Using point index as X value because of CategoryAxis | |
| // https://github.com/oxyplot/oxyplot/issues/1187 | |
| // | |
| Func<SalesPipelineDataPoint, DataPoint> dataPointBuilder = | |
| point => | |
| new DataPoint( | |
| //x: DateTimeAxis.ToDouble(point.yearAndMonth), | |
| x: dataset.IndexOf(point), | |
| y: Convert.ToDouble(point.pendingOpportunitiesTotalCount) | |
| ); | |
| List<DataPoint> points = | |
| dataset.Select(dataPointBuilder) | |
| .ToList(); | |
| LineSeries lineSeries = PlotHelpers.GetLineSeries( | |
| data: points, | |
| color: this.currentTheme.salesCountLineColor, | |
| markerSize: PlotHelpers.DefaultMarkerSize, | |
| thickness: PlotHelpers.DefaultThickness, | |
| filled: false); | |
| result.Series.Add(lineSeries); | |
| } | |
| /** | |
| * | |
| * Adds data of "amounts" normalized to "count space" | |
| * | |
| * | |
| */ | |
| private void AddAmountLineSeriesToPlot(PlotModel result) | |
| { | |
| var dataset = this.normalizedData.emulatedAmountsDataset.ValuesByMonth; | |
| // Using point index as X value because of CategoryAxis | |
| // https://github.com/oxyplot/oxyplot/issues/1187 | |
| // | |
| Func<SalesPipelineDataPointNormalized, DataPoint> dataPointBuilder = | |
| point => | |
| new DataPoint( | |
| // x: DateTimeAxis.ToDouble(point.yearAndMonth), | |
| x: dataset.IndexOf(point), | |
| y: point.totalBudgetAmountForPendingOpportunities | |
| ); | |
| List<DataPoint> points = dataset.Select(dataPointBuilder) | |
| .ToList(); | |
| LineSeries lineSeries = PlotHelpers.GetLineSeries( | |
| data: points, | |
| color: this.currentTheme.amountsLineColor, | |
| markerSize: PlotHelpers.DefaultMarkerSize, | |
| thickness: PlotHelpers.DefaultThickness, | |
| filled: false); | |
| result.Series.Add(lineSeries); | |
| } | |
| #endregion series | |
| #region Bars | |
| /** | |
| * | |
| * Adds tripple bar "count" data as it is. | |
| * | |
| */ | |
| private void AddCountBarsToPlot(PlotModel result) | |
| { | |
| var dataset = this.normalizedData.emulatedAmountsDataset.ValuesByMonth; | |
| //#if DEBUG | |
| // var dates = dataset.Select(dp => dp.yearAndMonth).ToArray(); | |
| // var started = dataset.Select(dp => dp.numberOfOpportunitiesStartedThisMonth).ToArray(); | |
| // var won = dataset.Select(dp => dp.numberOfOpportunitiesWonThisMonth).ToArray(); | |
| // var lost = dataset.Select(dp => dp.numberOfOpportunitiesLostThisMonth).ToArray(); | |
| //#endif | |
| // == | |
| // | |
| var startedBarPlotData = | |
| dataset.Select( | |
| salesPipelineDataPoint => | |
| new OxyColumnViewModel | |
| { | |
| Date = salesPipelineDataPoint.yearAndMonth, | |
| Value = salesPipelineDataPoint.numberOfOpportunitiesStartedThisMonth, | |
| Color = this.currentTheme.newCountBarColor | |
| }); | |
| var startedBarSeries = | |
| new ColumnSeries | |
| { | |
| StrokeColor = OxyColors.Transparent, | |
| StrokeThickness = 0, | |
| ColumnWidth = 4, | |
| ItemsSource = startedBarPlotData, | |
| ValueField = "Value", | |
| ColorField = "Color" | |
| }; | |
| // == | |
| // | |
| var wonBarPlotData = | |
| dataset.Select( | |
| salesPipelineDataPoint => | |
| new OxyColumnViewModel | |
| { | |
| Date = salesPipelineDataPoint.yearAndMonth, | |
| Value = salesPipelineDataPoint.numberOfOpportunitiesWonThisMonth, | |
| Color = this.currentTheme.wonCountBarColor | |
| }); | |
| var wonBarSeries = | |
| new ColumnSeries | |
| { | |
| StrokeColor = OxyColors.Transparent, | |
| StrokeThickness = 0, | |
| ColumnWidth = 4, | |
| ItemsSource = wonBarPlotData, | |
| ValueField = "Value", | |
| ColorField = "Color" | |
| }; | |
| // == | |
| // | |
| var lostBarPlotData = | |
| dataset.Select( | |
| salesPipelineDataPoint => | |
| new OxyColumnViewModel | |
| { | |
| Date = salesPipelineDataPoint.yearAndMonth, | |
| Value = salesPipelineDataPoint.numberOfOpportunitiesLostThisMonth, | |
| Color = this.currentTheme.lostCountBarColor | |
| }); | |
| var lostBarSeries = | |
| new ColumnSeries | |
| { | |
| StrokeColor = OxyColors.Transparent, | |
| StrokeThickness = 0, | |
| ColumnWidth = 4, | |
| ItemsSource = lostBarPlotData, | |
| ValueField = "Value", | |
| ColorField = "Color" | |
| }; | |
| result.Series.Add(startedBarSeries); | |
| result.Series.Add(wonBarSeries); | |
| result.Series.Add(lostBarSeries); | |
| } | |
| #endregion Bars | |
| #region Axes | |
| /** | |
| * | |
| * Expands range to | |
| * | |
| * ``` | |
| * max(totalCount) + normalized( max(amount) ) | |
| * ``` | |
| * | |
| */ | |
| private void AddVerticalAxisToPlot(PlotModel result) | |
| { | |
| int countMaxValue = this.normalizedData.countMax; | |
| double fCountMaxValue = this.normalizedData.fCountMax; | |
| double emulatedAmountMaxValue = this.normalizedData.emulatedAmountMax; | |
| // TODO: maybe we should round it to some "beautiful" value | |
| // like "5 / 10 / ..." | |
| double fNumberOfSquaresInGrid = Convert.ToDouble(this.currentTheme.numberOfSquaresInGrid); | |
| double step = this.normalizedData.countStep; | |
| double maxValue = Math.Max(fCountMaxValue, emulatedAmountMaxValue) + step; | |
| var verticalAxis = | |
| PlotHelpers.GetLinearAxis( | |
| minimum: 0, | |
| maximum: maxValue, | |
| textColor: this.currentTheme.axisColor, | |
| gridStep: step, | |
| position: AxisPosition.Right, | |
| moneyValues: false); | |
| var previousFormatter = verticalAxis.LabelFormatter; | |
| // TODO: maybe move lambda to PCL | |
| // so that it would be testable | |
| // | |
| // ** but not to SalesPipelineModelNormalizationHelper class | |
| // | |
| verticalAxis.LabelFormatter = | |
| (double arg) => | |
| { | |
| string currentLabel = previousFormatter(arg); | |
| double precision = 0.001; | |
| bool isZeroAmount = | |
| Math.Abs(arg - this.normalizedData.emulatedAmountZeroOffset) <= precision; | |
| bool isCountLabel = | |
| (arg < this.normalizedData.emulatedAmountZeroOffset); | |
| if (isZeroAmount) | |
| { | |
| return "$0"; | |
| } | |
| else if (isCountLabel) | |
| { | |
| string lambdaResult = currentLabel; | |
| return lambdaResult; | |
| } | |
| else | |
| { | |
| double recoveredAmount = | |
| this.normalizedData.ConvertEmulatedAmountToOriginalScale(arg); | |
| double millionsCount = | |
| recoveredAmount / 1000000; | |
| string strMillions = millionsCount.ToString("N1"); | |
| string lambdaResult = $"${strMillions}M"; | |
| return lambdaResult; | |
| } | |
| }; | |
| this.ConfigureGridForVerticalAxis(verticalAxis); | |
| result.Axes.Add(verticalAxis); | |
| } | |
| private void AddAxisForBarsToPlot(PlotModel result) | |
| { | |
| var dataset = this.normalizedData.emulatedAmountsDataset.ValuesByMonth; | |
| // CategoryAxis is forced by OxyPlot if ColumnSeries are used | |
| // otherwise an exception is thrown | |
| // | |
| var dates = | |
| dataset.Select( | |
| salesPipelineDataPoint => salesPipelineDataPoint.yearAndMonth | |
| ); | |
| var columnAxisX = new CategoryAxis | |
| { | |
| Position = AxisPosition.Bottom, | |
| ItemsSource = dates, | |
| StringFormat = "MMM yy", | |
| TicklineColor = OxyColors.Transparent, | |
| TextColor = this.currentTheme.axisColor, | |
| IsZoomEnabled = false, | |
| IsPanEnabled = true, | |
| // To avoid render collapse | |
| // https://github.com/oxyplot/oxyplot/issues/1187 | |
| // | |
| AbsoluteMinimum = -1, | |
| AbsoluteMaximum = dates.Count() | |
| }; | |
| this.ConfigureGridForTimeAxis(columnAxisX); | |
| result.Axes.Add(columnAxisX); | |
| } | |
| #endregion Axes | |
| #region Grid | |
| private void ConfigureGridForVerticalAxis(Axis verticalAxis) | |
| { | |
| verticalAxis.MajorGridlineStyle = LineStyle.Solid; | |
| verticalAxis.MajorGridlineThickness = 1; | |
| var blackColor = OxyColor.FromRgb(r: 0, g: 0, b: 0); | |
| verticalAxis.MajorGridlineColor = blackColor; | |
| } | |
| private void ConfigureGridForTimeAxis(Axis timeAxis) | |
| { | |
| timeAxis.MajorGridlineStyle = LineStyle.Dash; | |
| timeAxis.MajorGridlineThickness = 1; | |
| var blackColor = OxyColor.FromRgb(r: 0, g: 0, b: 0); | |
| timeAxis.MajorGridlineColor = blackColor; | |
| } | |
| #endregion Grid | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Implementation with Column Selection