/// <summary> /// 1) Calculate the step value based on MinValue, MaxValue, DisplayRange and availableHeight. /// 2) Width is calculated based on fontSize and number of digits needed to display MinValue & MaxValue. /// </summary> private double calculateLegendWidth(double availableHeight) { //calculateLegendWidth() is called from within Measure and Arrange. Legend guarantees that MinValue, DisplayValue, DisplayValueRange and //MaxValue have reasonable values if (!hasFontChanged && heightTracked == availableHeight && !HasMinMaxOrDisplayValueRangeChanged && !double.IsNaN(requiredWidth)) { return(requiredWidth);//nothing has changed } isNewWidthCalculated = true; hasFontChanged = false; heightTracked = availableHeight; //calculate step value between 2 legend values //-------------------------------------------- //value between 2 display labels (=step) double estimatedStepValue = DisplayValueRange * FontSize * 3 / availableHeight; if (estimatedStepValue < 1000 * double.Epsilon) { throw new ApplicationException(string.Format("Legend: range '{0}' too close to zero." + this, estimatedStepValue)); } //normalise amplitude between 1.0 and 10.0 DoubleDigitsExponent stepDigitsExponent = new DoubleDigitsExponent(estimatedStepValue); if (stepDigitsExponent.Digits < 1.00 || stepDigitsExponent.Digits >= 10.00) { throw new ApplicationException(string.Format("Legend: Normalised stepValue should be between 1.0 and 9.999, but was '{0}'.", stepDigitsExponent.Digits)); } if (stepDigitsExponent.Digits < 1.00) { step = new StepStruct(1, stepDigitsExponent.Exponent); } else if (stepDigitsExponent.Digits < 2.00) { step = new StepStruct(2, stepDigitsExponent.Exponent); } else if (stepDigitsExponent.Digits < 5.00) { step = new StepStruct(5, stepDigitsExponent.Exponent); } else { step = new StepStruct(1, stepDigitsExponent.Exponent + 1); } //calculate string format. It depends on: //--------------------------------------- //1) biggest and smallest value in Min-/MaxValue Range //2) step size in DisplayValue Range //find maximum number of characters needed to display longest Value double minValue; if (double.IsNaN(MinValue)) { minValue = DisplayValue; } else { minValue = MinValue; } //DoubleDigitsExponent minValueDigitsExponent = new DoubleDigitsExponent(minValue); double maxValue; if (double.IsNaN(MaxValue)) { maxValue = DisplayValue + DisplayValueRange; } else { maxValue = MaxValue; } //DoubleDigitsExponent maxValueDigitsExponent = new DoubleDigitsExponent(maxValue); //int minExponent; //int maxExponent; //if (minValueDigitsExponent.Exponent<=maxValueDigitsExponent.Exponent) { // //Example: Min=0.1, Max = 10000 // minExponent = minValueDigitsExponent.Exponent; // maxExponent = maxValueDigitsExponent.Exponent; //} else { // //Example: Min=-10000, Max = 0.1 // minExponent = maxValueDigitsExponent.Exponent; // maxExponent = minValueDigitsExponent.Exponent; //} numberFormat = "#,0"; if (step.Exponent < 0) { //add required digits after decimal point numberFormat += '.' + new string('0', -step.Exponent); } string minValueString = minValue.ToString(numberFormat); double minValueStringWidth = LegendGlyphDrawer.GetLength(minValueString, FontSize); string maxValueString = maxValue.ToString(numberFormat); double maxValueStringWidth = LegendGlyphDrawer.GetLength(maxValueString, FontSize); requiredWidth = Math.Max(minValueStringWidth, maxValueStringWidth); return(requiredWidth); }
protected override void OnRecalculate(ref double[]?labelValues, ref string?[]?labelStrings, ref Point[]?labelPoints) { //find first which label will need the most digits. Use MinValue or MaxValue, not DisplayValue and range, because the format //should stay the same for all values between min to max. double lowestValue; double highestValue; if (double.IsNaN(MinValue) || double.IsNaN(MaxValue)) { //Min- and MaxValue are not defined. Use DisplayValue and DisplayValueRange instead lowestValue = DisplayValue; highestValue = lowestValue + DisplayValueRange; //DisplayValueRange is guaranteed to be greater 0 } else { //Min- and MaxValue are defined, use them. lowestValue = MinValue; highestValue = MaxValue; } //use DisplayValueRange as first estimate for value difference between 2 labels. //normalise amplitude between 1.0 and 10.0 DoubleDigitsExponent stepDigitsExponent = new DoubleDigitsExponent(DisplayValueRange); #if DEBUG if (stepDigitsExponent.Digits < 1.00 || stepDigitsExponent.Digits >= 10.00) { System.Diagnostics.Debugger.Break(); throw new Exception(string.Format("Legend: Normalised stepValue should be between 1.0 and 9.999, but was '{0}'.", stepDigitsExponent.Digits)); } #endif //the first digits of a step (=value difference between 2 labels) can be 1, 2 or 5. All other digits of a step are 0. //chose a first step which is smaller than DisplayValueRange StepStruct step; if (stepDigitsExponent.Digits < 1.00) { step = new StepStruct(5, stepDigitsExponent.Exponent - 1); } else if (stepDigitsExponent.Digits < 2.00) { step = new StepStruct(1, stepDigitsExponent.Exponent); } else if (stepDigitsExponent.Digits < 5.00) { step = new StepStruct(2, stepDigitsExponent.Exponent); } else { step = new StepStruct(5, stepDigitsExponent.Exponent); } string minMaxnumberFormat = getNumberFormat(step); //find the label which needs the most digits //MinValue might need more digits, because it can be negative (-10000) and Max might be a small number (0). //convert smallest and highest value to string string lowestValueString = lowestValue.ToString(minMaxnumberFormat); string highestValueString = highestValue.ToString(minMaxnumberFormat); //select longer string. string longestValueString; double longestStringValue; if (lowestValueString.Length > highestValueString.Length) { longestValueString = lowestValueString; longestStringValue = lowestValue; } else { longestValueString = highestValueString; longestStringValue = highestValue; } //DisplayRange provides a first estimate for the distance between 2 labels (=step) //step gets made smaller and smaller, until the space needed to display the label is bigger than the space available between //2 labels string? numberFormat = null; double labelWidth = double.NaN; double pixelPerValue = RenderWidthTracked / DisplayValueRange; StepStruct previousStep = step; string longestValuePlusSpace = longestValueString + " "; while (true) { string newNumberFormat = getNumberFormat(step); longestValueString = longestStringValue.ToString(newNumberFormat); labelWidth = LegendGlyphDrawer.GetLength(longestValuePlusSpace, FontSize); if (labelWidth > step.Value * pixelPerValue) { //not enough space for new format. Use previous step and its format. //if there is not enough space for even 1 label: we come here first time going through the loop. step is already //equal to previousStep. Assigning it again doesn't hurt. Even step is in this case not a meaningful value, //it is ok, because the dingle label displayed will not use step for formatting. step = previousStep; break; } numberFormat = newNumberFormat; //try next smaller step previousStep = step; if (step.FirstDigit == 1) { step = new StepStruct(5, step.Exponent - 1); } else if (step.FirstDigit == 2) { step = new StepStruct(1, step.Exponent); } else if (step.FirstDigit == 5) { step = new StepStruct(2, step.Exponent); } else { throw new Exception("Illegal FirstDigit of step: " + step + ". It should be 1, 2 or 5."); } } //number of labels that can be displayed in window int estimatedLabelCount; if (numberFormat == null) { //not enough space to display even 1 label properly. estimatedLabelCount = 1; } else { #if DEBUG if (double.IsNaN(labelWidth)) { System.Diagnostics.Debugger.Break(); throw new Exception("labelWidth cannot be NaN."); } #endif estimatedLabelCount = (int)(RenderWidthTracked / labelWidth); } if (estimatedLabelCount <= 1) { //only 1 or even only the part of a label can be displayed. In this case it is better to just display the //very first value and not trying to find the first nicely rounded step-number if (labelValues == null || labelValues.Length != 1) { labelValues = new double[1]; labelStrings = new string[1]; labelPoints = new Point[1]; } labelValues[0] = DisplayValue; labelStrings ![0] = DisplayValue.ToString();