Extracting values for tooltip - javascript

This is a continuation from my previous posts on the bubble chart I have been struggling to make. I have achieved my desired effect by adapting Chris Tufts's code:
https://blockbuilder.org/lydiawawa/347e2b0aeed51d7dc56fde40b08e5fcd
However, when I attempt to add tooltip, I'm unable to extract the original value of BMI and Race(In the code, BMI = size and Race = group) This is because .data is calling nodes instead of the original data. Does anyone know how to point the tooltip to grab the right values?
I know that I should define group and size in create_node function such as the following, but I received an unexpected var token error.
function create_nodes(data,node_counter) {
var i = cs.indexOf(data[node_counter].group),
var z = cs.data[node_counter].group,
var s = cs.data[node_counter].size,
r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
d = {
cluster: i,
z,
s,
radius: radiusScale(data[node_counter].size)*1.5,
text: data[node_counter].text,
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
}
if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
console.log(d);
return d;
};

You can easily create the size property the same way you created the group one:
size: data[node_counter].size,
By the way, you have a syntax error (you're missing the colon) in the group one, which should be:
group: data[node_counter].group,
Here is your updated bl.ocks: https://bl.ocks.org/GerardoFurtado/5802f23a0bd1c4a3f94f95eded56bc97/dc36321d0d4bb7db2a44246f9330f22099276524
PS: as a friendly advice, you don't need that cumbersome function. Just change the data array and pass it directly to the layout.

Related

How to cluster bubble chart with many groups

I'm trying to imitate the following effects:
Orginal version V3:
https://bl.ocks.org/mbostock/7881887
Converted to V4:
https://bl.ocks.org/lydiawawa/1fe3c80d35e046c1636663442f34680b/86d1bda1dabb7f3a6d11cb1a16053564078ed964
An example used dataset:
https://jsfiddle.net/hf998do7/1/
This is what I have so far :
https://blockbuilder.org/lydiawawa/0899a02cc86f2274f52e27064bc86500
I want to make a bubble graph that shows clusters of Race, the size of the bubbles are assigned by BMI. The dots will not overlap (collide). There is a toggle control on the left top corner; when it is turned to the right, bubbles cluster into groups separated by Race, when it is turned to the left the dots combine and mix together and centers on the canvas ( in a large circle).
I tried to code for a clustered bubbles, but with the dataset I have there are too many jitters, takes a while for the split to stop. So I found Bostock's versions of clustered bubbles, they are intended to reduce jitters.
What I struggled the most is to fit my dataset with Bostock's version III layout, and I accidentally used the wrong version of forceCluster(alpha) code because I was looking through his different versions (I updated the code in the following section).
In Bostock's original code, he created his own bubbles by defining the following variable, nodes. He used a custom formula to define the distribution of nodes. However, in my case, I will be using my own dataset, bubbleChart.csv with a toggle that combines and splits the cluster.
Bostock's custom defined nodes:
var nodes = d3.range(n).map(function() {
var i = Math.floor(Math.random() * m),
r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
d = {
cluster: i,
radius: r,
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
};
if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
return d;
});
He then defined force function to reduce jitter:
var force = d3.layout.force()
.nodes(nodes) // not sure how to redefine this
.size([width, height])
.gravity(.02)
.charge(0)
.on("tick", tick)
.start();
Code for clustering (updated):
function forceCluster(alpha) {
return function(d) {
var cluster = clusters[d.Race];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = d.Race + cluster.Race;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
cluster.x += x;
cluster.y += y;
}
};
}
I'm having trouble to fit everything together in particularly to adapt to my own dataset and would like to have some help. Thank you!

JS randomly distribute child elements in their parent without overlap

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.

jquery segment displays wrong number all the time?

I'm trying to create a wheel of fortune type animation using jquery but for some reason the code that i am using always displays the wrong number!
here is the jsfiddle: http://jsfiddle.net/wf49mqaa/2/
click on the WHITE AREA in the wheel to see the animation and you will see a wrong number will be shown!
at the moment I only have 4 columns and 4 segments in my jquery code but in the future i am will pull the amount of segments from a database and I need this to work correctly at all times and display the correct number.
I tried everything from changing the segment = Math.ceil(((percent/100) * 4)), to segment = Math.ceil(((percent/100) * 4) -1), and also segment = Math.ceil(((percent/100) * 5)),
and it still display wrong number!
could someone please advise on this?
Thanks
Part of the Code you use I found in a Non-working demo from sitepoint., digging a bit deeper there are two different errors/ problems to solve the fortune-wheel behavior:
First: How to define the degree:
// existing code fragment (wrong)
var deg = 1500 + Math.round(Math.random() * 1500);
This would cause the wheel to stop at a totally random position, but that is not what you need. The wheel should always stop at the marker position, it should just turn around by a random number of segments.
// supposing you have a wheel with 4 segments (here the items array):
var deg = 0, /* basic degree */
spinbase = 1080, /* basic spinning of the wheel, here 3 times full turn */
items = [1,2,3,4];
// your spinning function...
spin: function () {
var min = 1,
max = 10,
rand = Math.floor(min + Math.random()*(max+1-min));
[...]
// like this you'll stop at the same position,
// but the wheel moved by a random number of segments
deg = deg + ( Math.round( 360 / items.length ) * rand) + spinbase;
[...]
}
Second: How to get the correct segment:
In short:
// where deg is the current degree, and items the array from above.
var segmentIndex = Math.ceil(
(deg - (360 * parseInt(deg / 360))) /
Math.round(360/items.length)
);
And when filling the algorithm..
// e.g. deg is (degree of each segment) * random (here 5) + 1080
// deg = 1530 (1080 + ( (360/4) * 5) )
// (1530 - (360 * parseInt( 1530 / 360))) / Math.round(360 / 4);
// (1530 - (360 * 4)) / 90;
// 90 / 90 = 1
// since we have 4 segments only and the random number is higher,
// the wheel did another full turn + 1 (from the starting number)
// so we get items[ 1 ] = (result: 2);
// due to the ceil/floor/round methods in calculation it can happen
// that you reach the extrem values segments.length or less than 0,
// to fix this:
var segmentIndex = Math.ceil(
(deg - (360 * parseInt(deg / 360))) /
Math.round(360/items.length)
);
if(target < 0 ) { target = segment.length - 1; }
if(target === segments.length ) { target = 0; }
alert( 'Winning: ' + items[target] );
Putting this together you'll get a working fortune-wheel. I allowed myself to create a new variant of the fortune wheel, which is able to handle different amounts of segments to make it easier to prove the algorithm.

RGB values varying with array values?

I have a project in which I am using d3js.I have to plot a line.But the twist is the color of line will vary with every array value(every 20px of line holds one element of following array).I have created lines but, I am facing problems with the colour intensity.I thought about for loops,but I am not getting exact results.I think I am not able to swith from one colour to another.
(for example--from dim red-->light red-->little dark red-->dark red)
I have a json with over 10k values.I have simplified my arrays for understanding purpose.
var array_1=[0.0,0.1,0.3,0.5,0.7,0.9]
var array_2=[0.02,0.04,0.06,0.08,0.09]
var array_3=[0.001,0.002,0.004,0.007,0.008]
MyQuestion:For any given color(red,blue,green,yellow etc etc),I should be able to plot the according to these array values in such a way that Greater the number will be,Intense the color will be.
Is it possible to create an algorithm that can manipulate the RGB values of any colour with array values that are numeric?If possible,it will be very kind of you to give some sample.
You just need to convert from YCbCr (YUV) to RGB since
the Y in YUV is luminescence it's as modifying the intensity of the color.
The formulas can be found on wikipedia here:
http://en.wikipedia.org/wiki/YCbCr
There is also an example of this implemented in JavaScript here:
http://www.mikekohn.net/file_formats/yuv_rgb_converter.php
For example on Micael Kohn's page you can try setting a color in RGB
and covert it to YUV. After that just lower or raise the Y value and
convert it to RGB. You will notice that it's like changing the
intensity.
EDIT:
Here you go, wrote an example for you, just click on the div
function yuv2rgb(Y,U,V){
R = Y + 1.4075 * (V - 128)
G = Y - 0.3455 * (U - 128) - (0.7169 * (V - 128))
B = Y + 1.7790 * (U - 128)
return { r:Math.floor(R) , g:Math.floor(G) , b:Math.floor(B)}
}
function rgb2yuv(R,G,B){
Y = R * 0.299000 + G * 0.587000 + B * 0.114000
U = R * -0.168736 + G * -0.331264 + B * 0.500000 + 128
V = R * 0.500000 + G * -0.418688 + B * -.081312 + 128
return { y:Math.floor(Y) , u:Math.floor(U) , v:Math.floor(V)}
}
http://jsfiddle.net/b3FCK/

Find column, row on 2D isometric grid from x,y screen space coords (Convert equation to function)

I'm trying to find the row, column in a 2d isometric grid of a screen space point (x, y)
Now I pretty much know what I need to do which is find the length of the vectors in red in the pictures above and then compare it to the length of the vector that represent the bounds of the grid (which is represented by the black vectors)
Now I asked for help over at mathematics stack exchange to get the equation for figuring out what the parallel vectors are of a point x,y compared to the black boundary vectors. Link here Length of Perpendicular/Parallel Vectors
but im having trouble converting this to a function
Ideally i need enough of a function to get the length of both red vectors from three sets of points, the x,y of the end of the 2 black vectors and the point at the end of the red vectors.
Any language is fine but ideally javascript
What you need is a base transformation:
Suppose the coordinates of the first black vector are (x1, x2) and the coordinates of the second vector are (y1, y2).
Therefore, finding the red vectors that get at a point (z1, z2) is equivalent to solving the following linear system:
x1*r1 + y1*r2 = z1
x2*r1 + y2*r2 = z2
or in matrix form:
A x = b
/x1 y1\ |r1| = |z1|
\x2 y2/ |r2| |z2|
x = inverse(A)*b
For example, lets have the black vector be (2, 1) and (2, -1). The corresponding matrix A will be
2 2
1 -1
and its inverse will be
1/4 1/2
1/4 -1/2
So a point (x, y) in the original coordinates will be able to be represened in the alternate base, bia the following formula:
(x, y) = (1/4 * x + 1/2 * y)*(2,1) + (1/4 * x -1/2 * y)*(2, -1)
What exactly is the point of doing it like this? Any isometric grid you display usually contains cells of equal size, so you can skip all the vector math and simply do something like:
var xStep = 50,
yStep = 30, // roughly matches your image
pointX = 2*xStep,
pointY = 0;
Basically the points on any isometric grid fall onto the intersections of a non-isometric grid. Isometric grid controller:
screenPositionToIsoXY : function(o, w, h){
var sX = ((((o.x - this.canvas.xPosition) - this.screenOffsetX) / this.unitWidth ) * 2) >> 0,
sY = ((((o.y - this.canvas.yPosition) - this.screenOffsetY) / this.unitHeight) * 2) >> 0,
isoX = ((sX + sY - this.cols) / 2) >> 0,
isoY = (((-1 + this.cols) - (sX - sY)) / 2) >> 0;
// isoX = ((sX + sY) / isoGrid.width) - 1
// isoY = ((-2 + isoGrid.width) - sX - sY) / 2
return $.extend(o, {
isoX : Math.constrain(isoX, 0, this.cols - (w||0)),
isoY : Math.constrain(isoY, 0, this.rows - (h||0))
});
},
// ...
isoToUnitGrid : function(isoX, isoY){
var offset = this.grid.offset(),
isoX = $.uD(isoX) ? this.isoX : isoX,
isoY = $.uD(isoY) ? this.isoY : isoY;
return {
x : (offset.x + (this.grid.unitWidth / 2) * (this.grid.rows - this.isoWidth + isoX - isoY)) >> 0,
y : (offset.y + (this.grid.unitHeight / 2) * (isoX + isoY)) >> 0
};
},
Okay so with the help of other answers (sorry guys neither quite provided the answer i was after)
I present my function for finding the grid position on an iso 2d grid using a world x,y coordinate where the world x,y is an offset screen space coord.
WorldPosToGridPos: function(iPosX, iPosY){
var d = (this.mcBoundaryVectors.upper.x * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.upper.y * this.mcBoundaryVectors.lower.x);
var a = ((iPosX * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.lower.x * iPosY)) / d;
var b = ((this.mcBoundaryVectors.upper.x * iPosY) - (iPosX * this.mcBoundaryVectors.upper.y)) / d;
var cParaUpperVec = new Vector2(a * this.mcBoundaryVectors.upper.x, a * this.mcBoundaryVectors.upper.y);
var cParaLowerVec = new Vector2(b * this.mcBoundaryVectors.lower.x, b * this.mcBoundaryVectors.lower.y);
var iGridWidth = 40;
var iGridHeight = 40;
var iGridX = Math.floor((cParaLowerVec.length() / this.mcBoundaryVectors.lower.length()) * iGridWidth);
var iGridY = Math.floor((cParaUpperVec.length() / this.mcBoundaryVectors.upper.length()) * iGridHeight);
return {gridX: iGridX, gridY: iGridY};
},
The first line is best done once in an init function or similar to save doing the same calculation over and over, I just included it for completeness.
The mcBoundaryVectors are two vectors defining the outer limits of the x and y axis of the isometric grid (The black vectors shown in the picture above).
Hope this helps anyone else in the future

Categories