/// <summary> /// Implements logic for splitting the interval defined by the <paramref name="valueRange" /> into smaller intervals (ranges) using logarithmic distribution. /// The base of the logarithm is the <see cref="LogarithmBase"/> property. /// </summary> /// <remarks> /// <para> /// The <paramref name="valueRange"/> is mapped to a made-up interval that is separated into <paramref name="count"/> sub-ranges as follows: /// b^0, b^1, ... , b^<paramref name="count"/>. /// The actual boundary (upper) value of a range with index i is defined from the following equality: /// </para> /// <para> /// actualBoundaryValue = (max - min) * ratio + min /// </para> /// <para> /// where /// </para> /// <para> /// ratio = (base^i - 1)/(base^count - 1) /// </para> /// <para> /// and min and max are <see cref="T:ValueRange.Minimum"/> and <see cref="T:ValueRange.Maximum"/> of the <paramref name="valueRange" />. /// </para> /// <para> /// This ratio is sensitive to the power values. The <see cref="CalculateMaxPowerForRangeLengthPrecision"/> method returns a value that defines the maximum power x, defined by the following inequality: /// </para> /// <para> /// b^x <= 10^10 /// </para> /// <para> /// This power defines the smallest possible width of a range. /// </para> /// </remarks> /// <param name="valueRange">The full range that will be divided.</param> /// <param name="count">The count of the generated ranges.</param> protected internal override IEnumerable <ColorRange> BuildRanges(ValueRange <double> valueRange, int count) { this.ranges.Clear(); double delta = valueRange.maximum - valueRange.minimum; if (delta <= 0) { yield break; } double startValue = valueRange.minimum; double max = 0d; if (this.logBaseCache == 1) { var step = delta / count; foreach (var colorRange in LinearRangeDistribution.BuildLinearRanges(valueRange, count, step)) { yield return(colorRange); } } else { var context = new RatioCalculationContext() { MaxPower = this.CalculateMaxPowerForRangeLengthPrecision(), RangeCount = count }; for (int i = 1; i <= count; i++) { if (i == count) { max = valueRange.maximum; } else { var ratio = this.logBaseCache > 1 ? this.CalculateRatio(i, context) : this.CalculateRatioForLogBaseBetweenZeroAndOne(i, context); max = valueRange.minimum + delta * ratio; } var range = new ColorRange() { Min = startValue, Max = max, Index = i - 1 }; this.ranges.Add(range); startValue = max; yield return(range); } } }
// calculates (1 - b^i)/(1 - b^n) // 0 < b < 1 // *in this case i = n-i -> reversed order of the intervals internal double CalculateRatioForLogBaseBetweenZeroAndOne(int index, RatioCalculationContext context) { var count = context.RangeCount; var maxPower = context.MaxPower; var logBase = this.logBaseCache; if (count > maxPower) { if (index > maxPower) { //// 1/1 return(1); } else { if (context.BaseToPowerOfCurrentIndex == 0) { //// 1-b^i context.BaseToPowerOfCurrentIndex = Math.Pow(logBase, index); } else { ////b^i context.BaseToPowerOfCurrentIndex *= logBase; } return(1 - context.BaseToPowerOfCurrentIndex); } } else { context.BaseToPowerOfCurrentIndex *= logBase; if (context.BaseToPowerOfCount == 0) { context.BaseToPowerOfCount = Math.Pow(logBase, count); } return((1 - context.BaseToPowerOfCurrentIndex) / (1 - context.BaseToPowerOfCount)); } }
// calculates (b^i - 1)/(b^n - 1) // 1 < b internal double CalculateRatio(int index, RatioCalculationContext context) { var count = context.RangeCount; var maxPower = context.MaxPower; var logBase = this.logBaseCache; if (count > maxPower) { if (count - index > maxPower) { //// 1/inf return(0); } else { if (context.BaseToPowerOfCount == 0) { //// b^(i-n) context.BaseToPowerOfCount = Math.Pow(logBase, index - count); } else { //// b^(i-n) context.BaseToPowerOfCount *= logBase; } return(context.BaseToPowerOfCount); } } else { context.BaseToPowerOfCurrentIndex *= logBase; if (context.BaseToPowerOfCount == 0) { context.BaseToPowerOfCount = Math.Pow(logBase, count); } return((context.BaseToPowerOfCurrentIndex - 1) / (context.BaseToPowerOfCount - 1)); } }