95

I have a slider with values ranging from 0 to 100.

I want to map them to a range from 100 to 10,000,000.

I've seen some functions scattered around the net but they're all in C++. I need it in Javascript.

Any ideas?

Flip
  • 6,233
  • 7
  • 46
  • 75
user103773
  • 953
  • 1
  • 8
  • 5

6 Answers6

212

You can use a function like this:

function logslider(position) {
  // position will be between 0 and 100
  var minp = 0;
  var maxp = 100;

  // The result should be between 100 an 10000000
  var minv = Math.log(100);
  var maxv = Math.log(10000000);

  // calculate adjustment factor
  var scale = (maxv-minv) / (maxp-minp);

  return Math.exp(minv + scale*(position-minp));
}

The resulting values match a logarithmic scale:

js> logslider(0);
100.00000000000004
js> logslider(10);
316.22776601683825
js> logslider(20);
1000.0000000000007
js> logslider(40);
10000.00000000001
js> logslider(60);
100000.0000000002
js> logslider(100);
10000000.000000006

The reverse function would, with the same definitions for minp, maxp, minv, maxv and scale, calculate a slider position from a value like this:

function logposition(value) {
   // set minv, ... like above
   // ...
   return (Math.log(value)-minv) / scale + minp;
}


All together, wrapped in a class and as a functional code snippet, it would look like this:

// Generic class:

function LogSlider(options) {
   options = options || {};
   this.minpos = options.minpos || 0;
   this.maxpos = options.maxpos || 100;
   this.minlval = Math.log(options.minval || 1);
   this.maxlval = Math.log(options.maxval || 100000);

   this.scale = (this.maxlval - this.minlval) / (this.maxpos - this.minpos);
}

LogSlider.prototype = {
   // Calculate value from a slider position
   value: function(position) {
      return Math.exp((position - this.minpos) * this.scale + this.minlval);
   },
   // Calculate slider position from a value
   position: function(value) {
      return this.minpos + (Math.log(value) - this.minlval) / this.scale;
   }
};


// Usage:

var logsl = new LogSlider({maxpos: 20, minval: 100, maxval: 10000000});

$('#slider').on('change', function() {
   var val = logsl.value(+$(this).val());
   $('#value').val(val.toFixed(0));
});

$('#value').on('keyup', function() {
   var pos = logsl.position(+$(this).val());
   $('#slider').val(pos);
});

$('#value').val("1000").trigger("keyup");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Input value or use slider:
<input id="value" />
<input id="slider" type="range" min="0" max="20" />
sth
  • 222,467
  • 53
  • 283
  • 367
  • 5
    Any chance you'd have the opposite function? Given the log value, output what position the slider be at. – GeekyMonkey Mar 09 '11 at 16:21
  • 1
    Isn't this a exponential slider? Is there a way to reverse the curve so it is logarithmic? So that the value goes up fast and then goes slow towards the end? – Nick Hooked Mar 02 '17 at 10:53
  • 2
    @NickH: Depends how you look at it. Currently when the slider position goes up the associated value goes up faster and faster, so from that perspective it's exponential. If you need it the other way around, switch `value` and `position` to get the opposite effect. – sth Mar 02 '17 at 23:17
  • @sth Thank you, I always get confused with exponential and logaritmic. Is there a way to make the curve in your solution go up less rapid(more linear, but still exponential)? At an input of 50 the value is somewhere around 550. – Nick Hooked Mar 03 '17 at 09:28
11

The problem with a true Logarithmic slider is at the low end, multiple points on the slider will likely result in duplicate values.

From purely UI perspective, it also doesn't provide a very intuitive output for the users input.

I think a better option is to use an even-distribution "stepped" transform.

enter image description here

In other words, we specify a series of increments we want to use (ex: 1, 10, 100, 1000). Then we split the slider into equal parts based on the number of increments we defined. When we are sliding through our different sections, the slider output will increment by the respective increment.

WORKING DEMO

REACT CODE

In the above example, we define our min, max & intervals array.

<ExpoStepSlider
  intervals={[1, 2, 5, 10, 100, 1000]}
  min={1}
  max={50000}
/>

We then must find the number of discrete values our slider must have so that it properly goes from min to max based on our defined interval distributions.

let sliderPoints = Math.ceil(
  (max - min) / 
  intervals.reduce((total, interval) => total + interval / intervals.length, 0)
);

In this case 535.

Note: Your slider points should not exceed the number of pixels in the slider

Finally, we just transform our output using the algorithm described above. The code example also does some work so the output is always round for the the current step interval.

NSjonas
  • 10,693
  • 9
  • 66
  • 92
11

Not quite answering the question, but for people interested, the reverse maping the last line is

return (Math.log(value)-minv)/scale + min;

just to document.

NOTE the value must be > 0.

siliconeagle
  • 7,379
  • 3
  • 29
  • 41
9

To get the distribution you want, I think you can use this formula:

var value = Math.floor(-900 + 1000*Math.exp(i/10.857255959));

Here's a self-contained page that will print the values you'll get for your 0-100 slider, having passed them through that formula:

<html><body><script>
for (var i = 0; i <= 100; i++) {
    var value = Math.floor(-900 + 1000*Math.exp(i/10.857255959));
    document.write(value + "<br>");
}
</script></body></html>

The numbers go from 100 to 10,000,000 in what looks to my mathematically-rusty eye to be the distribution you want. 8-)

RichieHindle
  • 272,464
  • 47
  • 358
  • 399
  • 4
    Started with Math.exp(i) and adjusted by hand until the numbers fit. My maths-fu is weak, but I can binary chop for England. 8-) – RichieHindle May 10 '09 at 22:47
2

POW() function

Here's a slightly different take with a pow() function. This allows setting a "skew" curve that governs the distribution of the input <-> output curve. Another refactored version allows setting the slider from logarithmic (exponential) input.

$(document).ready(function() {
  $('#test').change(function() {
    this.value = parseFloat(this.value).toFixed(2);
    widthSliderCurve = this.value;
  });
});

var widthSliderCurve = 0.42; // between -1 and 1 - governs the way the range is skewed

widthSlider.oninput = function() {
  var thisSlider = document.getElementById("widthSlider");


  var curve = Math.pow(10, widthSliderCurve); // convert linear scale into lograthimic exponent for other pow function
  var originalMin = 1.0; // these are set in the slider defintion in HTML make sure they match!
  var originalMax = 200.0; // these are set in the slider defintion in HTML
  var mappedOutputMin = 0.05; // this is the desired output min
  var mappedOutputMax = 10; // this is the desired output max
  var originalRange = originalMax - originalMin;
  var newRange = mappedOutputMax - mappedOutputMin;

  var zeroRefCurVal = thisSlider.value - originalMin; // zero referenced
  var normalizedCurVal = zeroRefCurVal / originalRange; // normalized to 0-1 output
  var rangedValue = ((Math.pow(normalizedCurVal, curve) * newRange) + mappedOutputMin).toFixed(2);

  var outbox = document.getElementById("wSliderOutput");
  outbox.innerHTML = rangedValue;
  //paper.tool.LineWidth = rangedValue;
};



let setLogSlider = function(value, widthSliderCurve, sliderType, outputName) {
  /*** make sure constants match the oninput function values ***/
  var curve = Math.pow(10, widthSliderCurve);
  var originalMin = 1.0; // these are set in the slider defintion in HTML make sure they match!
  var originalMax = 200.0; // these are set in the slider defintion in HTML
  var mappedOutputMin = 0.05; // this is the desired output min
  var mappedOutputMax = 10; // this is the desired output max
  var originalRange = originalMax - originalMin;
  var newRange = mappedOutputMax - mappedOutputMin;
  var logToLinear = Math.pow((value - mappedOutputMin) / newRange, 1 / curve) * originalRange + originalMin;
  console.log("logToLinear ", logToLinear);
  // set the output box
  var outbox = document.getElementById("wSliderOutput");
  outbox.innerHTML = Number(value).toFixed(2);
  // set the linear scale on the slider
  document.querySelectorAll(".optionSliders").forEach((optSlider) => {
    if (optSlider.getAttribute("data-slider-type") == sliderType) {
      optSlider.value = logToLinear;
    }
    var outBox = document.getElementById(outputName);
    outBox.value = value;
  });
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<html>

<head>
  <title>Pow function for variable skew of data mapping</title>
</head>

<body>
  Enter a float between -1.0 and 1.0, hit return, and cycle the slider.</br>

  <input id="test" type="text" value="0.5" />

  <input id="widthSlider" sliderName="widthSlider" type="range" min="1" value="20" max="200" data-slider-type="linesWidth">
  <label for="widthSlider">L Width <span id="Width">
        <output id="wSliderOutput"></output>
        </span></label>
</body>

</html>

var widthSliderCurve = 0.42; // between -1 and 1 - governs the way the range is skewed

widthSlider.oninput = function () { var thisSlider = document.getElementById("widthSlider");

  var curve = Math.pow(10, widthSliderCurve); // convert linear scale into lograthimic exponent for other pow function
  var originalMin = 1.0;     // these are set in the slider defintion in HTML make sure they match!
  var originalMax = 200.0;   // these are set in the slider defintion in HTML
  var mappedOutputMin = 0.05; // this is the desired output min
  var mappedOutputMax = 10;  // this is the desired output max
  var originalRange = originalMax - originalMin;
  var newRange = mappedOutputMax - mappedOutputMin;

  var zeroRefCurVal = thisSlider.value - originalMin;      // zero referenced
  var normalizedCurVal  =  zeroRefCurVal / originalRange;  // normalized to 0-1 output
  var rangedValue =  ((Math.pow(normalizedCurVal, curve) * newRange) + mappedOutputMin).toFixed(2);

  var outbox = document.getElementById("wSliderOutput");
  outbox.innerHTML = rangedValue;
  paper.tool.LineWidth = rangedValue;

 
  

  //setLogSlider(rangedValue, widthSliderCurve, "L_Width", "wSliderOutput2");

};let setLogSlider = function (value, widthSliderCurve, sliderType, outputName){ /*** make sure constants match the oninput function values ***/ var curve = Math.pow(10, widthSliderCurve); var originalMin = 1.0; // these are set in the slider defintion in HTML make sure they match! var originalMax = 200.0; // these are set in the slider defintion in HTML var mappedOutputMin = 0.05; // this is the desired output min var mappedOutputMax = 10; // this is the desired output max var originalRange = originalMax - originalMin; var newRange = mappedOutputMax - mappedOutputMin; var logToLinear = Math.pow((value - mappedOutputMin) / newRange, 1 / curve) * originalRange + originalMin; console.log("logToLinear ", logToLinear); // set the output box var outbox = document.getElementById("wSliderOutput"); outbox.innerHTML = Number(value).toFixed(2); // set the linear scale on the slider document.querySelectorAll(".optionSliders").forEach((optSlider) => { if (optSlider.getAttribute("data-slider-type") == sliderType) { optSlider.value = logToLinear; } var outBox = document.getElementById(outputName); outBox.value = value; }); };

var widthSliderCurve = 0.42; // between -1 and 1 - governs the way the range is skewed

widthSlider.oninput = function () { var thisSlider = document.getElementById("widthSlider");

  var curve = Math.pow(10, widthSliderCurve); // convert linear scale into lograthimic exponent for other pow function
  var originalMin = 1.0;     // these are set in the slider defintion in HTML make sure they match!
  var originalMax = 200.0;   // these are set in the slider defintion in HTML
  var mappedOutputMin = 0.05; // this is the desired output min
  var mappedOutputMax = 10;  // this is the desired output max
  var originalRange = originalMax - originalMin;
  var newRange = mappedOutputMax - mappedOutputMin;

  var zeroRefCurVal = thisSlider.value - originalMin;      // zero referenced
  var normalizedCurVal  =  zeroRefCurVal / originalRange;  // normalized to 0-1 output
  var rangedValue =  ((Math.pow(normalizedCurVal, curve) * newRange) + mappedOutputMin).toFixed(2);

  var outbox = document.getElementById("wSliderOutput");
  outbox.innerHTML = rangedValue;
  paper.tool.LineWidth = rangedValue;

};

puzzled
  • 21
  • 2
  • Please edit your question and place code in `code` blocks properly. – Sunderam Dubey May 07 '22 at 01:31
  • Is there a way to trigger a full page editor? I fussed with this for a bit in the small scrolling window. Feel free to delete my post if you are an editor. The code snippet does work however. – puzzled May 08 '22 at 10:23
  • This would be much more digestible if it was instead presented as a simple function that takes min/max/position/modifier and outputs the value. Right now this is a bit of a hogepoge of stuff, – Douglas Gaskell Dec 10 '22 at 21:44
1

I was searching for Logarithmic slider For Angular but can't find any and then I came across this answer ,

And I have created that for Angular 2+ (Demo is in Angular 6) : WORKING DEMO

Thanks to @sth, for snippet :

function LogSlider(options) {
   options = options || {};
   this.minpos = options.minpos || 0;
   this.maxpos = options.maxpos || 100;
   this.minlval = Math.log(options.minval || 1);
   this.maxlval = Math.log(options.maxval || 100000);

   this.scale = (this.maxlval - this.minlval) / (this.maxpos - this.minpos);
}

LogSlider.prototype = {
   // Calculate value from a slider position
   value: function(position) {
      return Math.exp((position - this.minpos) * this.scale + this.minlval);
   },
   // Calculate slider position from a value
   position: function(value) {
      return this.minpos + (Math.log(value) - this.minlval) / this.scale;
   }
};
Vivek Doshi
  • 56,649
  • 12
  • 110
  • 122