65

Is it possible to use the JQuery Slider (range slider / dual slider) to have non-linear (non consistent "step" size) values?

I want to horizontal Slider to look like:

|----|----|----|----|----|--------|--------|-------------------------|--------------------------|...
0   500  750  1000  1250 1500     2000     2500                      75000                      100000...

For example, I want to have the following JQuery code:

var values = [0, 500, 750, 1000, 1250, 1500, 2000, 2500, 75000, 100000, 150000, 200000, 250000, 300000, 350000, 400000, 500000, 1000000];
var slider = $("#price-range").slider({
    orientation: 'horizontal',
    range: true,
    min: 0,
    max: 1000000,
    values: [0, 1000000],
    slide: function(event, ui) {
            var includeLeft = event.keyCode != $.ui.keyCode.RIGHT;
            var includeRight = event.keyCode != $.ui.keyCode.LEFT;
            slider.slider('option', 'value', findNearest(includeLeft, includeRight, ui.value));
            $("#price-amount").html('$' + ui.values[0] + ' - $' + ui.values[1]);
            return false;
    },
    change: function(event, ui) { 
        getHomeListings();
    }
});
function findNearest(includeLeft, includeRight, value) {
    var nearest = null;
    var diff = null;
    for (var i = 0; i < values.length; i++) {
            if ((includeLeft && values[i] <= value) || (includeRight && values[i] >= value)) {
                    var newDiff = Math.abs(value - values[i]);
                    if (diff == null || newDiff < diff) {
                            nearest = values[i];
                            diff = newDiff;
                    }
            }
    }
    return nearest;
}

The code above is not exactly working but the snap to grid functionality doesn't work.

12 Answers12

53

Not sure if you want the slider scale to be in proportion to your values* but if so, I provided a solution to this for someone else who asked the same question. You can find my solution here. Basically I make use of the slide event that gets triggered when you move the slider to mimic the stepping, but based off a custom array defining the steps. This way it only allows you to "step" to your predefined values, even if they're not evenly spread.

*In other words if you want your slider to look like this:

|----|----|----|----|----|----|----|
0   10   20  100  1000 2000 10000 20000

then go with one of the other solutions here, but if you want your slider to look like this (diagram not to scale):

|--|--|-------|-----------|-----------|--------------------|--------------------|
0 10 20     100         1000        2000               10000                20000

Then the solution I linked to may be more what you're after.


Edit: Ok, this version of the script should work with dual sliders:

$(function() {
    var values = [0, 500, 750, 1000, 1250, 1500, 2000, 2500, 75000, 100000, 150000, 200000, 250000, 300000, 350000, 400000, 500000, 1000000];
    var slider = $("#price-range").slider({
        orientation: 'horizontal',
        range: true,
        min: 0,
        max: 1000000,
        values: [0, 1000000],
        slide: function(event, ui) {
            var includeLeft = event.keyCode != $.ui.keyCode.RIGHT;
            var includeRight = event.keyCode != $.ui.keyCode.LEFT;
            var value = findNearest(includeLeft, includeRight, ui.value);
            if (ui.value == ui.values[0]) {
                slider.slider('values', 0, value);
            }
            else {
                slider.slider('values', 1, value);
            }
            $("#price-amount").html('$' + slider.slider('values', 0) + ' - $' + slider.slider('values', 1));
            return false;
        },
        change: function(event, ui) { 
            getHomeListings();
        }
    });
    function findNearest(includeLeft, includeRight, value) {
        var nearest = null;
        var diff = null;
        for (var i = 0; i < values.length; i++) {
            if ((includeLeft && values[i] <= value) || (includeRight && values[i] >= value)) {
                var newDiff = Math.abs(value - values[i]);
                if (diff == null || newDiff < diff) {
                    nearest = values[i];
                    diff = newDiff;
                }
            }
        }
        return nearest;
    }
});

Note that it looks a little funny down the far left end, since the jumps are so close together compared to the right hand end, but you can see its stepping as desired if you use your keyboard arrows to move the slider. Only way to get around that is to change your scale to not be quite so drastically exponential.


Edit 2: Ok, if the spacing is too exaggerated when you use the true values, you could use a set of fake values for the slider & then look up the real value this corresponds to when you need to use the real value (in a similar way to what the other solutions here suggested). Here's the code:

$(function() {
    var trueValues = [0, 500, 750, 1000, 1250, 1500, 2000, 2500, 75000, 100000, 150000, 200000, 250000, 300000, 350000, 400000, 500000, 1000000];
    var values =     [0,   1,   2,    3,    4,    5,    6,    7,    10,     15,     20,     25,     30,     40,     50,     60,     75,     100];
    var slider = $("#price-range").slider({
        orientation: 'horizontal',
        range: true,
        min: 0,
        max: 100,
        values: [0, 100],
        slide: function(event, ui) {
            var includeLeft = event.keyCode != $.ui.keyCode.RIGHT;
            var includeRight = event.keyCode != $.ui.keyCode.LEFT;
            var value = findNearest(includeLeft, includeRight, ui.value);
            if (ui.value == ui.values[0]) {
                slider.slider('values', 0, value);
            }
            else {
                slider.slider('values', 1, value);
            }
            $("#price-amount").html('$' + getRealValue(slider.slider('values', 0)) + ' - $' + getRealValue(slider.slider('values', 1)));
            return false;
        },
        change: function(event, ui) { 
            getHomeListings();
        }
    });
    function findNearest(includeLeft, includeRight, value) {
        var nearest = null;
        var diff = null;
        for (var i = 0; i < values.length; i++) {
            if ((includeLeft && values[i] <= value) || (includeRight && values[i] >= value)) {
                var newDiff = Math.abs(value - values[i]);
                if (diff == null || newDiff < diff) {
                    nearest = values[i];
                    diff = newDiff;
                }
            }
        }
        return nearest;
    }
    function getRealValue(sliderValue) {
        for (var i = 0; i < values.length; i++) {
            if (values[i] >= sliderValue) {
                return trueValues[i];
            }
        }
        return 0;
    }
});

You can fiddle with the numbers in the values array (which represent the slider stop points) until you get them spaced out how you want. This way you can make it feel, from the user's perspective, like it's sliding proportionally to the values, but without being as exaggerated. Obviously if your true values are dynamically created, you may need to come up with an algorithm to generate the slider values instead of statically defining them...

Community
  • 1
  • 1
Alconja
  • 14,834
  • 3
  • 60
  • 61
  • 1
    @Alconja - Your bottom example looks great however, I don't believe it works if I use a dual handle (Slider Range). Is that correct, if so - how would I modified your code to make that work? Many thanks –  Jun 09 '09 at 02:05
  • @Alconja - Also, does this work with the "snap to grid" functionality? It's doesn't appear so. –  Jun 09 '09 at 02:41
  • @TomHankers - The "snap to grid" should work (it did when I built the other example), & no it currently doesn't work with dual handles, but I'll have a look at that for you... – Alconja Jun 09 '09 at 03:24
  • @Alconja - Thanks for looking into the dual handle. My code is above in the original post. –  Jun 09 '09 at 03:39
  • @Alconja - wow, thanks for the script. Yeah, you're right - the first few values array # can't be selected b/c they are so small. Would it be possible to invert the distance between the values so that there is more distance between close values and less distance between large difference in values? (So the distance between 0-500 is larger on the slider than the distance been 500,000-1,000,000 –  Jun 09 '09 at 05:14
  • @TomHankers - geez, you don't ask for much do you. :) ...see my latest edit. – Alconja Jun 09 '09 at 07:36
  • Looks like there is a small issue with the right-hand slider handle in your code. The left handle can traverse the full range of the slider, but the right handle stops 1 increment short of the minimum value. – Mysterei Feb 22 '11 at 15:02
  • @Alconja - do you have your prior version, with a fixed minimum range slider? I'm trying to get this to work with only one slider handle, and no matter how I change it, I keep getting dual sliders – Jason Dec 22 '11 at 21:37
  • @Jason - Did you mean like [my original example](http://stackoverflow.com/questions/681303) or did you need the scaled `realValues` vs `values` modifications? If so, you can get the above working as a single slider by removing a bunch of code.. basically remove everything in the `.slider({ ... })` initialisation except the `slide` function, and within that, you just want the first three lines and then `slider.slider('value', value); $("#price-amount").html('$' + getRealValue(value)); return false;`... if that doesn't make sense, let me know & I'll post a full edit above. – Alconja Dec 26 '11 at 11:36
  • @Alconja - thanks for the response - here's what I'm trying to do: http://stackoverflow.com/questions/8610276/jquery-ui-slider-non-linear-step-values-for-range-fixed-minimum - similar to above, where I need to step in a non-linear fashion with a fixed ranged slider. I also need a minimum of 5 instead of 0... – Jason Dec 26 '11 at 16:23
  • @Alconja - I have implemented your other answer, but the increments are too small at my low end - so I really need your answer above reworked for a min-range slider. Thanks! – Jason Dec 27 '11 at 14:39
  • @Alconja - ok, got it working - mostly. I have a min value that I don't want the slider to go beyond (which is working), but I also have a maximum, which does only return the max value, but the slider sitll moves. – Jason Dec 27 '11 at 15:34
  • @Alconja link to max value issue - http://stackoverflow.com/questions/8646796/jquery-ui-slider-max-value-not-end-of-slider – Jason Dec 27 '11 at 17:15
14

HTML:

<body>
   <p>
      Slider Value: <span id="val"></span><br/>
      Nonlinear Value: <span id="nlVal"></span><br/>
   </p>
   <div id="slider"></div>
</body>

JS:

    $(function() {
     var valMap = [0, 25,30,50,55,55,60,100];
     $("#slider").slider({
        // min: 0,
         max: valMap.length - 1,
         slide: function(event, ui) {
           $("#val").text(ui.value);
           $("#nlVal").text(valMap[ui.value]);
         }
     });
    });
Bharat Parmar
  • 1,842
  • 2
  • 18
  • 22
7

One option for you is use a step size of one and reference an array containing your selected values. Start at 0, go up to 20, get array[value_of_slider] to get your actual value. I don't know sliders well enough to tell you whether or not there is a way to give it a custom set of values.

Seburdis
  • 788
  • 1
  • 5
  • 11
  • @Seburdis - "reference an array containing your selected files." what files? See the demo available –  Jun 08 '09 at 22:52
  • That is, have an array values_array=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 250, 500, 1000, 1500, 2000, 2500, 5000, 10000]. Have the slider with a min value of 0 and a max of 20. When preparing a value for display, use values_array[slider.value]. – Chuck Jun 08 '09 at 22:58
  • @Chuck, would you mind posting the code that will make that work. –  Jun 08 '09 at 23:00
  • @TomHankers - My bad, I was thinking values and typed files. Fixed now. – Seburdis Jun 08 '09 at 23:53
6

I think I can offer a better solution: The idea is to keep an array with my real values apart from the slider, then to set the step attribute always to 1 and let the slider work with the index of that array and then fetch the real value from my real data array. Hope this example Helps:

myData = [ 1,1.5,2,2.5,3,3.5,4,4.5,5,6,7,8,9,10,20,30,40,50,100,200,300,400,500 ];

slider_config = {
    range: true,
        min: 0,
        max: myData.length - 1,
        step: 1,
        slide: function( event, ui ) {
            // Set the real value into the inputs
            $('#from').val( myData[ ui.values[0] ] );
            $('#to').val( myData[ ui.values[1] ] );
        },
    create: function() {
        $(this).slider('values',0,0);
        $(this).slider('values',1,myData.length - 1);
    }
};

// Render Slider
$('#slider').slider(slider_config);
* {
    font-family:Arial;
    font-size:20px;
}
body {
    padding:20px;
}
#slider { margin:20px }
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/black-tie/jquery-ui.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>

Slider:

<div id="slider"></div>

<input type="text" name="from" id="from" value="" placeholder="from" />
<input type="text" name="to" id="to" value="" placeholder="to" />

http://jsfiddle.net/3B3tt/3/

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Broshi
  • 3,334
  • 5
  • 37
  • 52
6

Based on the above answer and discussion from @Seburdis, and using the names from your example:

var prices_array = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 250, 500, 1000, 1500, 2000, 2500, 5000, 10000];

function imSliding(event,ui)
{
    //update the amount by fetching the value in the value_array at index ui.value
    $('#amount').val('$' + prices_arr[ui.value] + ' - $' + prices_arr[prices_arr.length - 1]); 
}

$('#slider-interval').slider({ min:0, max:values_arr.length - 1, slide: imSliding});
karim79
  • 339,989
  • 67
  • 413
  • 406
  • @karim79 - great little script; however, how do I get this to work with a Range Slider (dual slider)? –  Jun 09 '09 at 03:31
  • @karim79 - still curious to know how to get this to work with a dual handle range slider :) –  Jun 09 '09 at 05:11
  • you maybe wrong in that and should be this --> `max:prices_array.length - 1` and this `prices_array` to `prices_arr` – Karim Daraf Dec 08 '14 at 13:18
4

Somebody posted this on the jQuery website. See:

http://forum.jquery.com/topic/non-linear-logarithmic-or-exponential-scale-for-slider

and

http://jsbin.com/ezema

It is exactly what yo have, but way simpler (like 1 line of code).

Addo Solutions
  • 1,619
  • 3
  • 20
  • 37
3

here's how i did mine. demo here - http://janpatricklara.com/web/extra/jQueryUISliderUnevenSteps.html

have an array of your desired values

var amts=[50,100,150,200,225,250,300];

have the slider increment from 0 to the length of the array, with steps of 1. output the value from the index of the array instead of using the slider's actual value.

this way the increments and steps are evenly distributed. after that you can just grab the value of the label or save it to a var.

$('#amtslider').slider({
    min:0,
    max:amts.length-1,
    step:1,
    value:0,
    change:function(event, ui){
        //alert(amts[$(this).slider("value")]);
        //alert($(this).slider("value"));
        $('#lbl_amt').val(amts[$(this).slider("value")]);
    },
    slide:function(event, ui){
        $('#lbl_amt').val(amts[$(this).slider("value")]);
    }
});
jplara
  • 141
  • 6
2

Here is my simple solution for a custom (non consistent "step" size) dual-slider (you can modify it to a single-slider if the idea becomes clear) my slider is named "slider-euro", the text-area is named amount-euro as you can see in the code. The idea is to have a slider from 0 to 100 and an array ("realvalues") with 101 places. The slider-value is understood as the place in that array. The only thing is that you have to reference the array when you get the slider values. Here is my Example:

    $(function() {
    var realvalues = [0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 15000, 20000, 25000, 30000, 35000, 40000, 45000, 50000, 55000, 60000, 65000, 70000, 75000, 80000, 85000, 90000, 95000, 100000, 105000, 110000, 115000, 120000, 125000, 130000, 135000, 140000, 145000, 150000, 155000, 160000, 165000, 170000, 175000, 180000, 185000, 190000, 195000, 200000, 205000, 210000, 215000, 220000, 225000, 230000, 235000, 240000, 245000, 250000, 255000, 260000, 265000, 270000, 275000, 280000, 285000, 290000, 295000, 300000, 310000, 320000, 330000, 340000, 350000, 360000, 370000, 380000, 390000, 400000, 450000, 500000, 550000, 600000, 650000, 700000, 750000, 800000, 850000, 900000, 1000000, 1500000, 2000000];

    $( "#slider-euro" ).slider({
    range: true,
    min: 0,
    max: 100,
    step: 1,
    values: [ 25, 50 ],
    slide: function( event, ui ) {
    $( "#amount-euro" ).val( realvalues[ui.values[ 0 ]] + " € - " + realvalues[ui.values[ 1 ]] + " €");
    }
    });
    $( "#amount-euro" ).val( realvalues[$( "#slider-euro" ).slider( "values", 0 )] + " € - " + realvalues[$( "#slider-euro" ).slider( "values", 1 )]+" €" );
    });
Furqan Safdar
  • 16,260
  • 13
  • 59
  • 93
omega
  • 21
  • 1
1

I had to do something similar and even though this is a late response, someone else may happen on to this thread just as I did, and find my solution useful. Here is the solution I created:

http://jsfiddle.net/YDWdu/

So apparently, postings containing links to jsfiddle "must be accompanied by code".
OK - here ya go, stackoverflow... Some lovely "code" for you.
Cesar
  • 63
  • 4
1

if min and max value are the same, slider value don't change

change this part

        if (ui.value == ui.values[0]) {
            slider.slider('values', 0, value);
        }
        else {
            slider.slider('values', 1, value);
        }

into

        if ( ui.values[0] == ui.values[1] ) {
            slider.slider('values', 0, value);
            slider.slider('values', 1, value);
        }else{
            if (ui.value == ui.values[0]) {
                slider.slider('values', 0, value);
            }else {
                slider.slider('values', 1, value);
            }
        }
flex
  • 11
  • 1
0

Just change the values.

$( "#price-range" ).slider({
        range: true,
        min: 1000,
        max: 300000000,
        /*step:1,*/
        values: [ 1000, 300000000 ],
        slide: function( event, ui ) {
            if(ui.values[0]<=100000 && ui.values[1]<=100000){
                $("#price-range").slider({step:10000});
            }else if(ui.values[0]<=300000 && ui.values[1]<=300000){
                $("#price-range").slider({step:25000});
            }else if(ui.values[0]<=1000000 && ui.values[1]<=1000000){
                $("#price-range").slider({step:50000});
            }else if(ui.values[0]<=2000000 && ui.values[1]<=2000000){
                $("#price-range").slider({step:100000});
            }else if(ui.values[0]<=5000000 && ui.values[1]<=5000000){
                $("#price-range").slider({step:250000});
            }else if(ui.values[0]<=10000000 && ui.values[1]<=10000000){
                $("#price-range").slider({step:500000});
            }else if(ui.values[0]<=20000000 && ui.values[1]<=20000000){
                $("#price-range").slider({step:1000000});
            }else if(ui.values[0]<=50000000 && ui.values[1]<=50000000){
                $("#price-range").slider({step:5000000});
            }else if(ui.values[0]<=50000000 && ui.values[1]<=50000000){
                $("#price-range").slider({step:10000000});
            }else if(ui.values[0]<=200000000 && ui.values[1]<=200000000){
                $("#price-range").slider({step:25000000});
            }else{
                $("#price-range").slider({step:100000000});
            }

            $("#mins").val( ui.values[0] );
            $("#maxs").val( ui.values[1] );

        }
    });
kleopatra
  • 51,061
  • 28
  • 99
  • 211
0

Further to @Broshi's jsfiddle above, here is the code for a custom slider with range disabled:

jQuery:

var myData = [1,2,3,4,5,6,7,8,9,10,20,30,40,50,60,70,80,90,100,200,300,400,500,600,700,800,900,1000,2000,3000,4000,5000,10000,20000,30000,50000,100000,200000,500000,1000000,20000000,30000000];
slider_config = {
        range: false,
        min: 0,
        max: myData.length - 1,
        step: 1,
        slide: function( event, ui ) {
            // Set the real value into the inputs
            console.log(ui);
            $('#value').val( myData[ ui.value ] );
        },
        create: function() {
            $(this).slider('value',0);
        }
    };

$("#slider").slider(slider_config);

HTML:

<input id="value" />
<div id="slider"></div>
Eamorr
  • 9,872
  • 34
  • 125
  • 209