Подтвердить что ты не робот

Как создать слайдер с нелинейным масштабом?

У меня есть ползунок с минимальным значением 0 и максимум 500.

Я хочу, когда ползунок переходит на 100, большой палец будет посредине ползунка.

Я знаю, что это кажется странным, но некоторые программы делают это с помощью слайдера масштабирования, и я считаю, что это лучше.

4b9b3361

Ответ 1

Это был такой интересный вопрос, что я не мог оставить его в покое, и, надеюсь, я получил то, о чем вы спрашиваете:)

Вы хотите изменить Value от Slider от линейной до квадратичной функции, указав значение Y функции, когда Thumb находится посередине.

Квадратичная функция записывается на форме formula

Так как мы имеем 3 точки, мы имеем 3 набора значений для X и Y.

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

Отсюда мы можем создать квадратичное уравнение (см. эту ссылку, например), которая выходит на formula

К сожалению, некоторые значения в этом графе заканчиваются ниже 0, поэтому их нужно будет принудить к 0 (я включил график в нижней части ответа).

Я создал элемент управления QuadraticSlider, который происходит от Slider и добавляет два свойства зависимостей: QuadraticValue и CenterQuadraticValue. Он вычисляет QuadraticValue с использованием приведенной выше формулы на основе Value, Maximum, Minimum и CenterQuadraticValue. Он также делает обратное: установка QuadraticValue обновлений Value. Поэтому вместо привязки к Value привязывается к QuadraticValue.

Изменить: Последняя версия была немного ошибкой. Исправлено несколько вещей

  • Вычисление Value из QuadraticValue больше не прерывается, когда "a" равно 0
  • Использовал неправильный корень из решения второй степени, когда деривация была отрицательной.

Я загрузил пример приложения, где QuadraticSlider используется для увеличения изображения. Все параметры могут быть указаны, и первое изображение использует Value, а другое QuadraticValue.

Загрузите его здесь, если вы хотите попробовать.

Похоже, что это enter image description here

И вот как выглядит график, обратите внимание на значения ниже 0

enter image description here

Ответ 2

Хорошей формулой для отображаемого значения является монотонная функция, такая как кривая мощности, в следующем виде:

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

Внутреннее значение ползунка (например, от 0 до 1) получается путем инвертирования формулы:

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

Теперь, как получить A, B и C? Используя три ограничения, которые вы указали:

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

Три уравнения, три неизвестных, это решаются с использованием основных математических данных:

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

Используя следующий код:

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

Вы можете видеть, что отображаемое значение равно 100, когда внутреннее значение находится посередине:

final curve

Изменить: поскольку запрошена общая формула, вот оно. Дано:

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

Значения для A, B и C:

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

Обратите внимание, что если x - 2y + z равно нулю, нет решения, и вы получите деление на ноль. То потому что в этом случае шкала фактически линейна. Вам необходимо позаботиться об этом специальном случае.

Ответ 3

пусть ползунок как есть, и используйте ValueConverter для ваших привязок. В ValueConverter используйте нелинейное масштабирование для масштабирования значения по вашему желанию.

Ответ 4

Как дополнительная ссылка; если вам не нужны точные позиции для вашего ползунка, соответствующие конкретным значениям в вашем масштабе, но все же хотите, чтобы поведение, при котором ползунок более чувствителен к значениям в начале шкалы, чем на конце, возможно, используя простой масштаб журнала может быть достаточно.

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);
    }
}

Ответ 5

Некоторое дополнение к сообщению Meleak. Я немного исправил QuadraticSlider. Возникла проблема с обработчиками событий (событие на QuadraticValueChanged со значением prevoius; событие во время инициализации с пределами диапазона [min, max]).

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);
    }
}

Ответ 6

На основе алгоритма из sam hocevar, вот код, который я собрал:

/// <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;
}

Ответ 7

Обобщить Сэма Хочевара отличный ответ:

Пусть заданное максимальное значение равно M.
Пусть значение в средней точке слайдера равно m.
(очевидно, 0 < m < M), то

    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

Нужно позаботиться о том, чтобы обрабатывать случай 2 * m = M отдельно, потому что это приводит к делению на 0. Но в любом случае вы должны иметь слайдер в линейном режиме.

Выбор m из M/2 и M приводит к логарифмической кривой: эффективные значения ползунка сначала повышаются быстро, а затем медленно. Это в основном отменяет эффект и дает пользователю более точное управление более высокими значениями.

Как уже упоминалось, m близко к M/2 делает слайдер в основном линейным.
Выбор m, близкого к 0 или близкого к M, позволяет точно контролировать очень низкие или очень высокие значения.

Я предполагаю, что можно использовать это в сочетании со вторым слайдером, который устанавливает m в значение от 0 до M, чтобы изменить... errr... чувствительную зону реального ползунка.