@@ -20,6 +20,12 @@ namespace OxyPlot.Axes
2020 /// <see href="http://en.wikipedia.org/wiki/Logarithmic_scale"/>
2121 public class LogarithmicAxis : Axis
2222 {
23+ /// <summary>
24+ /// The lowest value that can be transformed to logarithmic and
25+ /// back yielding the same non-infinite number.
26+ /// </summary>
27+ public static readonly double LowestValidRoundtripValue = 2.22507385850726E-308 ;
28+
2329 /// <summary>
2430 /// Initializes a new instance of the <see cref = "LogarithmicAxis" /> class.
2531 /// </summary>
@@ -633,5 +639,86 @@ protected override void CoerceActualMaxMin()
633639
634640 base . CoerceActualMaxMin ( ) ;
635641 }
642+
643+ /// <summary>
644+ /// Calculates the actual maximum value of the axis, including the <see cref="Axis.MaximumPadding" />.
645+ /// </summary>
646+ /// <returns>The new actual maximum value of the axis.</returns>
647+ /// <remarks>
648+ /// Must be called before <see cref="CalculateActualMinimum" />
649+ /// </remarks>
650+ protected override double CalculateActualMaximum ( )
651+ {
652+ var actualMaximum = this . DataMaximum ;
653+ double range = this . DataMaximum - this . DataMinimum ;
654+
655+ if ( range < double . Epsilon )
656+ {
657+ double zeroRange = this . DataMaximum > 0 ? this . DataMaximum : 1 ;
658+ actualMaximum += zeroRange * 0.5 ;
659+ }
660+
661+ if ( ! double . IsNaN ( this . DataMinimum ) && ! double . IsNaN ( actualMaximum ) )
662+ {
663+ // On log axis we actually see log(x). Now if we want to have a padding we need to change actualMaximum
664+ // to an x value that corresponds to the padding expected in log scale
665+ // x_0 x_1 x_2
666+ // |---------------------|--------|
667+ // log(x_0) log(x_1) log(x_2)
668+ // where actualMaximum = x_2
669+ //
670+ // log(x_2) - log(x_1) = padding * [log(x_1) - log(x_0)]
671+ // log(x_2) = padding * log(x_1/x_0) + log(x_1)
672+ // x_2 = (x_1/x_0)^padding * x_1
673+
674+ double x1 = actualMaximum ;
675+ double x0 = this . DataMinimum ;
676+ return Math . Pow ( x1 , this . MaximumPadding + 1 ) * ( x0 > double . Epsilon ? Math . Pow ( x0 , - this . MaximumPadding ) : 1 ) ;
677+ }
678+
679+ return actualMaximum ;
680+ }
681+
682+ /// <summary>
683+ /// Calculates the actual minimum value of the axis, including the <see cref="Axis.MinimumPadding" />.
684+ /// </summary>
685+ /// <returns>The new actual minimum value of the axis.</returns>
686+ /// <remarks>
687+ /// Must be called after <see cref="CalculateActualMaximum" />
688+ /// </remarks>
689+ protected override double CalculateActualMinimum ( )
690+ {
691+ var actualMinimum = this . DataMinimum ;
692+ double range = this . DataMaximum - this . DataMinimum ;
693+
694+ if ( range < double . Epsilon )
695+ {
696+ double zeroRange = this . DataMaximum > 0 ? this . DataMaximum : 1 ;
697+ actualMinimum -= zeroRange * 0.5 ;
698+ }
699+
700+ if ( ! double . IsNaN ( this . ActualMaximum ) )
701+ {
702+ // For the padding on the min value it is very similar to the calculation mentioned in
703+ // CalculateActualMaximum. However, since this is called after CalculateActualMaximum,
704+ // we no longer know x_1.
705+ // x_3 x_0 x_1 x_2
706+ // |---------|------------|--------|
707+ // log(x_3) log(x_0) log(x_1) log(x_2)
708+ // where actualMiminum = x_3
709+ // log(x_0) - log(x_3) = padding * [log(x_1) - log(x_0)]
710+ // from CalculateActualMaximum we can use
711+ // log(x_1) = [log(x_2) + max_padding * log(x_0)] / (1 + max_padding)
712+ // x_3 = x_0^[1 + padding - padding * max_padding / (1 + max_padding)] * x_2^[-padding / (1 + max_padding)]
713+
714+ double x1 = this . ActualMaximum ;
715+ double x0 = actualMinimum ;
716+ double existingPadding = this . MaximumPadding ;
717+ return Math . Pow ( x0 , 1 + this . MinimumPadding - this . MinimumPadding * existingPadding / ( 1 + existingPadding ) ) * Math . Pow ( x1 , - this . MinimumPadding / ( 1 + existingPadding ) ) ;
718+ }
719+
720+ return actualMinimum ;
721+ }
722+
636723 }
637724}
0 commit comments