开发者

How to create a slider with a non-linear scale?

I have a slider with a minimum value of 0 and maximum of 500.

I want to when the slider goes to 100, t开发者_JAVA技巧he thumb be in the middle of the slider.

I know it seems wierd, but some programs do it with zoom slider, and I believe it's better.


A good formula for the displayed value is a monotonous function such as a power curve, in the following form:

DisplayValue = A + B * Math.Exp(C * SliderValue);

The internal slider value (from 0 to 1 for instance) is obtained by inverting the formula:

SliderValue = Math.Log((DisplayValue - A) / B) / C;

Now how to obtain A, B and C? By using the three constraints you gave:

f(0.0) = 0
f(0.5) = 100
f(1.0) = 500

Three equations, three unknowns, this is solved using basic maths:

A + B = 0
A + B exp(C * 0.5) = 100
A + B exp(C) = 500

B (exp(C * 0.5) - 1) = 100
B (exp(C) - 1) = 500

exp(C) - 5 exp(C * 0.5) + 4 = 0  // this is a quadratic equation

exp(C * 0.5) = 4

C = log(16)
B = 100/3
A = -100/3

Yielding the following code:

double B = 100.0 / 3;
double C = Math.Log(16.0);
DisplayValue = B * (Math.Exp(C * SliderValue) - 1.0);

You can see that the display value is at 100 when the internal value is in the middle:

How to create a slider with a non-linear scale?

Edit: since a generic formula was requested, here it is. Given:

f(0.0) = x
f(0.5) = y
f(1.0) = z

The values for A, B and C are:

A = (xz - y²) / (x - 2y + z)
B = (y - x)² / (x - 2y + z)
C = 2 * log((z-y) / (y-x))

Note that if x - 2y + z or y - x is zero, there is no solution and you’ll get a division by zero. That’s because in this case, the scale is actually linear. You need to take care of that special case.


let the slider as it is and use a ValueConverter for your bindings. In the ValueConverter use the non-linear scaling to scale the value as you wish.


Just as a further reference; if you are not interested on exact positions for your slider to correspond to specific values in your scale but still want a behavior where the slider is more sensitive to values on the beginning of the scale than on the end, then perhaps using a simple log scale may suffice.

public class LogScaleConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double x = (int)value;
        return Math.Log(x);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double x = (double)value;
        return (int)Math.Exp(x);
    }
}


Based on the algorithm from sam hocevar, here is the code I put together :

/// <summary>
/// Scale a linear range between 0.0-1.0 to an exponential scale using the equation returnValue = A + B * Math.Exp(C * inputValue);
/// </summary>
/// <param name="inoutValue">The value to scale</param>
/// <param name="midValue">The value returned for input value of 0.5</param>
/// <param name="maxValue">The value to be returned for input value of 1.0</param>
/// <returns></returns>
private double ExpScale(double inputValue, double midValue, double maxValue)
{
  double returnValue = 0;
  if (inputValue < 0 || inputValue > 1) throw new ArgumentOutOfRangeException("Input value must be between 0 and 1.0");
  if (midValue <= 0 || midValue >= maxValue) throw new ArgumentOutOfRangeException("MidValue must be greater than 0 and less than MaxValue");
  // returnValue = A + B * Math.Exp(C * inputValue);
  double M = maxValue / midValue;
  double C = Math.Log(Math.Pow(M - 1, 2));
  double B = maxValue / (Math.Exp(C) - 1);
  double A = -1 * B;
  returnValue = A + B * Math.Exp(C * inputValue);
  return returnValue;
}


This was such an interesting question that I couldn't leave it alone, and hopefully I got what you're asking right :)

You want to change the Value of a Slider from Linear to a Quadratic Function by specifying the Y value of the function when the Thumb is in the middle.

A Quadratic Function is written on the form
formula

Since we have 3 points, we have 3 sets of values for X and Y.

(X1, Y1) = 0, 0  
(X2, Y2) = MiddleX, CenterQuadraticValue (in your case 100)  
(X3, Y3) = Maximum, Maximum (in your case 500)

From here, we can create a Quadratic Equation (see this link for example) which comes out to
formula

Unfortunately, some values in this graph ends up below 0 so they will have to be coerced to 0 (I included a graph in the bottom of the answer).

I created a control, QuadraticSlider, which derives from Slider and adds two Dependency Properties: QuadraticValue and CenterQuadraticValue. It calculates QuadraticValue using the formula above based on Value, Maximum, Minimum and CenterQuadraticValue. It also does the reverse: setting QuadraticValue updates Value. So instead of Binding to Value, bind to QuadraticValue.

Edit: The last version was a little buggy. Fixed a couple of things

  • Calculating Value from QuadraticValue no longer breaks when "a" is 0
  • Used wrong root from the second degree solution when the derivate was negative

I uploaded a sample application where QuadraticSlider is used to zoom a picture. All parameteres can be specified and the first picture uses Value and the other QuadraticValue.

Download it here if you want to try it out.

It looks like this
enter image description here

And this is what the graph looks like, notice the values below 0

How to create a slider with a non-linear scale?


Some addition to Meleak's post. I've slightly corrected QuadraticSlider. There was issue with event handlers (event on QuadraticValueChanged with yet prevoius value; event during initialization with out of range [min, max] value).

protected override void OnValueChanged(double oldValue, double newValue)
{
    QuadraticValue = a * Math.Pow(Value, 2) + b * Value + c;
    base.OnValueChanged(oldValue, newValue);
}

public double QuadraticValue
{
    get {
        var qv = (double)GetValue(QuadraticValueProperty);
        if (double.IsNaN(qv))
            qv = 0;
        qv = Math.Max(qv, base.Minimum);
        qv = Math.Min(qv, base.Maximum);
        return qv;
    }
    set 
    {
        SetValue(QuadraticValueProperty, value);
    }
}


To generalise on Sam Hocevar's excellent answer:

Let the intended Maximum value be M.
Let the value at the slider midpoint be m.
(obviously, 0 < m < M), then

    A = - M*m^2 / (M^2 - 2*m*M)
    B = M*m^2 / (M^2 - 2*m*M)
    C = Ln((M - m)^2 / m^2) // <- logarithm to the base of e, I always think of 'Log' as base 10

One must take care to treat the case 2*m=M seperately, because that leads to a division by 0. But in that case, you'd have the slider behave in a linear fashion anyway.

Chosing m from between M/2 and M makes for a logarithmic curve: The effective slider values rise fast at first, then slowly later on. This basically reverses the effect and gives the user finer control of the higher values.

As mentioned, an m close to M/2 makes the slider basically linear.
Choosing m close to 0 or close to M makes for fine control over the very low or the very high values.

I suppose one could use this in combination with a second slider that sets m to a value between 0 and M to change the ... errr ... sensitive zone of the real slider.


Not nearly as elegant as some of the other answers here but instead of a smooth curve you could just use two straight lines. With y as your slider's position and x as your actual value, you'd use (y1 - y2)/(x1 - x2) to get the slope, then y - y1 = slope * (x - x1) to get the y intercept. Do that for each half of the slider then use a conditional statement to treat values differently depending on whether they are above or below 50. The basic maths in your case:

(0 - 50)/(0 - 100)
-50/-100
1/2
y = x/2

...since we know the y intercept is 0. Then...

(50-100)/(100-500)
-50/-400
1/8
y - 50 = 1/8 * (x - 100)
y - 50 = x/8 - 25/2
y = x/8 + 75/2

From a user experience perspective, you're definitely better off using a curve (I especially like Cesar's log idea) because it avoids a sharp "elbow" where the slider starts suddenly increasing values at a totally different rate. But this approach is simple to implement and flexible (you could easily add more zones with different ratios) so I thought it was worth mentioning it anyway.


Here is the go code for deriving the exponential equation: https://play.golang.org/p/JlWlwZjoebE

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜