DC.js - trigger elastic resize when criteria are met - javascript

I would like to trigger elastic resizing on outliers in certain graphs when the largest values are removed. Here's what I have so far:
dmsn = ndx.dimension(...)
groupTotal = dmsn.group().reduceSum(...)
row
.width(100)
.height(100)
.dimension(dmsn)
.group(groupTotal)
document.addEventListener('mouseup',function(){
setTimeout(function(e){
newTop = groupTotal.top(1)[0].value;
ratio = newTop / origTop;
if(ratio < .1){
row.elasticX(true);
dc.redrawAll('groupTotal');
} else {
row.elasticX(false);
dc.redrawAll('groupTotal');
}
},1);
}, true);
The function checks to see whether the new highest value is sufficiently small that a readjustment is useful.
The timeout ensures that the ratio is correctly calculated after the filters are applied. However, this also means that the opportunity to apply the new elasticity has passed. Unfortunately the redrawAll doesn't appear to work here.
Any advice on fixing this implementation or achieving this some other way?

Here's how to achieve this, for anyone curious. This results in conditional scaling of axes when the largest current bar is too small (or large) to be valuable.
var currentMax = 0,
ratio,
chartMax = groupData.top(1)[0].value; // initialize with largest value
row
.on('postRedraw', function(chart){
currentMax = groupData.top(1)[0].value; // after redraw, capture largest val
ratio = currentMax/chartMax;
if(ratio < .1 || ratio > 1){ // check if bars are too small or too large
row.elasticX(true);
chartMax = currentMax; // always be sure to reset the chartMax
dc.redrawAll();
} else {
row.elasticX(false);
chartMax = currentMax;
}
});

Related

Don't understand what this code does

So I know this code is randomly removing to of my objects to create a hole so another object can go through but line by line I would like to go through and understand each part. Would be great if someone not so arrogant could help me because I'm new. I would appreciate any help. The area I don't understand is the last part which I have highlighted in bold. Thank you.
// Add a pipe on the screen
add_one_pipe: function(x, y) {
// Get the first dead pipe of our group
var pipe = this.pipes.getFirstDead();
// Set the new position of the pokeballs
pipe.reset(x, y);
// Add velocity to the pokeballs to make it move left
pipe.body.velocity.x = -200;
// Kill the pokeballs when it's no longer visible
pipe.outOfBoundsKill = true;
},
**add_row_of_pipes: function() {
var hole = Math.floor(Math.random()*5)+1;**
**for (var i = 0; i < 8; i++)
if (i != hole && i != hole +1)
this.add_one_pipe(400, i*60+10);**
add_row_of_pipes will add 6 pipes at fixed intervals heights, but with a randomly placed gap of 2 missing pipes.
var hole = Math.floor(Math.random()*5)+1;
Take a random number (between 0 and 0.999),
Multiply by 5 (possible range is now 0-4.9999...),
Round down (0-4),
Add one (1-5)
This value is where the hole is
for (var i = 0; i < 8; i++)
For the whole numbers 0 to 7 inclusive, representing height, i...
if (i != hole && i != hole +1)
If this height is not where the hole starts, nor the next value,
this.add_one_pipe(400, i*60+10);
Add a pipe at width 400, and height i*60+10.

Efficient multi-chart drag selection for highcharts

I've used Highcharts documentation to enable a drag selection for a chart. I'm trying to expand this so that whatever points I've selected on my first chart, will also be selected on the other charts on the page (the x axis matches).
I've figured out a method to do this, & it works well for 2 or 3 highcharts on a page, but it gets some severe lag when i select many points (each chart is about 1500 points total) with 4 or more highcharts.
It could be because I use a nested for loop, but I was wondering if anyone had any tips for improving the efficiency of this code (or if this is even the issue). The first function is the one provided in the Highcharts documentation; the second function (updateCharts()) is the one I did.
I've never used highcharts, or programmed much in javascript before, so I'm still a bit unfamiliar with how all this works.
function selectPointsByDrag(e) {
// Select points
Highcharts.each(this.series, function (series) {
Highcharts.each(series.points, function (point) {
if (point.x >= e.xAxis[0].min && point.x <= e.xAxis[0].max &&
point.y >= e.yAxis[0].min && point.y <= e.yAxis[0].max) {
point.select(true, true);
}
});
});
// Fire a custom event
Highcharts.fireEvent(this, 'selectedpoints', { points: this.getSelectedPoints() });
updateCharts(this);
return false; // Don't zoom
};
function updateCharts(curr_chart){
var count;
var counter;
var allChartArray = [chart, chart1, chart2, chart3];
var indexOfCurrentChart = allChartArray.indexOf(curr_chart);
if (indexOfCurrentChart > -1) {
allChartArray.splice(indexOfCurrentChart, 1);
};
bla = curr_chart.getSelectedPoints();
for(count = 0; count < allChartArray.length; count++){
for(counter = 0; counter < bla.length; counter++){
allChartArray[count].series[0].data[bla[counter].index].select(true, true)
};
};
};

D3 Elastic easing transition of circle radius causes negative radius to be set

I have a very weird problem whilst using the ease("elastic", a, p) function in d3js. I am transitioning the radius of circles and I'm getting a flood of errors in the console, which are significantly slowing down my webpage. The errors state that I'm setting a negative radius — after some debugging (from console logging everything to diffs with previous code) I found out that's caused by the elastic easing. Here's my code:
function redraw() {
var day = d3.select('input[name="day"]:checked').node().value;
var time = d3.select('input[type="range"]').node().value.toString();
var direction = d3.select('input[name="direction"]:checked').node().value;
// fix bug in which data for single digit hours is not displayed
if (time.length == 1) {time = "0" + time;}
if (circles !== undefined) {
circles.transition().duration(900).ease('elastic', 1, 0.75).attr({
fill: function(d) {
return shadeColor("#ff0000", (-1 * getCircleSize(d.data, day, time, direction)));
},
r: function(d) {
var size = getCircleSize(d.data, day, time, direction);
if (size <= 0) {
return 0;
} else if (size > 0 && size < 2) {
return size + 2;
} else if (size > 50) {
return size*0.1 + 50;
} else {
return size;
}
}
});
}
}
And here is an example of the error I'm getting:
Error: Invalid negative value for <circle> attribute r="-0.04404809159933931"
(anonymous function)
# d3.v3.min.js:5l
# d3.v3.min.js:3Lt
# d3.v3.min.js:1qt
# d3.v3.min.js:1
If I change the line:
circles.transition().duration(900).ease('elastic', 1, 0.75).attr({...});
to:
circles.transition().duration(900).ease('quad').attr({...});
...it doesn't spit out any errors in the console.
Here's a screenshot as well:
It would be great if you can give me some guidance on how to solve this problem. Thanks in advance!
Check the spec on d3.ease():
elastic(a, p) - simulates an elastic band; may extend slightly beyond 0 and 1.
Because the elastic easing overshoots the range [0,1] a little, it is not useful for lengths which must not be negative like radius, width etc. As you already noticed there are other easing functions which are guaranteed to yield positive values only. Have a look at this overview on easing function to find a function which will stay within the range of [0,1].

Assign index # according to current section

Say I have a total width of 585px. And I wanted to divide the space into equal sections and assign each an index value within position. I could do something like this if I had lets say 6 sections: (assigned by total width / number of sections)
//Set up elements with variables
this.sliderContent = config.sliderContent;
this.sectionsWrap = config.sectionsWrap;
//Selects <a>
this.sectionsLinks = this.sectionsWrap.children().children();
//Create drag handle
this.sectionsWrap.parent().append($(document.createElement("div")).addClass("handle-containment")
.append($(document.createElement("a")).addClass("handle ui-corner-all").text("DRAG")));
//Select handle
this.sliderHandle = $(".handle");
var left = ui.position.left,
position = [];
var position = ((left >= 0 && left <= 80) ? [0, 1] :
((left >= 81 && left <= 198) ? [117, 2] :
((left >= 199 && left <= 315) ? [234, 3] :
((left >= 316 && left <= 430) ? [351, 4] :
((left >= 431 && left <= 548) ? [468, 5] :
((left >= 549) ? [585, 6] : [] ) ) ) ) ) );
if (position.length) {
$(".handle").animate({
left : position[0]
}, 400);
Slider.contentTransitions(position);
}
But what if I had an x number of sections. These sections are just elements like
<li><a></a></li>
<li><a></a></li>
<li><a></a></li>
Or
<div><a></a></div>
<div><a></a></div>
<div><a></a></div>
<div><a></a></div>
How would I divide the total of 585px and classify the index in position according to the current left value of the .handle element? I can know where the drag handle is by using ui.position.left, what I want is to be able to set an index for each element and be able to animate handle depending on where the handle is within the indexed elements. Since each element is indexed I later call a transition method and pass in the current index # to be displayed. The code I show above works, but isn't really efficient. I also need to account for the width of the handle to fit the section width. http://jsfiddle.net/yfqhV/1/
Ok, there is a slight inconsistency in the difference between the range figures in the question, which makes it hard to algorithmise [ my made-up-word de jour =) ] this exactly:
81 to 199 = 118
199 to 316 = 117
316 to 431 = 115
431 to 518 = 118
If you can adjust for that, I have a solution - it's not especially clever JavaScript, so there may well be better ways to do this (SO JS people, feel free to educate me!) but it works.
First we need a function to find the index of an array range, a given value falls within (this replaces your nested if-else shorthands), then we have a function to set up the positional arrays, and finally we can do a range search and return the corresponding array of values.
This solution should dynamically deal with a varying number of sections, as long as this line:
var len = $("#sectionContainer").children().length;
is adjusted accordingly. The only other values that may need adjusting are:
var totalWidth = 585;
var xPos = 81;
although you could set them if you have elements you can draw the values from, making it even more of a dynamic solution.
/**
* function to find the index of an array element where a given value falls
* between the range of values defined by array[index] and array[index+1]
*/
function findInRangeArray(arr, val){
for (var n = 0; n < arr.length-1; n++){
if ((val >= arr[n]) && (val < (arr[n+1]))) {
break;
}
}
return n;
}
/**
* function to set up arrays containing positional values
*/
function initPositionArrays() {
posArray = [];
leftPosArray = [];
var totalWidth = 585;
var xPos = 81;
var len = $("#sectionContainer").children().length;
var unit = totalWidth/(len - 1);
for (var i=1; i<=len; i++) {
pos = unit*(i-1);
posArray.push([Math.round(pos), i]);
xMin = (i >= 2 ? (i==2 ? xPos : leftPosArray[i-2] + posArray[1][0]) : 0);
leftPosArray.push(Math.round(xMin));
}
}
var left = ui.position.left;
initPositionArrays();
// find which index of "leftPosArray" range that "left" falls within
foundPos = findInRangeArray(leftPosArray, left);
var position = posArray[foundPos];
if (position.length) {
$(".handle").animate({
left : position[0]
}, 400);
Slider.contentTransitions(position);
}
I've set up a jsFiddle to illustrate.
Enjoy!
Edit
I've looked at #JonnySooter s own answer, and whilst it calculates the positioning correctly, it won't deal with a variable number of sections.
To get it to work with any number of sections, the handleContainment div (that is created on-the-fly) needs to have it's width set dynamically (via inline styling).
This is calculated by multiplying the number of sections by the width of each section (which is actually the same as the width of the slider).
This is all done after creating the handle so that the width can be extracted from the "handle" css class, meaning a change to the width of the handle will cascade into the routine when applied at the css level.
See this jsFiddle where the number of sections can be altered and the slider behaves properly.
var numSections = // ...;
var totalWidth = // ...;
var sectionWidth = totalWidth / numSections;
var index = Math.floor($(".handle").position().left / sectionWidth);
var leftPosition = index * sectionWidth;
var rightPosition = leftPosition + sectionWidth - 1;
UPDATE:
I worked on trying to find a solution myself and this is what I came up with:
function( event, ui ) {
var left = ui.position.left, //Get the current position of the handle
self = Slider, //Set to the Slider object cus func is a callback
position = 1;
sections_count = self.sectionsLinks.length, //Count the sections
section_position = Math.floor(self.sectionsWrap.width() / sections_count); //Set width of each section according to total width and section count
left = Math.round(left / section_position); //Set the index
position = (left * section_position); //Set the left ammount
if(position < section_position){ //If handle is dropped in the first section
position = 0.1; //Set the distance to animate
left = 0; //Set index to first section
}
if (position.length) {
$(this).animate({
left : position //Animate according to distance
}, 200);
left = left += 1; //Add one to the index so that I can use the nth() child selector later.
self.contentTransitions(left);
}
}

Javascript animate math

I'm struggling to get my head around such simple Math, well at least it seems it should be simple.
I'm basically trying to mirror what jQuery's .animate does, but to no luck.
Here's a simplified version of what I have so far:
var args = {
speed: 1000, // 1 second.
left: 65 // distance.
}, rot, step;
// Terrible math.
rot = step = (((args.left / args.speed) * 10) - 0.10);
var t = setInterval(function() {
if(elem.style.left >= args.left) {
clearInterval(t);
return;
}
rot += step;
elem.style.left = rot;
}, 10);
Please excuse any illogical code (or math), I've been messing around for a good few hours and totally lost my sanity.
Edit:
Here's the way I would do it.
var start_time = Date.now();
// Get the starting time in milliseconds
var t = setInterval(function() {
var delta_time = Date.now() - start_time;
// Get time that has elapsed since starting
if (delta_time >= 1000) {
// if it's been a second
clearInterval(t);
// Stop the timer
elem.style.left = args.left + 'px';
// Set the element to exactly the value it should be (avoids having it set to a float value)
return;
}
elem.style.left = delta_time * args.left / args.speed + 'px';
// Move the element according to how much time has elapsed
}, 10);​
This method has a few advantages. For example, you can adjust the interval to make it more or less smooth, and it won't mess up the animation.
The reason why your solution was taking longer than one second is because of how you used setInterval. setInterval doesn't account for the time your code takes to run, so the total time is always increased by a bit. You can fix this by using delta timing (like in my example).
Try using useing sin and cos to calculate rotation Some what like this
newx = distance * Math.cos(direction) + x
newy = distance * Math.sin(direction) + y
Not sure , this will solve your problem I guess you want to to do a smooth rotation
Try making it as a function it will work , I am not seeing any problem in your math ,
like this
function move(elem) {
var left = 0
function frame() {
left++ // update parameters
elem.style.left = left // show frame
if (left == 100) // check finish condition
clearInterval(id)
}
var id = setInterval(frame, 10) // draw every 10ms
}
Well for one it should be
var args = { ... }
assuming you have the elem set up correctly, you're going to need a inline styling of the attribute you want to animate. Also, you're going to need to parse the style since it has the 'px' attached to it, but you can always add that after you do the math within the interval function.
I set up something here so you can mess around with the settings and whatnot.
edit:
http://jsfiddle.net/mb4JA/2/
edit2:
this should be one second
http://jsfiddle.net/mb4JA/4/
final answer ;) http://jsfiddle.net/mb4JA/10/
You should be able to put any speed in there, and have it animate for that amount of seconds.

Categories

Resources