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?
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" />
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.
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.
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.
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-)
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;
}
};
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;
};
Related
It's kind of math problem. I want to fire specific number of setTimeout (the number is based on an array length) in a specific period of time (say, 5 seconds).
The first setTimeout should start at 0 sec. and the last at 5 sec.. All timeouts between should start with an ease-in effect, so that each timeout starts faster.
There's an example which ilustrates what I want to achieve exactly.
I'm struggling around this line:
next += timePeriod/3.52/(i+1);
which works almost perfect in demo example (for any timePeriod), but obviously it doesn't work for a different letters.length as I have used static number 3.52.
How do I calculate next?
var letters = [
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T'
];
var div = $('#container');
var timePeriod = 5000; // 5 seconds;
var perLetter = timePeriod/(letters.length-1); // it gives equal time between letters
var next = 0;
for(var i=0; i<letters.length; i++){
setTimeout(function(letter){
//div.append('<span class="letter">' + letter + '</span>');
// Used "|" instead of letter, For better redability:
div.append('<span class="letter">|</span>');
}, next, letters[i]);
// Can't find the logic here:
next += timePeriod/3.52/(i+1);
};
///////////////// FOR DEMO: ///////////////
var sec = timePeriod/1000;
var secondsInterval = setInterval(seconds, 1000);
var demoInterval = setInterval(function(){
sec >= 0 || clearInterval(demoInterval);
div.append('\'');
}, 30);
function seconds(){
sec || clearInterval(secondsInterval);
$('#sec').text(sec-- || 'DONE');
}
seconds();
.letter{
color : red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id=container></span>
<span id=sec class=letter></span>
var steps = letters.length;
var target = timePeriod;
function easeOutQuad(t, b, c, d) {
t /= d;
return -c * t*(t-2) + b;
};
var arrayOfTimeouts = new Array(steps);
var n;
var prev = 0;
for(var i = 1; i <= steps; i++){
n = easeOutQuad(i, 0.0, target, steps);
arrayOfTimeouts[i-1] = n-prev;
prev = n;
}
This one should work with any input value.
fiddle
Note that the graph appears to be slightly too fast but I believe that discrepancy to be a product of timing imperfections, as the sum of my array equals the timePeriod exactly.
more on easing equations
Here's a solution based on a geometric series. It's a bit goofy but it works. It generates an array with your timeout values.
Steps = size of your array.
Target = the total time.
var steps = 50;
var target = 5000;
var fraction = 1.5 + steps / 7;
var ratio = (fraction-1) / fraction;
var n = target / fraction;
var sum = 0;
var arrayOfTimeouts = new Array(steps);
for(var i = 0; i < steps; i++){
sum += n;
arrayOfTimeouts[i] = n;
n *= ratio;
}
console.log(arrayOfTimeouts, sum);
I have a database that has got a month full of datasets in 10min intervals. (So a dataset for every 10min)
Now I want to show that data on three graphs: last 24 hours, last 7 days and last 30 days.
The data looks like this:
{ "data" : 278, "date" : ISODate("2016-08-31T01:51:05.315Z") }
{ "data" : 627, "date" : ISODate("2016-08-31T01:51:06.361Z") }
{ "data" : 146, "date" : ISODate("2016-08-31T01:51:07.938Z") }
// etc
For the 24h graph I simply output the data for the last 24h, that's easy.
For the other graphs I thin the data:
const data = {}; //data from database
let newData = [];
const interval = 7; //for 7 days the interval is 7, for 30 days it's 30
for( let i = 0; i < data.length; i += interval ) {
newData.push( data[ i ] );
};
This works fine but extreme events where data is 0 or differs greatly from the other values average, can be lost depending on what time you search the data. Not thinning out the data however will result in a large sum of data points that are sent over the pipe and have to be processed on the front end. I'd like to avoid that.
Now to my question
How can I reduce the data for a 7 day period while keeping extremes in it? What's the most efficient way here?
Additions:
In essence I think I'm trying to simplify a graph to reduce points but keep the overall shape. (If you look at it from a pure image perspective)
Something like an implementation of Douglas–Peucker algorithm in node?
As you mention in the comments, the Ramer-Douglas-Peucker (RDP) algorithm is used to process data points in 2D figures but you want to use it for graph data where X values are fixed. I modified this Javascript implementation of the algorithm provided by M Oehm to consider only the vertical (Y) distance in the calculations.
On the other hand, data smoothing is often suggested to reduce the number of data points in a graph (see this post by csgillespie).
In order to compare the two methods, I made a small test program. The Reset button creates new test data. An algorithm can be selected and applied to obtain a reduced number of points, separated by the specified interval. In the case of the RDP algorithm however, the resulting points are not evenly spaced. To get the same number of points as for the specified interval, I run the calculations iteratively, adjusting the espilon value each time until the correct number of points is reached.
From my tests, the RDP algorithm gives much better results. The only downside is that the spacing between points varies. I don't think that this can be avoided, given that we want to keep the extreme points which are not evenly distributed in the original data.
Here is the code snippet, which is better seen in Full Page mode:
var svgns = 'http://www.w3.org/2000/svg';
var graph = document.getElementById('graph1');
var grpRawData = document.getElementById('grpRawData');
var grpCalculatedData = document.getElementById('grpCalculatedData');
var btnReset = document.getElementById('btnReset');
var cmbMethod = document.getElementById('cmbMethod');
var btnAddCalculated = document.getElementById('btnAddCalculated');
var btnClearCalculated = document.getElementById('btnClearCalculated');
var data = [];
var calculatedCount = 0;
var colors = ['black', 'red', 'green', 'blue', 'orange', 'purple'];
var getPeriod = function () {
return parseInt(document.getElementById('txtPeriod').value, 10);
};
var clearGroup = function (grp) {
while (grp.lastChild) {
grp.removeChild(grp.lastChild);
}
};
var showPoints = function (grp, pts, markerSize, color) {
var i, point;
for (i = 0; i < pts.length; i++) {
point = pts[i];
var marker = document.createElementNS(svgns, 'circle');
marker.setAttributeNS(null, 'cx', point.x);
marker.setAttributeNS(null, 'cy', point.y);
marker.setAttributeNS(null, 'r', markerSize);
marker.setAttributeNS(null, 'fill', color);
grp.appendChild(marker);
}
};
// Create and display test data
var showRawData = function () {
var i, x, y;
var r = 0;
data = [];
for (i = 1; i < 500; i++) {
x = i;
r += 15.0 * (Math.random() * Math.random() - 0.25);
y = 150 + 30 * Math.sin(x / 200) * Math.sin((x - 37) / 61) + 2 * Math.sin((x - 7) / 11) + r;
data.push({ x: x, y: y });
}
showPoints(grpRawData, data, 1, '#888');
};
// Gaussian kernel smoother
var createGaussianKernelData = function () {
var i, x, y;
var r = 0;
var result = [];
var period = getPeriod();
for (i = Math.floor(period / 2) ; i < data.length; i += period) {
x = data[i].x;
y = gaussianKernel(i);
result.push({ x: x, y: y });
}
return result;
};
var gaussianKernel = function (index) {
var halfRange = Math.floor(getPeriod() / 2);
var distance, factor;
var totalValue = 0;
var totalFactor = 0;
for (i = index - halfRange; i <= index + halfRange; i++) {
if (0 <= i && i < data.length) {
distance = Math.abs(i - index);
factor = Math.exp(-Math.pow(distance, 2));
totalFactor += factor;
totalValue += data[i].y * factor;
}
}
return totalValue / totalFactor;
};
// Ramer-Douglas-Peucker algorithm
var ramerDouglasPeuckerRecursive = function (pts, first, last, eps) {
if (first >= last - 1) {
return [pts[first]];
}
var slope = (pts[last].y - pts[first].y) / (pts[last].x - pts[first].x);
var x0 = pts[first].x;
var y0 = pts[first].y;
var iMax = first;
var max = -1;
var p, dy;
// Calculate vertical distance
for (var i = first + 1; i < last; i++) {
p = pts[i];
y = y0 + slope * (p.x - x0);
dy = Math.abs(p.y - y);
if (dy > max) {
max = dy;
iMax = i;
}
}
if (max < eps) {
return [pts[first]];
}
var p1 = ramerDouglasPeuckerRecursive(pts, first, iMax, eps);
var p2 = ramerDouglasPeuckerRecursive(pts, iMax, last, eps);
return p1.concat(p2);
}
var internalRamerDouglasPeucker = function (pts, eps) {
var p = ramerDouglasPeuckerRecursive(data, 0, pts.length - 1, eps);
return p.concat([pts[pts.length - 1]]);
}
var createRamerDouglasPeuckerData = function () {
var finalPointCount = Math.round(data.length / getPeriod());
var epsilon = getPeriod();
var pts = internalRamerDouglasPeucker(data, epsilon);
var iteration = 0;
// Iterate until the correct number of points is obtained
while (pts.length != finalPointCount && iteration++ < 20) {
epsilon *= Math.sqrt(pts.length / finalPointCount);
pts = internalRamerDouglasPeucker(data, epsilon);
}
return pts;
};
// Event handlers
btnReset.addEventListener('click', function () {
calculatedCount = 0;
clearGroup(grpRawData);
clearGroup(grpCalculatedData);
showRawData();
});
btnClearCalculated.addEventListener('click', function () {
calculatedCount = 0;
clearGroup(grpCalculatedData);
});
btnAddCalculated.addEventListener('click', function () {
switch (cmbMethod.value) {
case "Gaussian":
showPoints(grpCalculatedData, createGaussianKernelData(), 2, colors[calculatedCount++]);
break;
case "RDP":
showPoints(grpCalculatedData, createRamerDouglasPeuckerData(), 2, colors[calculatedCount++]);
return;
}
});
showRawData();
div
{
margin-bottom: 6px;
}
<div>
<button id="btnReset">Reset</button>
<select id="cmbMethod">
<option value="RDP">Ramer-Douglas-Peucker</option>
<option value="Gaussian">Gaussian kernel</option>
</select>
<label for="txtPeriod">Interval: </label>
<input id="txtPeriod" type="text" style="width: 36px;" value="7" />
</div>
<div>
<button id="btnAddCalculated">Add calculated points</button>
<button id="btnClearCalculated">Clear calculated points</button>
</div>
<svg id="svg1" width="765" height="450" viewBox="0 0 510 300">
<g id="graph1" transform="translate(0,300) scale(1,-1)">
<rect width="500" height="300" stroke="black" fill="#eee"></rect>
<g id="grpRawData"></g>
<g id="grpCalculatedData"></g>
</g>
</svg>
I have a computed value here,
self.total_remain_percent = ko.computed(function() {
var x = 0;
var y = 0;
var z = 0;
var a = 0;
$.each(self.paymentPlan(), function (index, item) {
x += parseFloat(item.total_paid());
});
$.each(self.paymentPlan(), function (index, item) {
y += parseFloat(item.total_payment());
});
a = y-x ;
z = (a/y) * 100;
return z.toFixed(0);
});
Which I need to show it in a progress bar , I used knockout observable to bind with the progress bar, but I cannot display it, here is how the progress bar is done.
self.progress = ko.observable(10);
<div data-bind="progress: progress"></div>
For now the value in progress bar shows and also the computed value shows 100,
<span data-bind="text : $data.total_remain_percent"></span>
But I need to show this total remain percent, on the progress bar,
I tried it in this way , but did not work
self.progress = ko.observable(self.total_remain_percent);
And
self.progress = ko.observable(self.total_remain_percent());
Need help on putting the value there.
On a general note, don't call your variables x, y, z and a.
self.total_remain_percent = ko.computed(function() {
var totalPaid = 0;
var totalPayment = 0;
var remain = 0;
var remainPercent = 0;
ko.utils.arrayForEach(self.paymentPlan(), function (item) {
// Why aren't item.total_paid and item.total_payment numbers?
// There should not be any calls to parseFloat() here.
totalPaid += parseFloat(item.total_paid());
totalPayment += parseFloat(item.total_payment());
});
remain = totalPayment - totalPaid;
remainPercent = remain / totalPayment * 100;
// this is all about numbers, so let's return a number (toFixed returns strings)
return Math.round(remainPercent);
});
and
<div data-bind="progress: total_remain_percent"></div>
should work fine, assuming that the progress binding turns the <div> into a progress bar representation.
So I've been working on re-producing the slider found here https://www.skylight.io/ ( Scroll down to find the price slider ).
So far Ive managed to create something similiar, but some numbers are hard coded, making it difficult to change and not very re-usable.
I've been researching around and I think I need to use Math.log() and Math.exp() together to achieve something like in the link above but I'm not sure.
Heres a jsfiddle of what I have so far https://jsfiddle.net/7wrvpb34/.
I feel that its the maths part of this problem that is halting me I think, so any help would be greatly appreciated.
Javascript code below:
var slider = document.getElementById("slider")
var sliderFill = document.getElementById("slider-fill")
var knob = document.getElementById("knob")
var mouseDown;
var mousePos = {x:0};
var knobPosition;
var minPrice = 20;
var price = 0;
var minRequests = 50;
var requests = 50 + ",000";
var incrementSpeed = 2;
var incrementModifier = 20;
var incrementValue = 1;
var minMillionCount = 1;
var millionCount = 1;
var previousRequestAmount = 0;
document.getElementById("price").innerHTML = price;
document.getElementById("requests").innerHTML = requests;
highlightTable(1);
document.addEventListener('mousemove', function(e) {
if(mouseDown) {
updateSlider(e);
}
})
function updateSlider(event) {
mousePos.x = event.clientX - slider.getBoundingClientRect().left;
mousePos.x -= knob.offsetWidth / 2;
console.log(mousePos.x);
if(mousePos.x < 0) {
knob.style.left = "0px";
sliderFill.style.width = "0px";
price = 0;
requests = 50 + ",000";
document.getElementById("price").innerHTML = price;
document.getElementById("requests").innerHTML = requests;
return
}
if(mousePos.x > slider.offsetWidth - 20) {
return
}
sliderFill.style.width = mousePos.x + 10 + "px";
knob.style.left = mousePos.x + "px";
//Increase requests by using X position of mouse
incrementSpeed = mousePos.x / incrementModifier;
requests = minRequests + (mousePos.x * incrementSpeed);
//Round to nearest 1
requests = Math.round(requests / incrementValue) * incrementValue;
if (requests >= 1000){
var m = requests/ 1000;
m = Math.round(m / 1) * 1;
//Problem, lower the modifier depending on requests
incrementModifier = 20 * 0.95;
document.getElementById("requests").innerHTML = m + " million";
//Adjust Prices
if(( requests >= 1000) && (requests < 10000)) {
var numOfMillions = requests / 100;
//Round to closest 10.
//10 * number of millions
var rounded = Math.round(numOfMillions / 10) * 10;
price = minPrice + rounded;
highlightTable(3);
}
//Adjust Prices
if(requests >= 10000) {
var numOfMillions = requests / 1000;
var rounded = Math.round(numOfMillions / 1) * 1;
var basePrice = minPrice * 6;
price = basePrice + rounded;
highlightTable(4);
}
} else {
incrementModifier = 20;
document.getElementById("requests").innerHTML = requests + ",000"
if(requests < 100) {
highlightTable(1);
price = 0;
} else {
highlightTable(2);
price = 20;
}
}
previousRequestAmount = requests;
document.getElementById("price").innerHTML = price;
}
knob.addEventListener('mousedown', function() {
mouseDown = true;
});
document.addEventListener('mouseup', function() {
mouseDown = false;
});
function highlightTable(rowNum) {
var table = document.getElementById("payment-table")
for(var i = 0; i < table.rows.length; ++i) {
var row = table.rows[i]
if(i == rowNum) {
row.style.background = "grey"
} else {
row.style.background = "white";
}
}
}
Thank you for your time.
If you want it to be reusable you need to create a mathematical function that assigns a result to the number of requests. I will give you a very easy example.
If you want a different result for 1,10,100,100,10000 etc
var d = Math.log10(requests);
if(d<1){
doSomething();
}else if(d<2){
doSomethingElse();
} //etc
This way if you want to change the specific values that create certain results, all you need to do is change the function.
This only works if your tiers of requests follow a math function, if they don't you need to hard code it.
However if say they don't follow a math function, but you know how you would like them to change based on a value then you can do this.
var changingValue = 4;
if(requests < 400*changingValue){
doSomthing();
}else if(requests <= 400*changingValue*changingValue){
doSomethingElse();
}else{// the requests is greater than any of the above
doTheOtherThing();
}
Edit:
For the second one you need to make sure that each condition if always larger than the other from top to bottom.
The description "increasingly increasing" matches an arbitrary number of functions. I assume you also want it to be continuous, since you already have a non-continuous solution.
TL;DR
Use an exponential function.
Generic approach
Assuming imin and imax are the minimal and maximal values of the slider (i for input) and omin and omax are the minimal and maximal values to be displayed, the simplest thing I can think of would be a multiplication by something based on the input value:
f(x)
{
return omin + (omax - omin) * g((x - imin) / (imax - imin));
}
This will pass 0 to g if x == imin and 1 if x == imax.
The return value r of g(y) should be
r == 0 for y == 0
r == 1 for y == 1
0 < r < y for 0 < y < 1
The simplest function that I can think of that fulfills this is an exponential function with exponent > 1.
An exponent of 1 would be a linear function.
An exponent of 2 would be make the middle of the slider display one fourth of the maximum price instead of half of it.
But you really need to find that exponent yourself, based on your needs.
var q3a1 = parseInt(valueOne); //get variables
var q3a2 = parseInt(valueTwo);
var q3a3 = parseInt(valueThree);
var totalAmountThree = Math.ceil((q3a1+q3a2+q3a3) / 100)*100; //round to nearest 100
var percentOnec = ((q3a1 / totalAmountThree) * 100); //calculate percentage
var percentTwoc = ((q3a2 / totalAmountThree) * 100);
var percentThreec = ((q3a3 / totalAmountThree) * 100);
alert("1: "+percentOnec);
alert("2: "+percentTwoc);
alert("3: "+percentThreec);
Is there a better way for me to be calculating a percentage?
(Fiddle with me: http://jsfiddle.net/neuroflux/Sez3Q/)
You could optimize this by calculating the factor, needed to scale to percentage directly:
S = A+B+C
p(x)=100*x / S
so the 'factor' is 100/(A+B+C):
var total = a+b+c;
var scale = 100/total;
function pct(x) { return x*scale; }
And it can be generalized, of course, to work with an array of input values etc...
function toPctFn( values ) {
var sum = 0;
for( var i = 0; i != values.length; ++i ) { sum = sum + values[i]; }
var scale = 100/sum;
return function( x ){
return x*scale;
};
}
var inputs=[1,2,40,44,23];
var toPct=toPctFn(inputs);
for( var i = 0; i != inputs.length; ++i ) {
alert(""+i+": "+toPct(inputs[i])) ;
}
(see jsFiddle)
Also, it's quite important to postpone rounding to the displaying code. This way you don't introduce unnecessary errors in the calculation.
you're introducing rounding off errors, first on the line using Math.ceil(). If there's no reason to round off to the nearest hundred first, it's better to leave it out. It's possible to have a significant (depending on what you're calculating) disparity between the 'real' value and your calculation.