I am visualising bubbles for cities, bigger if the city has a bigger value. Eg:
London: 14500
New York: 100
Tokyo: 1100
The values range from ~100-15000
I am having trouble creating a function that will return reasonable values, so that the 100 value bubbles aren't too small. The only way I could think to do this is to set a minimum size, eg:
if (size < 5) { size = 5 }
However, this causes the cities with values of ~100 to look very similar to cities with values of ~1000. I'd like the values of approx 0-15000 to return as values between 0.5 and 1 (or something similar). How would this be done?
Here's what I have so far, but like I said it's causing values of 100 and values of 1000 to both be under the min value:
var minBulletSize = 7.5;
var maxBulletSize = 20;
var maxSquare = maxBulletSize * maxBulletSize * 2 * Math.PI;
var minSquare = minBulletSize * minBulletSize * 2 * Math.PI;
// create circle for each location
for (var i = 0; i < mapData.length; i++) {
var dataItem = mapData[i];
var value = dataItem.value;
// calculate size of a bubble
var square = (value/1000 - minBulletSize) / (maxBulletSize - minBulletSize) * (maxSquare - minSquare) + minSquare;
if (square < minSquare) {
square = minSquare;
}
if (square > maxSquare) {
square = maxSquare;
}
var size = Math.sqrt(square / (Math.PI * 2));
var id = dataItem.code;
}
I have taken a look at how to make a logarithmic function to look "logarithmic" within the constraints of 0.5 and 1 :
Math.log10(x / 0.8 + 1) / 3 + 0.5 where x is in between 0 to 24.5.
This is purely a function that seems to look good for me where you can get very dynamic numbers early although a clear growth can be seen in larger numbers.
Feel free to mess around with the numbers, this is VERY subjective.
Next you will need to fit in your 100~15000 range within 0 to 24.5.
I would simply do a x = (x - 100) / 608.16 to get your range to be within 0 to 24.5.
var minBulletSize = 7.5;
var maxBulletSize = 20;
var maxSquare = maxBulletSize * maxBulletSize * 2 * Math.PI;
var minSquare = minBulletSize * minBulletSize * 2 * Math.PI;
// create circle for each location
for (var i = 0; i < mapData.length; i++) {
var dataItem = mapData[i];
var value = dataItem.value;
// assuming value is in between 100 and 15000
value = (value - 100) / 608.16;
value = Math.log10(value / 0.8 + 1) / 3.0 + 0.5;
// value will be in between 0.5 and 1 on a logarithmic scale.
// Do what you want with it :)
}
Tinker the values within the functions until you find a perfect curve for you.
Related
I am struggling with simple problem. What is elegant way of achieving this.
I have 2 values minimum and maximum which can be positive or negative and a third value which is between them. Now I want to calculate position(percentage) of other given number, which in my case is the width of element.
For example here I want to find what is position in the range 0-200 that represents value -10 in range -500 to 600 ?
var min = -500;
var max = 600;
var value = -10;
var width = 200;
var positionInWidth = ???;
You can one-liner this but let's do this in a few steps to see how it works:
// first we get the total range
var length = max - min; // 1100
// then we offset the position by the start position
var positionInLength = value - min; // 490
// now we get the percent through the value is in the position
var valuePercent = positionInLength / length;
// or a one liner (value - min) / (max - min);
// Then finally, apply `width percentage`
const positionInWidth = width * valuePercent;
Or as a one liner:
const positionInWidth = width * ((value - min) / (max - min));
So if I understand the question correctly, you are looking for the so called map function. If that's the case, the implementation is as given:
function map(input, inMin, inMax, outMin, outMax) {
return (input - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}
In your scenario outMin = 0:
console.log(map(-10, -500, 600, 0, 200)); // === 89.0909090909091
After I saw a video from the Coding Train on youtube about fractal trees, I tried to build one myself. Which worked great and I played with some variables to get different results.
I would love to see the tree moving like it got hit by some wind. I tried different approaches like rotating the branches a little bit or some minor physics implementations but that failed miserably.
So my question is: What would be the best approach to render a fractal tree and give it some sort of "life" like little shakes from wind.
Is there some sort of good reference ?
Do I need physics ? -> If so where do I have to look ?
If not -> How could I fake such an effect?
I am glad about every help I can get.
Source for the idea: https://www.youtube.com/watch?v=0jjeOYMjmDU
Tree in the wind.
The following are some short points re bending a branch in the wind. As the whole solution is complex you will have to get what you can from the code.
The code includes a seeded random number functions. A random recursive tree renderer, a poor quality random wind generator, all drawn on canvas using an animation loop.
Wind
To apply wind you need to add a bending force to each branch that is proportional to the angle of the branch to the wind.
So if you have a branch in direction dir and a wind in the direct wDir the amount of scaling the bending force needs is
var x = Math.cos(dir); // get normalize vector for the branch
var y = Math.sin(dir);
var wx = Math.cos(wDir); // get normalize vector for the wind
var wy = Math.sin(wDir);
var forceScale = x * wy - y * wx;
The length of the branch also effects the amount of force to include that you lengthen the vector of the branch to be proportional to its length
var x = Math.cos(dir) * length; // get normalize vector for the branch
var y = Math.sin(dir) * length;
var wx = Math.cos(wDir); // get normalize vector for the wind
var wy = Math.sin(wDir);
var forceScale = x * wy - y * wx;
Using this method ensures that the branches do not bend into the wind.
There is also the thickness of the branch, this is a polynomial relationship related to the cross sectional area. This is unknown so is scaled to the max thickness of the tree (an approximation that assumes the tree base can not bend, but the end branches can bend a lot.)
Then the elastic force of the bent branch will have a force that moves the branch back to its normal position. This acts like a spring and is very much the same as the wind force. As the computational and memory load would start to overwhelm the CPU we can cheat and use the wind to also recoil with a little bit of springiness.
And the tree.
The tree needs to be random, yet being fractal you don't want to store each branch. So you will also need a seeded random generator that can be reset at the start of each rendering pass. The tree is rendered randomly with each iteration but because the random numbers start at the same seed each time you get the same tree.
The example
Draws random tree and wind in gusts. Wind is random so tree may not move right away.
Click tree image to reseed the random seed value for the tree.
I did not watch the video, but these things are quite standard so the recursive function should not be to far removed from what you may have. I did see the youTube cover image and it looked like the tree had no randomness. To remove randomness set the leng, ang, width min, max to be the same. eg angMin = angMax = 0.4; will remove random branch angles.
The wind strength will max out to cyclone strength (hurricane for those in the US) to see the max effect.
There are a zillion magic numbers the most important are as constants with comments.
const ctx = canvas.getContext("2d");
// click function to reseed random tree
canvas.addEventListener("click",()=> {
treeSeed = Math.random() * 10000 | 0;
treeGrow = 0.1; // regrow tree
});
/* Seeded random functions
randSeed(int) int is a seed value
randSI() random integer 0 or 1
randSI(max) random integer from 0 <= random < max
randSI(min, max) random integer from min <= random < max
randS() like Math.random
randS(max) random float 0 <= random < max
randS(min, max) random float min <= random < max
*/
const seededRandom = (() => {
var seed = 1;
return { max : 2576436549074795, reseed (s) { seed = s }, random () { return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max }}
})();
const randSeed = (seed) => seededRandom.reseed(seed|0);
const randSI = (min = 2, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
/* TREE CONSTANTS all angles in radians and lengths/widths are in pixels */
const angMin = 0.01; // branching angle min and max
const angMax= 0.6;
const lengMin = 0.8; // length reduction per branch min and max
const lengMax = 0.9;
const widthMin = 0.6; // width reduction per branch min max
const widthMax = 0.8;
const trunkMin = 6; // trunk base width ,min and max
const trunkMax = 10;
const maxBranches = 200; // max number of branches
const windX = -1; // wind direction vector
const windY = 0;
const bendability = 8; // greater than 1. The bigger this number the more the thin branches will bend first
// the canvas height you are scaling up or down to a different sized canvas
const windStrength = 0.01 * bendability * ((200 ** 2) / (canvas.height ** 2)); // wind strength
// The wind is used to simulate branch spring back the following
// two number control that. Note that the sum on the two following should
// be below 1 or the function will oscillate out of control
const windBendRectSpeed = 0.01; // how fast the tree reacts to the wing
const windBranchSpring = 0.98; // the amount and speed of the branch spring back
const gustProbability = 1/100; // how often there is a gust of wind
// Values trying to have a gusty wind effect
var windCycle = 0;
var windCycleGust = 0;
var windCycleGustTime = 0;
var currentWind = 0;
var windFollow = 0;
var windActual = 0;
// The seed value for the tree
var treeSeed = Math.random() * 10000 | 0;
// Vars to build tree with
var branchCount = 0;
var maxTrunk = 0;
var treeGrow = 0.01; // this value should not be zero
// Starts a new tree
function drawTree(seed) {
branchCount = 0;
treeGrow += 0.02;
randSeed(seed);
maxTrunk = randSI(trunkMin, trunkMax);
drawBranch(canvas.width / 2, canvas.height, -Math.PI / 2, canvas.height / 5, maxTrunk);
}
// Recusive tree
function drawBranch(x, y, dir, leng, width) {
branchCount ++;
const treeGrowVal = (treeGrow > 1 ? 1 : treeGrow < 0.1 ? 0.1 : treeGrow) ** 2 ;
// get wind bending force and turn branch direction
const xx = Math.cos(dir) * leng * treeGrowVal;
const yy = Math.sin(dir) * leng * treeGrowVal;
const windSideWayForce = windX * yy - windY * xx;
// change direction by addition based on the wind and scale to
// (windStrength * windActual) the wind force
// ((1 - width / maxTrunk) ** bendability) the amount of bending due to branch thickness
// windSideWayForce the force depending on the branch angle to the wind
dir += (windStrength * windActual) * ((1 - width / maxTrunk) ** bendability) * windSideWayForce;
// draw the branch
ctx.lineWidth = width;
ctx.beginPath();
ctx.lineTo(x, y);
x += Math.cos(dir) * leng * treeGrowVal;
y += Math.sin(dir) * leng * treeGrowVal;
ctx.lineTo(x, y);
ctx.stroke();
// if not to thing, not to short and not to many
if (branchCount < maxBranches && leng > 5 && width > 1) {
// to stop recusive bias (due to branch count limit)
// random select direction of first recusive bend
const rDir = randSI() ? -1 : 1;
treeGrow -= 0.2;
drawBranch(
x,y,
dir + randS(angMin, angMax) * rDir,
leng * randS(lengMin, lengMax),
width * randS(widthMin, widthMax)
);
// bend next branch the other way
drawBranch(
x,y,
dir + randS(angMin, angMax) * -rDir,
leng * randS(lengMin, lengMax),
width * randS(widthMin, widthMax)
);
treeGrow += 0.2;
}
}
// Dont ask this is a quick try at wind gusts
// Wind needs a spacial component this sim does not include that.
function updateWind() {
if (Math.random() < gustProbability) {
windCycleGustTime = (Math.random() * 10 + 1) | 0;
}
if (windCycleGustTime > 0) {
windCycleGustTime --;
windCycleGust += windCycleGustTime/20
} else {
windCycleGust *= 0.99;
}
windCycle += windCycleGust;
currentWind = (Math.sin(windCycle/40) * 0.6 + 0.4) ** 2;
currentWind = currentWind < 0 ? 0 : currentWind;
windFollow += (currentWind - windActual) * windBendRectSpeed;
windFollow *= windBranchSpring ;
windActual += windFollow;
}
requestAnimationFrame(update);
function update() {
ctx.clearRect(0,0,canvas.width,canvas.height);
updateWind();
drawTree(treeSeed);
requestAnimationFrame(update);
}
body {
font-family : arial;
}
<canvas id="canvas" width="250" heigth="200"></canvas>
Click tree to reseed.
Update
I just noticed that the wind and branch length are absolute thus drawing the tree on a larger canvas will create a bending force too great and the branches will bend past the wind vector.
To scale the sim up either do it via a global scale transform, or reduce the windStrength constant to some smaller value. You will have to play with the value as its a 2nd order polynomial relation. My guess is multiply it with (200 ** 2) / (canvas.height ** 2) where the 200 is the size of the example canvas and the canvas.height is the new canvas size.
I have added the calculations to the example, but its not perfect so when you scale you will have to change the value windStrength (the first number) down or up if the bending is too far or not enough.
I am trying to make something where a bunch of circles (divs with border-radius) can be dynamically generated and laid out in their container without overlapping.
Here is my progress so far - https://jsbin.com/domogivuse/2/edit?html,css,js,output
var sizes = [200, 120, 500, 80, 145];
var max = sizes.reduce(function(a, b) {
return Math.max(a, b);
});
var min = sizes.reduce(function(a, b) {
return Math.min(a, b);
});
var percentages = sizes.map(function(x) {
return ((x - min) * 100) / (max - min);
});
percentages.sort(function(a, b) {
return b-a;
})
var container = document.getElementById('container');
var width = container.clientWidth;
var height = container.clientHeight;
var area = width * height;
var maxCircleArea = (area / sizes.length);
var pi = Math.PI;
var maxRadius = Math.sqrt(maxCircleArea / pi);
var minRadius = maxRadius * 0.50;
var range = maxRadius - minRadius;
var radii = percentages.map(function(x) {
return ((x / 100) * range) + minRadius;
});
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
var coords = [];
radii.forEach(function(e, i) {
var circle = document.createElement('div');
var randomTop = getRandomArbitrary(0, height);
var randomLeft = getRandomArbitrary(0, width);
var top = randomTop + (e * 2) < height ?
randomTop :
randomTop - (e * 2) >= 0 ?
randomTop - (e * 2) :
randomTop - e;
var left = randomLeft + (e * 2) < width ?
randomLeft :
randomLeft - (e * 2) >= 0 ?
randomLeft - (e * 2) :
randomLeft - e;
var x = left + e;
var y = top + e;
coords.push({x: x, y: y, radius: e});
circle.className = 'bubble';
circle.style.width = e * 2 + 'px';
circle.style.height = e * 2 + 'px';
circle.style.top = top + 'px';
circle.style.left = left + 'px';
circle.innerText = i
container.appendChild(circle);
});
I have got them being added to the parent container but as you can see they overlap and I don't really know how to solve this. I tried implementing a formula like (x1 - x2)^2 + (y1 - y2)^2 < (radius1 + radius2)^2 but I have no idea about this.
Any help appreciated.
What you're trying to do is called "Packing" and is actually a pretty hard problem. There are a couple potential approaches you can take here.
First, you can randomly distribute them (like you are currently doing), but including a "retry" test, in which if a circle overlaps another, you try a new location. Since it's possible to end up in an impossible situation, you would also want a retry limit at which point it gives up, goes back to the beginning, and tries randomly placing them again. This method is relatively easy, but has the down-side that you can't pack them very densely, because the chances of overlap become very very high. If maybe 1/3 of the total area is covered by circle, this could work.
Second, you can adjust the position of previously placed circles as you add more. This is more equivalent to how this would be accomplished physically -- as you add more you start having to shove the nearby ones out of the way in order to fit the new one. This will require not just finding the things that your current circle hits, but also the ones that would be hit if that one was to move. I would suggest something akin to a "springy" algorithm, where you randomly place all the circles (without thinking about if they fit), and then have a loop where you calculate overlap, and then exert a force on each circle based on that overlap (They push each other apart). This will push the circles away from each other until they stop overlapping. It will also support one circle pushing a second one into a third, and so on. This will be more complex to write, but will support much more dense configurations (since they can end up touching in the end). You still probably need a "this is impossible" check though, to keep it from getting stuck and looping forever.
I have a 300px * 300px image. I also have some specific pixels every 100px in every direction (to 16 specific pixels in total).
0 1 2 <-- IntervalX
______ ______ ______
0 | | | |
|______|______|______|
1 | | | |
|______|______|______|
2 | | | |
|______|______|______|
^
IntervalY
I want to put every pixels (except the specific ones) in blocks bounded by the specific pixels, but defined by the pixels value in a 1D array, not 2D array.
const gridX = width / (trueX - 1);
const gridY = width / (trueY - 1);
//Loop for every pixel:
intervalX = Math.floor((pixel[inc].x) / gridX);
intervalY = Math.floor((pixel[inc].y) / gridY);
//Implementing formula for transforming from 1D array to 2D array : (y * width + x) = item number
//All leftmost known pixels start from a "zero" value, so we do not need to substract 1
let isNotFirstArray;
if (intervalX == 0)
isNotFirstArray = 0;
else
isNotFirstArray = 1;
p1 = pixel[intervalY * gridY * 299 + intervalX * gridX - isNotFirstArray];
p2 = pixel[intervalY * gridY * 299 + (intervalX + 1) * gridX - 1];
p3 = pixel[(intervalY + 1) * gridY * 299 + intervalX * gridX - isNotFirstArray];
p4 = pixel[(intervalY + 1) * gridY * 299 + (intervalX + 1) * gridX - 1];
pixel[inc].value = Math.round(bilinearInterpolate(p1, p2, p3, p4, j, i));
inc++;
The problem is the values for the specific pixels are not calculated correctly. The Y values are correct, but the X are not.
Later edit:
The error is like this: the coordinates of the specific pixels are correct on the Y axis, but not on the X axis. They are shifted for the inner intervals with 100px*(max intervals - current interval).
If I get that right, you would like to »filter-out« some of the pixels, which appear at some interval in x and y direction?
const img = <image array>,
imgWidth = 600,
gridX = 150,
gridY = 200;
let result = [];
for (let i = 0; i < input.length; i++) {
let x = i % imgWidth,
y = Math.floor(i / imgWidth);
if (!(x % gridX == 0 || y % gridY == 0)) {
result.push(input[i])
}
}
That should filter out all the indexes which are as special as you said. It remains an array of pixels of an image with the a width.
What is the percent change of this code reaching the number 200, 500 and 1000?
I created this code for 200 to be 50% but it keeps rolling numbers above 200, someone please help me if you understand :D.
var mainMultplier = 100;
var numerator = 99;
var denominator = 100;
for(;;) {
var randomInt = random.real(0, 1.0);
if ( numerator/denominator > randomInt ) {
numerator = numerator + 1;
denominator = denominator + 1;
mainMultplier = mainMultplier + 1;
} else {
break;
}
}
Edit
Based on the code you have posted, we can see these two base rules:
P(100) = 1 - 0.99 = 0.01
P(101) = (1 - P(100)) * (1 - (100 / 101)) = P(101) = (1 - P(100)) * (1 / 101)
The second rule can be generalized for any number X after 100:
P(X) = (1 - P(X - 1)) * (1 / X)
Now, I did learn how to do proofs by induction at Uni, which I'm sure would help my explanation here, but I can't remember it anymore :(. So instead, I've written some code to generate a lookup table p, from 100 to 1000:
var p = [];
p[100] = 0.01;
for (var x = 101; x <= 1000; x++)
p[x] = (1 - p[x - 1]) * (1 / x);
Edit 2
And that's as far as my help can go. You may want to post the generalized algorithm on the Software Engineering page.