Skip to content

Instantly share code, notes, and snippets.

@dodikk
Created January 29, 2018 11:46
Show Gist options
  • Save dodikk/02e85b3f38dc4bd87cf96a489a0e2983 to your computer and use it in GitHub Desktop.
Save dodikk/02e85b3f38dc4bd87cf96a489a0e2983 to your computer and use it in GitHub Desktop.
[solved] OxyPlot columns and line series together Raw
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
}
}
@dodikk
Copy link
Author

dodikk commented Jan 29, 2018

@dodikk
Copy link
Author

dodikk commented Jan 30, 2018

Implementation with Column Selection

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;
    using global::OxyPlot.Annotations;
    using Xamarin.Forms;

    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;

        private RectangleAnnotation selectedArea;
        private CategoryAxis categoryAxisX;


        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


        #region Logic
        private void ImplBuildPlot()
        {
            PlotModel result = new PlotModel();

            this.AddAxisForBarsToPlot(result);
            this.AddVerticalAxisToPlot(result);


            this.AddCountLineToPlot(result);
            this.AddAmountLineSeriesToPlot(result);
            this.AddCountBarsToPlot(result);

            this.AddSelectionEventHandlersToPlot(result);

            this.ResultModel = result;
        }


        
        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;


            // ==
            //
            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 = 20, // screen points - ?
                    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 = 20, // screen points - ?
                    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 = 20, // screen points - ?
                    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;


            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
                );

            double lastMonthCategory = dataset.Count();

            // according to the design doc
            //
            double eightMonthsBefore = lastMonthCategory - 8; 

            // to make the chart look better on 
            // 1. UWP desktop
            // 2. iPad / Android Tablets
            //
            double oneYearBefore = lastMonthCategory - 13.5;

            var columnAxisX = new CategoryAxis
            {
                Position = AxisPosition.Bottom,
                ItemsSource = dates,
                StringFormat = "MMM", // "MMM yy" might be more usable until year restriction is introduced
                TicklineColor = OxyColors.Transparent,
                TextColor = this.currentTheme.axisColor,
                IsZoomEnabled = false,
                IsPanEnabled = true,

                // To avoid render collapse
                // https://github.com/oxyplot/oxyplot/issues/1187
                //
                AbsoluteMinimum = -1, // dataset units
                AbsoluteMaximum = dates.Count(), // dataset units
            };


            bool isSmallScreenSize = (Device.Idiom == TargetIdiom.Phone);
            // TODO: improve detection.
            // 1. maybe take page orientation into account
            // 2. Subscribe to orientation change events

            if (isSmallScreenSize)
            {
                columnAxisX.Minimum = eightMonthsBefore; // dataset units
                columnAxisX.Maximum = lastMonthCategory; // dataset units
            }
            else
            {
                columnAxisX.Minimum = oneYearBefore;      // dataset units
                columnAxisX.Maximum = lastMonthCategory;  // dataset units
            }

            this.categoryAxisX = columnAxisX;
            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

        #region Selection

        private void AddSelectionEventHandlersToPlot(PlotModel result)
        {
            this.selectedArea = new RectangleAnnotation
            {
                Fill = this.currentTheme.selectedAreaColor,
                MinimumX = 0,
                MaximumX = 0
            };
            result.Annotations.Add(this.selectedArea);

            // TODO: should we unsubscribe explicitly?
            //
            result.MouseUp += PlotMouseUpHandler;
            result.TouchCompleted += PlotGestureHandler;
        }


        private void PlotGestureHandler(
            object sender, 
            OxyTouchEventArgs firedEvent)
        {
            // TODO: maybe we'll have to filter "tap" / "touchUpInside" gestures
            //

            this.OnPointSelected(firedEvent.Position);
            firedEvent.Handled = true;
        }

        private void PlotMouseUpHandler(
            object sender, 
            OxyMouseEventArgs firedEvent)
        {
            this.OnPointSelected(firedEvent.Position);
            firedEvent.Handled = true;
        }

        private void ClearSelection()
        {
            this.selectedArea.MinimumX = 0;
            this.selectedArea.MaximumX = 0;
        }

        private void OnPointSelected(global::OxyPlot.ScreenPoint screenCoordinates)
        {
            var dataset = this.normalizedData.emulatedAmountsDataset.ValuesByMonth;
            CategoryAxis axis = this.categoryAxisX;

            DataPoint selectedPoint = this.selectedArea.InverseTransform(screenCoordinates);

            // We need it because integer value of X is "category middle"
            // This is a voodoo magic of `OxyPlot.CategoryAxis`
            //
            double halfColumnBias = 0.5;

            double rawSelectedCategoryIndex = Math.Floor(selectedPoint.X + halfColumnBias);
            int selectedCategoryIndex = Convert.ToInt32(rawSelectedCategoryIndex);
            Debug.Assert(selectedCategoryIndex >= 0);
            Debug.Assert(selectedCategoryIndex <= dataset.Count());




            bool isSelectionAreaEmpty = (Math.Abs(this.selectedArea.MaximumX - this.selectedArea.MinimumX) <= 0.001);
            bool isSomeCategorySelected = !isSelectionAreaEmpty;

            bool isUnselecting = false;
            if (isSomeCategorySelected)
            {
                double selectedAreaMid = (this.selectedArea.MaximumX + this.selectedArea.MinimumX) / 2;
                double rawPreselectedCategoryIndex = selectedAreaMid;

                int preselectedCategoryIndex = Convert.ToInt32(rawPreselectedCategoryIndex);
                Debug.Assert(preselectedCategoryIndex >= 0);
                Debug.Assert(preselectedCategoryIndex <= dataset.Count());

                bool isPreselectedColumnTapped = (preselectedCategoryIndex == selectedCategoryIndex);
                isUnselecting = isPreselectedColumnTapped;
            }

            if (isUnselecting)
            {
                this.selectedArea.MinimumX = 0;
                this.selectedArea.MaximumX = 0;
            }
            else
            {
                double thisCategoryStart = selectedCategoryIndex - halfColumnBias;
                double thisCategoryEnd = rawSelectedCategoryIndex + halfColumnBias;


                this.selectedArea.MinimumX = thisCategoryStart;
                this.selectedArea.MaximumX = thisCategoryEnd;
            }

            this.ResultModel.InvalidatePlot(updateData: true);
        }

        #endregion Selection
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment