Related
I generate graph code for a graph with a logarithmic scale with negative values. The result is in the fiddle below.
I made the function required to handle the negative values like here and it worked perfectly. But since some days it does not function anymore. So I adjusted the code according to this article but it still does not work. See my JSFidddle:
(function (H) {
H.addEvent(H.Axis, 'afterInit', function () {
const logarithmic = this.logarithmic;
if (logarithmic && this.options.custom.allowNegativeLog) {
// Avoid errors on negative numbers on a log axis
this.positiveValuesOnly = false;
// Override the converter functions
logarithmic.log2lin = num => {
const isNegative = num < 0;
let adjustedNum = Math.abs(num);
if (adjustedNum < 10) {
adjustedNum += (10 - adjustedNum) / 10;
}
const result = Math.log(adjustedNum) / Math.LN10;
return isNegative ? -result : result;
};
logarithmic.lin2log = num => {
const isNegative = num < 0;
let result = Math.pow(10, Math.abs(num));
if (result < 10) {
result = (10 * (result - 1)) / (10 - 1);
}
return isNegative ? -result : result;
};
}
});
}(Highcharts));
I get an error 10 but the examples in that description don't go anywhere.
What is going on and how do I repair?
Apparently Highcharts made a change to the Axis definition.
The Y-axis was defined as:
yAxis:
{
type: 'logarithmic',
allowNegativeLog: true,
title:
{
text: 'Regen Spreiding ( mm)'
},
min: 0.0,
max: 25.40
},
And it is now required to be (at least it is working with this modification):
type: 'logarithmic',
custom:
{
allowNegativeLog: true,
},
NOTE: this is together with the modified function H which now starts with :
H.addEvent(H.Axis, 'afterInit', function () {
If you landed here like I did, checkout this JSFiddle by Highcharts with working demo.
I wrote some JavaScript code to animate CSS properties of elements. I pass the following arguments to the function: amount, interval, and duration; amount being the change in the property (for example 200 could mean add 200 pixels to the element's width), interval being the time between two consecutive changes, and duration being the total duration of the animation.
The code works fine unless I pass the arguments in a way that the change in each interval becomes very small (like a tiny fraction of a pixel).
I know the code is working fine theoretically, as I get the change in console.
Any ideas about the problem?
Cheers.
UPDATE: the code:
function handleTimer (amount, interval, duration, execute, element) {
let i = 0;
let current = 0;
let stepsCount = countSteps(interval, duration);
let stepLength = calcStepLength(stepsCount, amount);
let count = setTimeout(function addOneMore () {
if ( i < stepsCount -1 ){
i++;
current += stepLength;
execute(stepLength, element);
if (current < amount) {
count = setTimeout(addOneMore, interval)
}
} else {
current = amount;
execute(amount - (stepsCount -1) * stepLength, element);
}
}, interval)
}
function countSteps (interval, duration) {
let remainder = duration % interval;
let stepsCount;
if (remainder) {
stepsCount = Math.floor(duration / interval) + 1;
} else {
stepsCount = duration / interval;
}
return stepsCount;
}
function calcStepLength(stepsCount, amount) {
return amount / stepsCount;
}
function resizeWidth (amount, element) {
let widthSTR = $(element).css('width');
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
$(element).css('width', `${width + amount}px`);
}
So this:
handleTimer(218, 5, 200, resizeWidth, '.box');
works fine, but this:
handleTimer(218, 5, 2000, resizeWidth, '.box');
doesn't.
UPDATE 2:
I know browsers are super accurate with pixels, like when you use percentages. Of course the value will be rounded before rendering since displays cant display half pixels, but the value is still calculated accurately.
I don't know at what decimal the rounding occurs.
This happens because parseInt is rounding your number up.
Pay attention to this line:
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
if width is a decimal number, like 22.5px, it will be rounded up to 22.
If amount is less than 1, it won't reach 23 and when you round up the number again, you'll get 22 again and it becomes a loop.
You have two solutions:
Use another variable to save the width value, avoiding to writing and reading it from CSS:
let initialWidth = $(element).css('width');
let savedWidth = widthSTR.substr(0, initialWidth, initialWidth.length - 2 ) );
function resizeWidth (amount, element) {
savedWidth += amount;
$(element).css('width', `${savedWidth}px`);
}
Just use parseFloat in place of parseInt to don't round your number up:
let width = parseFloat( widthSTR.substr( 0 , widthSTR.length - 2 ) );
I am working on simple script that should animate given value (for example 6345.23) to 0 by counting it down, it should also end up at 0 if specified amount of time have passed (for example 2 seconds.
I started by simple logic:
given config: initial value, time in sec, interval
time is given in seconds so convert it to milliseconds
calculate amount of ticks by dividing time in ms by interval
calculate amount of decreased value per tick by dividing initial value by amount of ticks
once above are known we can simply do: (simple model, not actual code)
intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
clearInterval(intId);
} else {
value -= amount_per_tick;
// update view
}
}, interval);
actual code:
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
Link do fiddle (in console you can observe how it works)
If you set time to 2 (2 seconds) its ok, but if you set time to for example 2.55 (2.55 seconds) it doesnt stop at all at 0, its passing by and going indefinitely in negative values.
How i can fix it so no matter what is set in seconds its always go precisly one by one until reaches perfectly 0?
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
You're relying on ticks_made === amount_of_ticks being an exact match. Chances are, due to rounding, you won't get an exact match, so you'd be better off doing:
if(ticks_made >= amount_of_ticks) {
kshetline's answer correctly addresses why you get into negative values. When dealing with fractional IEEE-754 double-precision binary numbers (in the normal range, or even whole numbers in very high ranges), == and === can be problematic (for instance, 0.1 + 0.2 == 0.3 is false). Dealing with values as small as the fractional values here are, accumulated imprecision is also a factor. It's inevitable to have to fudge the final step.
But there's a larger issue: You can't rely on timers firing on a precise schedule. Many, many things can prevent their doing so — other UI rendering work, other scripts, CPU load, the tab being inactive, etc.
Instead, the fundamental technique for animation on browsers is:
Update when you can
Update based on where you should be in the animation based on time, not based on how many times you've animated
Use requestAnimationFrame so your update synchronizes with the browser's refresh
Here's your code updated to do that, see comments:
// Tell in-snippet console to keep all lines (rather than limiting to 50)
console.config({maxEntries: Infinity});
var value = 212.45,
time = 2.55, // in seconds
time_in_ms = time * 1000,
amount_per_ms = value / time_in_ms,
interval = 100 / 6, // in milliseconds, ~16.66ms is a better fit for browser's natural refresh than 20ms
ticks_made = 0;
// A precise way to get relative milliseconds timings
var now = typeof performance !== "undefined" && performance.now
? performance.now.bind(performance)
: Date.now.bind(Date);
// Remember when we started
var started = now();
// Because of the delay between the interval timer and requestAnimationFrame,
// we need to flag when we're done
var done = false;
// Use the interval to request rendering on the next frame
var intId = setInterval(function() {
requestAnimationFrame(render);
}, interval);
// About half-way in, an artificial 200ms delay outside your control interrupts things
setTimeout(function() {
console.log("************DELAY************");
var stop = now() + 200;
while (now() < stop) {
// Busy-loop, preventing anything else from happening
}
}, time_in_ms / 2);
// Our "render" function (okay, so we just call console.log in this example, but
// in your real code you'd be doing a DOM update)
function render() {
if (done) {
return;
}
++ticks_made;
var elapsed = now() - started;
if (elapsed >= time_in_ms) {
console.log(ticks_made, "done");
done = true;
clearInterval(intId);
} else {
var current_value = value - (amount_per_ms * elapsed);
console.log(ticks_made, current_value);
}
}
/* Maximize in-snippet console */
.as-console-wrapper {
max-height: 100% !important;
}
If you run that, then scroll up to the "************DELAY************" line, you'll see that even though rendering was held up by "another process", we continue with the appropriate next value to render.
It would make sense to convert the result of .toFixed() to a number right away:
let amount_per_tick = +(value / amount_of_ticks).toFixed(5);
let value = +(value - amount_per_tick).toFixed(5);
(note the + signs)
Then you will never have to worry about type coercion or anything, and instead just focus on math.
$("#team").css("background-color","blue");
I'd like to turn the background colour of id:team to blue however I would only like to turn it this colour for 4 seconds.
How do I do this?
I've Googled around but I couldn't find anything regarding changing css for a given time frame.
Also, a fading out/in from/to the previous settings would be a nice touch.
If you want it to only appear blue for 4 seconds you could do:
var element = $( "#team" );
var oldColor = element.css( "background-color" );
element.animate( { "background-color": "blue" } )
.delay( 4000 )
.animate( { "background-color": oldColor } );
You need jQuery UI to .animate(), otherwise you can just use .css().
You have to use the timer feature:
setTimeout(function() {
$('#team').css('background-color', 'whatever');
}, 4000);
The second argument is a count in milliseconds of how long you'd like to wait before the first argument (a function) is called.
There's no built-in ability to say "go back to what it was before"; you'll have to remember the old value in your own code.
If you do not want to use the jQuery UI plugin (perhaps due to its size) then you could do the animating manually.
See the following code working in a jsFiddle.
function animblue(selector, from, to, step) {
var
target = $('#target'),
color = from,
next;
next = function () {
var hex = (Math.floor(color) < 16 ? '0' : '') + Math.floor(color).toString(16);
target.css('background-color', '#' + hex + hex + 'ff');
color += step;
if (!((color < from && color < to) || (color > from && color > to))) {
setTimeout(next, 10);
}
};
next();
}
$('#action').on('click', function () {
animblue('#target', 255, 0, -255 / 16);
window.setTimeout(function () {
animblue('#target', 0, 255, 255 / 16);
}, 4000);
});
I have a problem with jQuery-UI sliders ( http://jqueryui.com/demos/slider/#default ).
I've created a couple of them on my website, and there is DIV with "points" (I entered there 1500). Each slider after increasing takes points from the DIV, so - when I increase one slider from 0 to 100, there is only 1400 points in DIV left. When I increase another slider, there will be less points in the DIV. If I decrease one of the sliders, these points will be added to the DIV - this works great.
But... I need to disable increasing value of the sliders after points in DIV gains 0 (there can't be values less than 0), decreasing must be still available (so .slider({ disabled: true }) is not an option). I've tried almost everything, and nothing works...
JS code:
$("#addMapPage").ready(function() {
$("#addMap").click(function(){ checkMap() });
$(".vChanger").slider({ step: 10,
max: 1500,
slide: function(event, ui) { checkValues() },
stop: function(event, ui) { checkValues() }
});
checkValues();
});
function getValues() {
var data = new Array();
data['water'] = $("#water").slider("value");
data['steppe'] = $("#steppe").slider("value");
data['swamp'] = $("#swamp").slider("value");
data['desert'] = $("#desert").slider("value");
data['forest'] = $("#forest").slider("value");
data['mountain'] = $("#mountain").slider("value");
data['hill'] = $("#hill").slider("value");
return data;
}
function checkValues() {
var slidersValues = getValues(); //getting slider values
var sum = 0;
var pula = parseInt($("#pula").text()); //points to use (now can be less than 0)
if (pula < 0){
$(".vChanger").slider({ range: pula });
}
for (value in slidersValues) {
sum += slidersValues[value];
$("#"+value+"Info").text(slidersValues[value]);
}
var pula = 1500-sum;
$("#pula").text(pula);
}
HTML:
<div id="addMapPage">
<ul>
<li><span>Woda:</span><div class="vChanger" id="water"></div><span id="waterInfo"><span/></li>
<li><span>Step:</span><div class="vChanger" id="steppe"></div><span id="steppeInfo"><span/></li>
<li><span>Bagno:</span><div class="vChanger" id="swamp"></div><span id="swampInfo"><span/></li>
<li><span>Pustynia:</span><div class="vChanger" id="desert"></div><span id="desertInfo"><span/></li>
<li><span>Las:</span><div class="vChanger" id="forest"></div><span id="forestInfo"><span/></li>
<li><span>Góry:</span><div class="vChanger" id="mountain"></div><span id="mountainInfo"><span/></li>
<li><span>Wzgórza:</span><div class="vChanger" id="hill"></div><span id="hillInfo"><span/></li>
<li><span>Nazwa mapy:</span><input type="text" id="mapName"></input></li>
</ul>
<p id="mapInfo"><span id="pula"></span>Points to use</p>
<input type="button" id="addMap" value="create map"></INPUT>
</div>
Here is example (3 screenshots):
Any ideas, how to limit points (don't let them to be less than 0)?
You'll find that adjusting the max values of your sliders is probably not a very desirable solution. It will change the scale of the increments on your slider and not be very representative of what portion of the whole the current slider value represents. Instead, intercept and prevent the slide event if the current sum of all other sliders plus the value for this event exceed the max. For instance:
var maxSum = 1500;
var $sliders = $(".slider").slider({
value: 0,
min: 0,
max: 1500,
step: 10,
slide: function(event, ui) {
var sum = 0;
// Don't add the value for this slider;
// We need to add the new value represented by the slide event
$(".slider").not(this).each(function() {
sum += $(this).slider("value");
});
// Add the slide event value
sum += ui.value;
if (sum > maxSum) event.preventDefault();
else $(this).next().html("Value: " + ui.value);
}
});
The only thing this is prone to is not pushing the values all the way to the max if the user is sliding very rapidly by large increments. See a working demo here.
You're already storing the available points in "pula".
Whenever a user adjusts a slider, all the remaining sliders need to be changed so that the user can't allocate more than the remaining points. You do this by setting the .slider() MAX option to the current value of the slider + the remaining available points.
for (value in slidersValues) {
var currentValue = slidersValues[value];
var newMax;
//If max + available is > 1500, we set it to 1500,
//otherwise we add the available points to the current values
if ( currentValue + pula >= 1500) {
newMax = 1500;
} else {
newMax = currentValue + pula;
}
//jQuery allows you to reset the 'max' value for a slider
$('#'+value).slider( "option" , max , newMax );
}