1
Vote

Gauge behavior on negative numbers is inaccurate

description

Consider, you set a negative number in Value property, you will see the following:
Image

I believe that behavior should be consistent. When we increase value (not absolute), the scale and needle direction should be clock-wise. And when we decrease value, the scale and needle direction should be opposite.
That is, -1 is larger than -10, so in a given example, the minimum scale value (-10) should be on a left side and 0 on the right:
Image

I worked out this issue by updating the code in the UpdateAngle() method as follows:
internal void UpdateAngle() {
    if (Value == Double.NegativeInfinity) {
        NeedleRotateTransform.Angle = StartAngle - 10;
        NeedleDropShadowEffect.Direction = StartAngle - 10;
        DrawElements();
        return;
    }

    if (AutoScale) {
        MaxValue = GetGreatestValue().GetGaugeTop();
        MinValue = 0;
    }

    Double valueInPercent = AutoScale ? valueInPercent = Value / MaxValue
                                        : valueInPercent = (Value - MinValue) / (MaxValue - MinValue);
    Double valueInDegrees;
    if (Value < 0) {
        valueInDegrees = AutoScale ? EndAngle - valueInPercent * (EndAngle - StartAngle)
                                    : StartAngle + valueInPercent * (EndAngle - StartAngle);
    } else {
        valueInDegrees = StartAngle + valueInPercent * (EndAngle - StartAngle);
    }

    if (!AutoScale) { ValueText.Foreground = Brushes.LimeGreen; }
    if (!AutoScale && valueInPercent < 0) {
        valueInDegrees = StartAngle - 10;
        ValueText.Foreground = Brushes.Red;
    }
    if (!AutoScale && valueInPercent > 1) {
        valueInDegrees = EndAngle + 10;
        ValueText.Foreground = Brushes.Red;
    }


    NeedleRotateTransform.Angle = valueInDegrees;
    NeedleDropShadowEffect.Direction = valueInDegrees;

    DrawElements();
}
I changed calculation logic when the value is negative. This ensures that the needle will move in a right direction when we increase Value property and in a left direction when decrease. Also, I'm performing additional calculation depending on AutoScale property:
valueInDegrees = AutoScale ? EndAngle - valueInPercent * (EndAngle - StartAngle)
                                    : StartAngle + valueInPercent * (EndAngle - StartAngle);
It is necessary in the case when AutoScale is set to False and MinValue is negative (say, -5) and MaxValue is positive (say, +5). In this case, zero will be in the middle. This condition makes behavior consistent in all cases.

Also, I changed DrawElement() method to draw correct scale for negative values (numbers are placed from right to left):
internal void DrawElements() {
            // TODO: Improve this...
            // First, clear any children that may exist in the grid
            DynamicLayout.Children.Clear();

            // There are no specified Major Tick Markers so auto-create them
            // The default number of  Major Tick Markers is 11 (0 - 10)
            // The total degrees of the scale
            Double totalDegrees = EndAngle - StartAngle;
            Double degreeIncrement = totalDegrees / (MajorTickCount);
            Double labelIncrement = (MaxValue - MinValue) / (MajorTickCount);
            Double smallDegreeIncrement = degreeIncrement / (MinorTickCount + 1);

            MajorTickMarks = new ObservableCollection<TickMarker>();
            for (int i = 0; i <= MajorTickCount; i++) {
                TickMarker majorTick = new TickMarker(true);
                Panel.SetZIndex(majorTick, 1);
                if (labelIncrement < 0) {
                    majorTick.Angle = EndAngle - (i * degreeIncrement);
                } else {
                    majorTick.Angle = StartAngle + (i * degreeIncrement);
                }
                if (AutoScale) {                    
                    majorTick.Label = (i * labelIncrement).ToString();
                } else {
                    majorTick.Label = ((i * labelIncrement) + MinValue).ToString();
                }
                MajorTickMarks.Add(majorTick);
                DynamicLayout.Children.Add(majorTick);

                majorTick.TickMarkColor = MajorTickMarkColor;
                majorTick.LineMargin = LineMargin;
                majorTick.LabelMargin = LabelMargin;

                if (TickLabelStyle != null) {
                    majorTick.TickLabelStyle = TickLabelStyle;
                }
            }
            MinorTickMarks = new ObservableCollection<TickMarker>();

            foreach (var majorTick in MajorTickMarks) {
                if (MajorTickMarks.IndexOf(majorTick) < MajorTickMarks.Count - 1) {
                    for (int i = 0; i <= this.MinorTickCount; i++) {
                        TickMarker minorTick = new TickMarker(false);
                        Panel.SetZIndex(minorTick, 0);
                        if (labelIncrement < 0) {
                            minorTick.Angle = majorTick.Angle - (i * smallDegreeIncrement) - smallDegreeIncrement;
                        } else {
                            minorTick.Angle = majorTick.Angle + (i * smallDegreeIncrement) + smallDegreeIncrement;
                        }
                        this.MinorTickMarks.Add(minorTick);
                        DynamicLayout.Children.Add(minorTick);

                        minorTick.TickMarkColor = this.MinorTickMarkColor;
                        minorTick.LineMargin = this.LineMargin;
                        minorTick.LabelMargin = this.LabelMargin;
                    }
                }
            }
            currentMinMark = Convert.ToDouble(MajorTickMarks[0].Label);
            currentMaxMark = Convert.ToDouble(MajorTickMarks[MajorTickMarks.Count - 1].Label);
        }
look for "labelIncrement" conditions where I check whether it is positive or negative. I attached my current WpfGauge.xaml.cs file (in zip), so you can check changes and update your code if necessary.

Also, there is a need to improve code performance. For example, you update scale each time when the value is changed. I think, it would be better to track current scale min and max values and update only needle angle when the value is changed in a valid range and redraw the scale only when value is outside this range. I'm working on it and will let you know if I'm succeeded on this.

file attachments

comments

Camelot wrote Nov 12, 2013 at 12:15 PM

A small update. The following piece in the UpdateAngle() method:
if (!AutoScale) { ValueText.Foreground = Brushes.LimeGreen; }
    if (!AutoScale && valueInPercent < 0) {
        valueInDegrees = StartAngle - 10;
        ValueText.Foreground = Brushes.Red;
    }
    if (!AutoScale && valueInPercent > 1) {
        valueInDegrees = EndAngle + 10;
        ValueText.Foreground = Brushes.Red;
    }
This code is not necessary, but may be useful. I'm adapting your gauge to my need (they will represent voltmeters). I'm considering to use AutoScale=False and set Min/MaxValue to a reasonable range I need. If current voltage is in the range, the textual value (in the text block) is Green, when outside -- red. So, this code is just an adoption for electrical measurements.