From 65c450eb67491be2fcdbd773993d7d39e42d2d0c Mon Sep 17 00:00:00 2001 From: Murdo R Ergeaux Date: Wed, 18 Nov 2020 15:12:18 +0000 Subject: [PATCH] Add MinimumMajorIntervalCount and MaximumMajorIntervalCount Axis Properties (#24) --- CHANGELOG.md | 1 + .../ExampleLibrary/Axes/AxisExamples.cs | 83 +++++++++++++++++++ Source/OxyPlot/Axes/Axis.cs | 37 +++++++-- Source/OxyPlot/Axes/DateTimeAxis.cs | 24 +++--- Source/OxyPlot/Axes/TimeSpanAxis.cs | 24 +++--- 5 files changed, 139 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e27a69b3..d4465638f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. - Add properties for `MinimumSegmentLength` to series and annotations (#1853) - Add fractal examples for PolygonAnnotation and PolylineAnnotations (#1853) - Add `AxisPreference` to `PlotManipulator` +- Add MinimumMajorIntervalCount and MaximumMajorIntervalCount Axis Properties (#24) ### Changed - Update net40 and net45 to net452 (#1835) diff --git a/Source/Examples/ExampleLibrary/Axes/AxisExamples.cs b/Source/Examples/ExampleLibrary/Axes/AxisExamples.cs index e8d5908a2..2a73e07c9 100644 --- a/Source/Examples/ExampleLibrary/Axes/AxisExamples.cs +++ b/Source/Examples/ExampleLibrary/Axes/AxisExamples.cs @@ -1819,6 +1819,89 @@ public static PlotModel MarginsAndPaddingAsymmetrical() return plot; } + [Example("Minimum Major Interval Count")] + public static PlotModel MinimumMajorIntervalCount() + { + var plot = new PlotModel + { + Title = "MinimumMajorIntervalCount = 10", + }; + + var xaxis = new LinearAxis + { + Position = AxisPosition.Bottom, + MinimumMajorIntervalCount = 10, + }; + + plot.Axes.Add(xaxis); + + var yaxis = new LinearAxis + { + Position = AxisPosition.Left, + MinimumMajorIntervalCount = 10, + }; + + plot.Axes.Add(yaxis); + + return plot; + } + + [Example("Maximum Major Interval Count")] + public static PlotModel MaximumMajorIntervalCount() + { + var plot = new PlotModel + { + Title = "MaximumMajorIntervalCount = 5", + }; + + var xaxis = new LinearAxis + { + Position = AxisPosition.Bottom, + MaximumMajorIntervalCount = 5, + }; + + plot.Axes.Add(xaxis); + + var yaxis = new LinearAxis + { + Position = AxisPosition.Left, + MaximumMajorIntervalCount = 5, + }; + + plot.Axes.Add(yaxis); + + return plot; + } + + [Example("Minimum and Maximum Major Interval Count")] + public static PlotModel MinimumAndMaximumMajorIntervalCount() + { + var plot = new PlotModel + { + Title = "MinimumMajorIntervalCount = MaximumMajorIntervalCount = 4", + }; + + var xaxis = new LinearAxis + { + Position = AxisPosition.Bottom, + MinimumMajorIntervalCount = 4, + MaximumMajorIntervalCount = 4, + }; + + plot.Axes.Add(xaxis); + + var yaxis = new LinearAxis + { + Position = AxisPosition.Left, + MinimumMajorIntervalCount = 4, + MaximumMajorIntervalCount = 4, + }; + + plot.Axes.Add(yaxis); + + return plot; + } + private static CategoryAxis GetLongLabelSeries() { var axis = new CategoryAxis() { Position = AxisPosition.Bottom }; diff --git a/Source/OxyPlot/Axes/Axis.cs b/Source/OxyPlot/Axes/Axis.cs index a88f252d2..d11a6c78e 100644 --- a/Source/OxyPlot/Axes/Axis.cs +++ b/Source/OxyPlot/Axes/Axis.cs @@ -72,6 +72,8 @@ protected Axis() this.MajorStep = double.NaN; this.MinimumMinorStep = 0; this.MinimumMajorStep = 0; + this.MinimumMajorIntervalCount = 2; + this.MaximumMajorIntervalCount = double.MaxValue; this.MinimumPadding = 0.01; this.MaximumPadding = 0.01; @@ -162,6 +164,20 @@ protected Axis() /// public double ActualMajorStep { get; protected set; } + /// + /// Gets or sets the minimum number of major intervals on the axis. + /// + /// Non-integer values are accepted. + public double MinimumMajorIntervalCount { get; set; } + + /// + /// Gets or sets the minimum number of major intervals on the axis. + /// + /// Non-integer values are accepted. + /// The maximum will be bounded acording to the . + /// The takes precedence over the when determining the major step. + public double MaximumMajorIntervalCount { get; set; } + /// /// Gets or sets the actual maximum value of the axis. /// @@ -1376,7 +1392,7 @@ internal virtual void UpdateIntervals(OxyRect plotArea) this.ActualMajorStep = !double.IsNaN(this.MajorStep) ? this.MajorStep - : this.CalculateActualInterval(length, labelSize); + : this.CalculateActualInterval(length, labelSize, this.MinimumMajorIntervalCount, this.MaximumMajorIntervalCount); this.ActualMinorStep = !double.IsNaN(this.MinorStep) ? this.MinorStep @@ -1804,10 +1820,12 @@ protected void SetTransform(double newScale, double newOffset) /// /// Size of the available area. /// Maximum length of the intervals. + /// The minimum number of intervals. + /// The maximum number of intervals, once the minimum number of intervals is satisfied. /// The calculate actual interval. - protected virtual double CalculateActualInterval(double availableSize, double maxIntervalSize) + protected virtual double CalculateActualInterval(double availableSize, double maxIntervalSize, double minIntervalCount, double maxIntervalCount) { - return this.CalculateActualInterval(availableSize, maxIntervalSize, this.ActualMaximum - this.ActualMinimum); + return this.CalculateActualInterval(availableSize, maxIntervalSize, this.ActualMaximum - this.ActualMinimum, minIntervalCount, maxIntervalCount); } /// @@ -1816,8 +1834,10 @@ protected virtual double CalculateActualInterval(double availableSize, double ma /// The available size. /// The maximum interval size. /// The range. + /// The minimum number of intervals. + /// The maximum number of intervals, once the minimum number of intervals is satisfied. /// Actual interval to use to determine which values are displayed in the axis. - protected double CalculateActualInterval(double availableSize, double maxIntervalSize, double range) + protected double CalculateActualInterval(double availableSize, double maxIntervalSize, double range, double minIntervalCount, double maxIntervalCount) { if (availableSize <= 0) { @@ -1837,10 +1857,9 @@ protected double CalculateActualInterval(double availableSize, double maxInterva Func exponent = x => Math.Ceiling(Math.Log(x, 10)); Func mantissa = x => x / Math.Pow(10, exponent(x) - 1); - // reduce intervals for horizontal axis. - // double maxIntervals = Orientation == AxisOrientation.x ? MaximumAxisIntervalsPer200Pixels * 0.8 : MaximumAxisIntervalsPer200Pixels; - // real maximum interval count - double maxIntervalCount = availableSize / maxIntervalSize; + // bound min/max interval counts + minIntervalCount = Math.Max(minIntervalCount, 0); + maxIntervalCount = Math.Min(maxIntervalCount, availableSize / maxIntervalSize); range = Math.Abs(range); double interval = Math.Pow(10, exponent(range)); @@ -1869,7 +1888,7 @@ protected double CalculateActualInterval(double availableSize, double maxInterva intervalCandidate = removeNoise(intervalCandidate / 2.0); } - if (range / intervalCandidate > maxIntervalCount) + if (range / interval >= minIntervalCount && range / intervalCandidate > maxIntervalCount) { break; } diff --git a/Source/OxyPlot/Axes/DateTimeAxis.cs b/Source/OxyPlot/Axes/DateTimeAxis.cs index 119dd42ad..a377e91c2 100644 --- a/Source/OxyPlot/Axes/DateTimeAxis.cs +++ b/Source/OxyPlot/Axes/DateTimeAxis.cs @@ -312,13 +312,8 @@ protected override string FormatValueOverride(double x) return string.Format(this.ActualCulture, fmt, time); } - /// - /// Calculates the actual interval. - /// - /// Size of the available area. - /// Maximum length of the intervals. - /// The calculate actual interval. - protected override double CalculateActualInterval(double availableSize, double maxIntervalSize) + /// + protected override double CalculateActualInterval(double availableSize, double maxIntervalSize, double minIntervalCount, double maxIntervalCount) { const double Year = 365.25; const double Month = 30.5; @@ -341,11 +336,13 @@ protected override double CalculateActualInterval(double availableSize, double m double interval = goodIntervals[0]; - int maxNumberOfIntervals = Math.Max((int)(availableSize / maxIntervalSize), 2); + // bound min/max interval counts + minIntervalCount = Math.Max(minIntervalCount, 0); + maxIntervalCount = Math.Min(maxIntervalCount, Math.Max((int)(availableSize / maxIntervalSize), 2)); while (true) { - if (range / interval < maxNumberOfIntervals) + if (range / interval < maxIntervalCount) { break; } @@ -356,6 +353,11 @@ protected override double CalculateActualInterval(double availableSize, double m nextInterval = interval * 2; } + if (range / nextInterval < minIntervalCount) + { + break; + } + interval = nextInterval; } @@ -400,13 +402,13 @@ protected override double CalculateActualInterval(double availableSize, double m if (this.actualIntervalType == DateTimeIntervalType.Months) { double monthsRange = range / 30.5; - interval = this.CalculateActualInterval(availableSize, maxIntervalSize, monthsRange); + interval = this.CalculateActualInterval(availableSize, maxIntervalSize, monthsRange, this.MinimumMajorIntervalCount, this.MaximumMajorIntervalCount); } if (this.actualIntervalType == DateTimeIntervalType.Years) { double yearsRange = range / 365.25; - interval = this.CalculateActualInterval(availableSize, maxIntervalSize, yearsRange); + interval = this.CalculateActualInterval(availableSize, maxIntervalSize, yearsRange, this.MinimumMajorIntervalCount, this.MaximumMajorIntervalCount); } if (this.actualMinorIntervalType == DateTimeIntervalType.Auto) diff --git a/Source/OxyPlot/Axes/TimeSpanAxis.cs b/Source/OxyPlot/Axes/TimeSpanAxis.cs index 937f23c58..5a5e8a121 100644 --- a/Source/OxyPlot/Axes/TimeSpanAxis.cs +++ b/Source/OxyPlot/Axes/TimeSpanAxis.cs @@ -78,25 +78,22 @@ protected override string FormatValueOverride(double x) return string.Format(this.ActualCulture, fmt, span); } - /// - /// Calculates the actual interval. - /// - /// Size of the available area. - /// Maximum length of the intervals. - /// The calculate actual interval. - protected override double CalculateActualInterval(double availableSize, double maxIntervalSize) + /// + protected override double CalculateActualInterval(double availableSize, double maxIntervalSize, double minIntervalCount, double maxIntervalCount) { double range = Math.Abs(this.ClipMinimum - this.ClipMaximum); double interval = 1; var goodIntervals = new[] { 1.0, 5, 10, 30, 60, 120, 300, 600, 900, 1200, 1800, 3600 }; - int maxNumberOfIntervals = Math.Max((int)(availableSize / maxIntervalSize), 2); + // bound min/max interval counts + minIntervalCount = Math.Max(minIntervalCount, 0); + maxIntervalCount = Math.Min(maxIntervalCount, Math.Max((int)(availableSize / maxIntervalSize), 2)); while (true) { - if (range / interval < maxNumberOfIntervals) + if (range / interval < maxIntervalCount) { - return interval; + break; } double nextInterval = goodIntervals.FirstOrDefault(i => i > interval); @@ -105,8 +102,15 @@ protected override double CalculateActualInterval(double availableSize, double m nextInterval = interval * 2; } + if (range / nextInterval < minIntervalCount) + { + break; + } + interval = nextInterval; } + + return interval; } } }