Can d3 intelligently choose an appropriate number format? - javascript

I have a chart that displays the frequency of names in the population over time, just one name at a time that the user selects. For, say, "John", the maximum value is 0.08 (8% of all names), so it's no problem to display the percents on the y axis. For the name "Stevens," meanwhile, the peak value is 0.000014984.
I don't want to have a 60px left margin if I can avoid it, so I'd prefer for these small numbers to be in scientific notation.
Right now I have a hacky solution:
y.tickFormat(function(d) {
if (d == 0) {
return 0;
}
if (d > 0.001) { // arbitrary cutoff
return d3.format(",.1%")(d);
}
return d3.format(".2e")(d * 100) + "%";
});
Is there a cleaner way to do this, where numbers from 1-99, for example, don't get "e0" appended?

Related

Javascript, rounding and calculating values against original values

I'm trying to calculate a series of values which should aggregate to the same as the Original Value.
If you look at the above example, you can see that the aggregated cost and billed figures are aggregating to 239.99 and 219.98 respectively. They obviously need to aggregate to 240 and 220 respectively.
The cost and billed figures are calculated when Hours changes (by using a jQuery on change function). The multiplier is determined by dividing the Original Value Hours by the number specified in the Calculated Values Hours below.
(0.2 / 0.7) = 0.28571428571428575
(0.3 / 0.7) = 0.4285714285714286
Self-evidently, the figures need to aggregate to the Original Values.
This is an issue no doubt to do with floating point precision and rounding and I've tried using the following function without success:-
function to2DP(num, fixed) {
var re = new RegExp('^-?\\d+(?:\.\\d{0,' + (fixed || -1) + '})?');
return num.toString().match(re)[0];
}
$("#splitTimeContainer").on("change", ".changeHours", function(e) {
var splitID = $("#splitID").val();
var splitOrigCost = $("#splitOrigCost").val();
var splitOrigBilled = $("#splitOrigBilled").val();
var splitOrigHours = $("#splitOrigHours").val();
var i = 1;
var divideMe = $(this).val();
var coEfficient = (divideMe/splitOrigHours);
var newCost = to2DP((coEfficient * splitOrigCost), 2);
var newBilled = to2DP((coEfficient * splitOrigBilled), 2);
var parent = $(this).parent().parent();
parent.find('.changeCost').val(newCost);
parent.find('.changeBilled').val(newBilled);
});
As well as using toFixed, but again without success.
Anyone have any suggestions at how I go about achieving this?
This question is more about rounding and mathematics.
I recommend to use different rounding to even. This rounding at least has no bias so it will not systematically increase or decrease values.
Another problem is that even with this rounding there is no guarantee that sum of values will match due to odd number of calculated values.
In this particular example this rounding will achieve cost 240 and billed 220.01.
This problem in my opinion has not optimal solution.
Consider this:
Hours: 0.9, Cost 100
hours: 0.3, cost 33.33
hours: 0.3, cost 33.33
hours: 0.3, cost 33.33
There is no way how to make this working.
You might want to consider adding the amount lost during division to one or more values.
Splitwise also does that to ensure no loss of value.

Where is my logic going wrong in trying to calculate the presidential outcome?

Let me explain what I'm trying to do. I have data like
const dataByState = {
'Washington' : { ElectoralVotes : 12, RChance: 54, DChance: 46 },
'Oregon': { ElectoralVotes: 7, RChance: 51, DChance: 49 },
.
.
.
'Hawaii' : { ElectoralVotes: 4, RChance : 40, DChance: 60 }
};
where one of the above key-value pairs like
'Hawaii' : { ElectoralVotes: 4, RChance : 40, DChance: 60 }
means "In the state Hawaii, which has 4 electoral votes, there is a 40% chance of the Republican Candidate winning and a 60% chance of the Democrat candidate winning". What I'm ultimately trying to do is calculate the chance of each candidate winning the election. How this would be done in a perfect world is
Iterate through all 2^51 combinations of states
For each combination c, its combined electoral votes are greater than or equal to 270, add it to a collection C of collecions of states
For the Republican candidate, sum up the probabilities of winning each combination of states in C; call that value r. That's his/her chance of winning. The Democrat's chance is 1 - r.
But since I can't go through all 2^51, what I'm doing is choosing some N smaller than 51 and doing
Find a random 2^N combinations of states whose combined electoral votes sum to greater than or equal to 270; call this combination C.
For the Republican candidate, sum up the probabilities of winning each combination of states in C; call that value r. Multiply r by 2^(51-N). That's approximately his/her chance of winning. The Democrat's chance is 1 - r.
Anyhow, this doesn't seem to be working and I'm wondering whether my logic is wrong (I haven't taken statistics since college 3 years ago) or if I'm running into rounding errors. I'm getting a near 100% of the Republican winning (i.e. America being made great again) when I make the chance even in every state, which is wrong because it should calculate to about 50/50.
Code dump: https://jsfiddle.net/pqhnwek9/
The probability of a republican victory is
probRepVict = 0
for(combination in combinations) {
if(combination is republican victory) {
probRepVict += proability of combination
}
}
As you observe it is not feasible to calculate the entire sum. Hence, you choose some subset C to try to estimate this probability.
N = number of combination // 2^51
n = size of C
probRepVictEstimate = 0
for(combination in C) {
if(combination is republican victory) {
probRepVictEstimate += proability of combination
}
}
probRepVictEstimate *= N/n
In the last statement we assume that the probability of a victory scales linearly with the size of the subset.
I believe the logic goes wrong at several places in the script:
(1) When generating the random number you might not get a sufficiently many bits of randomness. For instance if there were 54 states you would be outside of the safe integer range. Some implementations might give you even less fewer bits of randomness (it did break for me in Node, which only give 32 bits). Thus I suggest adding a function
function getRandom() {
// Generate 32 random bits
var s = Math.floor(Math.random()*Math.pow(2, 32)).toString(2)
return new Array(32 - s.length + 1).join("0") + s
}
Replacing
const rand = Math.floor(Math.random() * Math.pow(2,states.length));
with const rand = getRandom() + getRandom();, and replace getCombo with
const getCombo = (i) => {
let combo = [];
for(var j = 0; j < states.length; ++j)
if(i[j] == "0")
combo.push(states[j]);
return combo;
}
(2) You need to count both wins and losses for the republican party to be able to estimate the probability. Thus you cannot add the complement of a combo (by the way, ~ is a bitwise operations, hence convert the operand to a 32-bit integer, so your code does not work as intended). Hence your code should be simplified to:
...
if(!winningCombos.hasOwnProperty(rand)) {
const stateCombo = getCombo(rand);
if(hasSufficientVotes(stateCombo))
{
winningCombos[rand] = stateCombo;
++wins;
}
++count;
}
...
(3) You should scale repubChanceSum by N/n, where N = Math.pow(2, 51) and n = limit. Note that limit should be considerably greater than winningCombos.length.
With these modifications the code correctly predicts a ~50% probability. See this modified fiddle.
Let's hope we get a more optimistic outlook for the future with more realistic probabilities.

HTML Canvas game: 2D collision detection

I need to program a very simple 2D HTML canvas game with a character and a few walls. The map (top view) is a multidimensional array (1=walls)
map = [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
[1,0,0,1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,1,0,0,0,0,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,1],
[1,0,0,1,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1],
[1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,1],
[1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1,1,0,1,1],
[1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,1,1,0,1,1],
[0,0,0,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,1,0,1,1],
[1,1,1,0,1,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,1,0,1,1],
[1,1,1,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,0,0,0,0,1,1],
[1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,1,0,0,1,1],
[1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
];
The character shouldn't be able to walk over the walls... so he should only walk on the "0"s. I already got the map rendering and the walking part of the character working just fine, but I can't quite figure out how to check for collisions yet.
A very simple version can be found on JSBin. You can either use the arrow keys or WASD to move around (black square).
I already tried to do a very simple collision detection by using something like this:
function checkCollision( x, y ) {
if ( map[ Math.round( x ) ][ Math.round( y ) ] !== 0 ) {
return true; // Collision
}
return false;
}
But this doesn't quite work (see JSBin). With Math.round the character and wall overlap... if I use Math.ceil or Math.floor it's even worse.
Is there any way that I can improve this "collision detection", so that the character can't walk over the red walls?
There are a few problems:
First you should avoid using 0.1 as coordinate step because it's a "bad number" in floating point (it's periodic when expressed in binary). Much better is 0.125 (1/8). Adding/substracting 1/8 will guarantee your numbers will always remain exact multiples of 1/8, not accumulating any error.
You should define an "ok(x, y)" function checking if the (possibly fractional) point (x, y) is valid or inside a wall... a simple implementation could be:
return map[y|0][x|0] == 0; // <expr>|0 is used to convert to integer
Finally you should compute new_charX and new_charY and only accept moving from charX, charY to the new position if all four points:
(new_charX+s, new_charY+s)
(new_charX+1-s, new_charY+s)
(new_charX+s, new_charY+1-s)
(new_charX+1-s, new_charY+1-s)
are valid with s = 1/16 (i.e. half of the moving step).
Example: http://jsbin.com/wipimidije/edit?js,output
Your player can potentially be overlapping four squares in the map at any time. So you need to check for collision in all four squares (corresponding to the top-left, top-right, bottom-left, bottom-right corners of the character). To allow it to 'squeeze' through corridors (given that the character is the same size as a tile) you may also need to adjust this by one or two pixels (hence the 1 / config.tileSize in the following).
function checkCollision( x, y ) {
var x1 = Math.floor(x + 1 / config.tileSize),
y1 = Math.floor(y + 1 / config.tileSize),
x2 = Math.floor(x + 1 - 1 / config.tileSize),
y2 = Math.floor(y + 1 - 1 / config.tileSize);
if (map[y1][x1] !== 0 || map[y2][x1] !== 0 || map[y1][x2] !== 0 ||
map[y2][x2] !== 0) {
return true; // Collision
}
return false;
}
See this version of the JSBin
My answer is a little different. Instead of having a 2D array I'd use a simple array and calculate the rows (if you even need them). Now you only have to check the map array at the one index that is the position of the actor:
var map = [0,0,0,1,1,0,0,1,1,0,0,...];
//heres how to calculate the x/y if you need it for something else
var pos_x = position % NUM_OF_ROWS;
var pos_y = Math.floor(position / NUM_OF_ROWS);
//for collisions now you just check the value of the array at that index
leftKey.addEventListener('keypress', function() {
test(position - 1);
});
rightKey.addEventListener('keypress', function() {
test(position + 1);
});
upKey.addEventListener('keypress', function() {
test(position + NUM_OF_ROWS);
});
downKey.addEventListener('keypress', function() {
test(position - NUM_OF_ROWS);
});
function test(n) {
if (map[n] === 0) {
//if there's no collision, update the position.
position = n;
} else {
console.log('Collided!');
}
}
You need to consider two aspects: the first is collision detection, the second is response. Let's start with the detection. You are checking a single point, but in reality you have a tile size, so there is thickness which you must consider. The coordinate of the character, and the coordinate of your tiles is the top-left corner. It is not sufficient to compare the top-left corners, you must also check the other corners. The right-hand side of the player's square for example is at charX + config.tileSize.
The second aspect is the collision response. The simplest mechanism you can use here is to check the next position of the character for collisions, and only move the character if there are none. You should preferably check the two axes separately to allow the character to "slide" along walls (otherwise it till get stuck in walls in moving diagonally into the wall).
First of all I would change the tiles "value" if you change the character to walk in 1's but in 0's you can check if a tile is walkable by typing
If(tile[x][y])...
Then, I would, first calculate the next position and then make the move if the player is able to...
Var nextpos = new position;
If(KEYLEFT){
Nextpos.x = currpos - 1;
}
If(nextpos > 0 && nextpos < mapsize && tile[nextpos.x][nextpos.y])
Player.pos = nextpos;

nvd3 tickValues() not drawing ticks close to boundary values

I am drawing a line chart using nvd3 and specifying a set of xAxis values to draw ticks for using:
chart.xAxis.tickValues(tickArr)
where tickArr has the list of points to draw ticks for.
For some reason, the ticks close to the beginning or the end values for the x axis are not being drawn. I am guessing this is because of some default setting for the boundary value ticks but am not able to find a way to override it and show all specified ticks.
Here is the link to the fiddle . You can see that though tickArr has 7 data points, only 3 ticks are shown.
Any help as to which parameter should I change or why this is happening would be really appreciated.
I modified the source to get around this situation.
In the nv.models.axis(), there is a buffer given when showMaxMin is true for a bottom/top orientation:
if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
var maxMinRange = [];
wrap.selectAll('g.nv-axisMaxMin')
.each(function(d,i) {
try {
if (i) // i== 1, max position
maxMinRange.push(scale(d) - this.getBoundingClientRect().width - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
else // i==0, min position
maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
}catch (err) {
if (i) // i== 1, max position
maxMinRange.push(scale(d) - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
else // i==0, min position
maxMinRange.push(scale(d) + 4);
}
});
// the g's wrapping each tick
g.selectAll('g').each(function(d, i) {
if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
d3.select(this).remove();
else
d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
}
});
}
I just removed these buffers:
try {
if (i) // i== 1, max position
maxMinRange.push(scale(d));
else // i==0, min position
maxMinRange.push(scale(d))
}catch (err) {
if (i) // i== 1, max position
maxMinRange.push(scale(d));
else // i==0, min position
maxMinRange.push(scale(d));
}
There is another post talking about a similar issue. The solution posted there is to remove the boundary ticks but this seems like the wrong approach as the boundary ticks are very helpful when considering the perceptual aspects of the visualization. I hope this answer helps someone who faces a similar situation in the future.

Create unique colors using javascript

What is the best way to pick random colors for a bar chart / histogram such that each color is different from the other.. and possibly in contrast
The most talked about way is
'#'+(Math.random()*0xFFFFFF<<0).toString(16);
but this can generate similar colors.. and sometimes distinguishing them might be a problem..
Example
I would generate colors using HSV (hue, saturation, value) instead of RGB. In HSV, the color is defined by the hue, ranging from 0-360. Thus, if you want e.g. 6 different colors, you can simply divide 360 by 5 (because we want to include 0) and get 72, so each color should increment with 72. Use a function like this one to convert the generated HSV color to RGB.
The following function returns an array of total different colors in RGB format. Note that the colors won't be "random" in this example, as they will always range from red to pink.
function randomColors(total)
{
var i = 360 / (total - 1); // distribute the colors evenly on the hue range
var r = []; // hold the generated colors
for (var x=0; x<total; x++)
{
r.push(hsvToRgb(i * x, 100, 100)); // you can also alternate the saturation and value for even more contrast between the colors
}
return r;
}
The best way is to convert from HSV values. You can divide the maximum value of "Hue" by the amount of colors you need and then increment by this result.
For improved contrast, you can also alternate between high and low values of lightness.
The existing answers which mention the Hue, Saturation, Value representation of colors are very elegant, are closer to how humans perceive color, and it is probably best to follow their advice. Also creating a long precalculated list of colors and choosing subsets of them as needed is fast and reliable.
However, here is some code that answers your question directly: it will generate random colors in RGB that are sufficiently different. There are two drawbacks to this technique that I can see. First, these colors are really random and could look kind of gross together, and second it might take a while for the code to stumble on colors that work, depending on how "far apart" you require the colors to be.
function hex2rgb(h) {
return [(h & (255 << 16)) >> 16, (h & (255 << 8)) >> 8, h & 255];
}
function distance(a, b) {
var d = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
return Math.sqrt((d[0]*d[0]) + (d[1]*d[1]) + (d[2]*d[2]));
}
function freshColor(sofar, d) {
var n, ok;
while(true) {
ok = true;
n = Math.random()*0xFFFFFF<<0;
for(var c in sofar) {
if(distance(hex2rgb(sofar[c]), hex2rgb(n)) < d) {
ok = false;
break;
}
}
if(ok) { return n; }
}
}
function getColors(n, d) {
var a = [];
for(; n > 0; n--) {
a.push(freshColor(a, d));
}
return a;
}
The distance between colors is the Euclidean distance measured by the R, G, and B components. Thus the furthest that two colors (black and white) can be is about 441.67.
To use this code, call getColors where the first parameter is the number of colors, and the second is the minimum distance between any two of them. It will return an array of numerical RGB values.
I like using hsl values for specifying colour this way.
So
"color: hsl(" + getRandomArbitary(0, 360) + ", 50%, 50%)";
would give you random results, but that won't give you your distinct separations. So I'd base it on the i value of a loop. Something like,
for (var i = 0; i < whateverYourValue; i += 1) {
color = "color: hsl(" + i * 10 + ", 50%, 50%)";
// set your colour on whatever
}
obviously the above is indicative, and not valid code on it's own.
Want to know more on hsl? Check http://mothereffinghsl.com/ 'cause, you know, it's fun.
'#'+(Math.random()*0xFFFFFF<<0).toString(16);
Isn't the best method to use because it can generate values like #4567 which is missing two digits instead of generating #004567
It's better to pick each character individually like:
'#'+Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16);
But that can easily be reduced to picking three numbers since hex colours can be shortened. IE. #457 == #445577
Then if you want to decrease the number of posibilities and widen the gap between them you can use:
'#'+(5*Math.floor(Math.random()*4)).toString(16)+
(5*Math.floor(Math.random()*4)).toString(16)+
(5*Math.floor(Math.random()*4)).toString(16);
Which divides the number of choices for each color by 5, and then evens out the distribution equally.
I second what kbok and Harpyon say about working in HSV colorspace, and this little library makes it super easy to switch between RGB and HSV - and others.

Categories

Resources