I would like to create a mathematical function for my D3 page.
Every node has got a "score" that I can get via d.score
I like to create a function that draws the radius of a circles based on that score.
I thought about following:
r <= 10, r >= 3
10 - (d.score / 50) = r
How could I do this in D3?
.attr('r', function(d) {
return node_size( * * ? ? ? * * );
});
Kind regards,
Marc
Please be advised, that this might be a bad question, due to it's "please give me code for this problem" character.
Anyway, i think this is what you want to try i guess:
.attr('r', function(d) {
var r = 10 - (d.score / 50);
if (r > 10) {r = 10;}
else if (r < 3) {r = 3;}
return r;
});
First you need to define a linear scale that would map the value of your node_size to an actual radius, as follows (example):
r = d3.scale.linear().range([3,10]);
Then specify as a domain, the values of your score parameter as follows (assuming DATA is your data structure)
// first define your radius according to your formula specified in your question.
DATA.forEach(function(d){
d.radius=10 - (d.score / 50);
})
//Then, assign is as a domain of your radius function
r.domain(d3.extent(DATA.map(function(d){return d.radius;})))
Finally, when plotting your data you should do something like this:
.attr('r', function(d) {return r(d.radius)});
You could merge or optimize these steps, but I think it is a good start.
Hope this helps.
Related
I am currently working on a visualization in d3.js with the goal to visualize data similarity. I wish to compare my data within circles by creating two semi-circles for each node and putting the comparison data within these semicircles. My data consists of strings (each semicircle receives a single sentence).
My current approach is as follows:
First, I create my necessary node data using the pack-layout.
var bubble = d3.pack().size([SVG_WIDTH,SVG_HEIGHT]).padding(CIRCLE_PADDING),
root = d3.hierarchy({children: COMPARISON_DATA}).sum(function(d){ return d.children ? 0 : d[2]});
var nodeData = bubble(root).children;
d[2] is the maximum string length of the two sentences that are being put into the semicircles and thus decides the radius of the circles.
Next, I iterate over each node and create the corresponding semicircles. I have removed all the code-parts which are irrelevant to my question.
nodeData.forEach(function (data, index) {
//upperCircleGroup simply adds a small y-translate, so that the semicircles have a margin
var gUpper = upperCircleGroup.append("g");
var gLower = lowerCircleGroup.append("g");
var lowerCircle = gLower.append('path')
.attr('d', d3.arc()({
innerRadius: 0,
outerRadius: data.r,
startAngle: Math.PI / 2,
endAngle: 3 / 2 * Math.PI
}))
.attr('transform', `translate(${data.x},${data.y})`)
var upperCircle = gUpper.append('path')
.attr('d', d3.arc()({
innerRadius: 0,
outerRadius: data.r,
startAngle: 1 / 2 * Math.PI,
endAngle: - 1 / 2 * Math.PI
}))
.attr('transform', `translate(${data.x},${data.y})`)
var upperText = gUpper
.append("foreignObject")
.attr("width", () => {return data.r*Math.sqrt(2)})
.attr("height", () => {return data.r*(Math.sqrt(2)/2)})
.attr('transform', `translate(${data.x - (data.r / Math.sqrt(2))},${data.y - (data.r/Math.sqrt(2)) })`)
.text(() => {return data.data[0]})
var lowerText = gLower
.append("foreignObject")
.attr("width", () => {return data.r*Math.sqrt(2)})
.attr("height", () => {return data.r*(Math.sqrt(2)/2)})
.attr('transform', `translate(${data.x - (data.r / Math.sqrt(2))},${data.y })`)
.text(() => {return data.data[1]})
});
As you can see, I draw my semicircles using d3's arc. Now this is where my question arises. I've had trouble putting my textual content inside the arc, so after searching for a while I chose this solution to put a div inside my semicircles which then receives the text. The sqrt(2) operations are used to fit the square into the semicircle.
My problem with this solution is, that at times, the sentence simply won't fit into the div and some content is lost. Is there a way to calculate the font-size of a string necessary, so that it fits the div of a given size? If this were possible, I could simply calculate the appropriate font-size and add a zoom option to the visualization. Also, if there are better ways to achieve what I am trying to do I would also be happy to get some feedback from you guys as I am a complete beginner when it comes to using d3.
Making text responsive to an element is difficult but CSS-Tricks have made a great article about different ways to approach it...
https://css-tricks.com/fitting-text-to-a-container/
I've been playing around with this example here for a little while. What I'm trying to do is highlight a single node/circle in the plot (by making it larger with a border; later I want to add text or a letter inside it too).
Currently, I've made the circle for Bhutan larger in the plot like the following:
.attr("r",
function(d){return ( d.countryName === "Bhutan" ? r + 4 : r);})
.attr("stroke", function(d){if (d.countryName==="Bhutan"){return "black"}})
However, it overlaps with the other circles. What would be the best approach to avoid these collisions/overlaps? Thanks in advance.
Link to Plunkr - https://plnkr.co/edit/rG6X07Kzkg9LeVVuL0PH?p=preview
I tried the following to add a letter inside the bhutan circle
//find bhutan circle and add a "B" to it
countriesCircles
.data(data)
.enter().append("text")
.filter(function(d) { return d.countryName === "Bhutan"; })
.text("B");
Updated Plunkr - https://plnkr.co/edit/Bza5AMxqUr2HW9CYdpC6?p=preview
This is a slightly different problem than in this question here: How to change the size of dots in beeswarm plots in D3.js
You have a few options that I can think of:
Set the forceCollide to be your largest possible radius * 1.33, e.g. (r + 4) * 1.33. This will prevent overlapping, but spread things out a lot and doesn't look that great.
Add the radius property to each entry in your array and make the collide work based off that, which will look a bit better but not perform as awesomely for large sets.
Here's an example of how to do that:
...
d3.csv("co2bee.csv", function(d) {
if (d.countryName === "Bhutan") {
d.r = r + 4;
} else {
d.r = r;
}
return d;
}, function(error, data) {
if (error) throw error;
var dataSet = data;
...
var simulation = d3.forceSimulation(dataSet)
...
.force("collide", d3.forceCollide(function(d) { return d.r * 1.33; }))
...
countriesCircles.enter()
.append("circle")
.attr("class", "countries")
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.attr("r", function(d){ return d.r; })
....
Use the row function in d3.csv to add a property to each member of the array called r, and check the country name to determine which one gets the larger value. Then use that value wherever you need to mess with the radius.
I guess it would've been possible to check the country name everywhere the radius was impacted (e.g. .force("collide", d3.forceCollide(function(d) { return d.countryName === "Bhutan" ? (r + 4) * 1.33 : r * 1.33; }), etc.). This feels a bit cleaner to me, but it might be cleaner still by abstracting out the radius from the data entries themselves...
Forked your plunk here: https://plnkr.co/edit/Tet1DVvHtC7mHz91eAYW?p=preview
I'm using a d3 attrTween to translate a circle over a path smoothly, similar to this example and as shown in the picture below:
The circle's transition is defined here:
function transition() {
circle.transition()
.duration(2051)
.ease("linear")
.attrTween("transform", translateAlong(path.node()))
}
And the attribute tween is shown here:
function translateAlong(path) {
var l = path.getTotalLength();
return function (d, i, a) {
return function (t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
This works well thanks to the SVG method getPointAtLength, which allows us to retrieve coordinates at different lengths of the path. However, I need a different kind of behavior and I've been unable to come up with a solution so far.
I need the circle to animate along the path, but at a steady horizontal speed. Meaning that the circle ought to take as much time to navigate this slice:
As it does with this slice:
Because both slices encompass the same width. On a low level, what I need is to be able to translate any X coordinate with its corresponding Y coordinate along the path. I've looked at all the SVG path methods and I haven't found anything particularly useful here. I'm hoping there's some way in D3 to feed an X coordinate to a d3 line and retrieve its corresponding Y coordinate.
Here's a JSFiddle working as described above. I'd really appreciate any help I can get on this. Thanks!
I ended up creating a lookup array for all my points along the line using getPointAtLength:
var lookup = [];
var granularity = 1000;
var l = path.node().getTotalLength();
for(var i = 1; i <= granularity; i++) {
var p = path.node().getPointAtLength(l * (i/granularity))
lookup.push({
x: p.x,
y: p.y
})
}
Once I had all those points in my lookup table, I used a bisector in my translate tween:
var xBisect = d3.bisector(function(d) { return d.x; }).left;
function translateAlong(path) {
var l = path.getTotalLength();
return function (d, i, a) {
return function (t) {
var index = xBisect(lookup, l * t);
var p = lookup[index];
return "translate(" + p.x + "," + p.y + ")";
};
};
}
And it works as expected! Yahoo!
Fiddle
I want to reimplement the following Processing sketch with d3js.
Recursion Thing
This wonderful sketch recursively builds up the graph, using a complex pushMatrix, popMatrix hierarchy.
How could this be implemented in d3.js as we there always work on the DOM immediately when appending a shape or transformation. But in a logic like in this sketch the appending part seems to have to be held back for the respective popMatrix to come. It feels like I have to implement my own transform and shape stack to temporary remember the transformation and shapes to be added until the popMatrix comes but that seems so not d3.js.
Any suggestion highly appreciated
ps:
i dont want to use processing.js as i want to work with svg, not canvas.
Interesting problem! Here's my take on it: http://jsfiddle.net/Y48BL/
This is more a proof of concept; I didn't do all the different colours and such. Nevertheless, it demonstrates the general approach. The general idea is to use g elements instead of the matrices that processing uses. Both are used for local transformations of the coordinate system; in the case of the g elements by setting transform accordingly. New gs (matrices) are created inside the recursive function and then passed on to the next level of the recursion. This would correspond to pushMatrix(). Coming back up, we continue to use the original g, corresponding to popMatrix().
The translation of the drawing of the circles and lines is fairly straightforward -- I find the D3 code much easier to read.
So I came up with this helper "class" to get this done, maybe a bit of a overkill but I will have more use cases for this.
var TransformStack = (function () {
function TransformStack() {
this.stack = [];
}
TransformStack.prototype.getCurrentElement = function () {
return this.stack[this.stack.length - 1];
};
TransformStack.prototype.setCurrentElement = function (element) {
this.stack[this.stack.length - 1] = element;
};
TransformStack.prototype.push = function (transformElement) {
this.stack.push(transformElement);
};
TransformStack.prototype.pushAndTransform = function (transformAttr) {
this.push(this.getCurrentElement().append("g").attr("transform", transformAttr));
};
TransformStack.prototype.transform = function (transformAttr) {
this.setCurrentElement(this.getCurrentElement().append("g").attr("transform", transformAttr));
};
TransformStack.prototype.pop = function () {
return this.stack.pop();
};
return TransformStack;
})();
Basically a stack to push/pop g elements which replaces the matrices approach in processing as Lars already pointed out. With this the main routine looks something like
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height)
.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + gScale + ")");
var tstack = new TransformStack();
tstack.push(svg);
doIt(nRecursions);
function doIt(n) {
// Circle
tstack.getCurrentElement()
.append("circle")
.attr("r", theSize)
.style("fill", "#fe6b0c")
.style("stroke", "0")
.style("stroke-width", "2")
.style("opacity", 0.3);
if (n != nRecursions) {
for (var i = 0; i < 4; i++) {
tstack.getCurrentElement().append("line")
.style("stroke", "red")
.style("opacity", 0.16)
.attr("x1", Math.random() * 4 - 2)
.attr("x2", Math.random() * 4 - 2)
.attr("y1", theSize / 2.0 + Math.random() * 4 - 2)
.attr("y2", distance - (theSize * theScale / 2.0) - 8.0 + Math.random() * 4 - 2);
}
}
var rot = 0;
tstack.pushAndTransform("scale(" + theScale + ")");
for (var i = 0; i < n; i++) {
if (n > 0) {
tstack.pushAndTransform("translate(0," + distance + ")");
doIt(n - 1);
tstack.pop();
rot = 360 / n;
tstack.transform('rotate(' + rot + ')');
}
}
tstack.pop();
}
}
Just wanted to share this, maybe of some use for some. The main point was given by Lars already.
I'm trying to emulate this graph: http://www.nytimes.com/interactive/2012/11/30/us/tax-burden.html
Here is the bare-bones rendition: http://jsfiddle.net/jd5Ym/6/
I can't get the different cursors to each follow the data for their own city. I can only do one at a time. My code depends on this function:
function mousemove() {
// p is the fraction of a graph traversed. decimalize strips integers.
var p=decimilize((x0.rangeBand()-d3.mouse(this)[0]+margin.left)/(x0.rangeBand()));
var u=data[Math.round(data.length-p*data.length)];
var v=cities[1].values[Math.round(data.length-p*data.length)];
cursor.data(data).attr("transform", "translate(" + (x1(u.date)) +","+y(v.temperature)+")");
}
Where it says v=cities[1], the index decides which city's data to follow. I want it to index each city itself, but when I try using the function (d,i) {...} setup, it doesn't work out, and I tried appending the mousemovefunction within a transform attribute in the declaration of city, and that didn't work either.
I am a beginning programmer so maybe this is easy. The data structure and parsing come out of Mike Bostock's examples.
You should use selectAll('.cities').each(...) to step over all the cities and update their cursors independently.
function mousemove() {
// the proportion of the way across any given graph that the mouse is
var mouseX = d3.mouse(this)[0]
var graph_width = x0.rangeBand()
// the corresponding data
var index = Math.floor( ( mouseX / graph_width ) * data.length );
d3.selectAll('.city')
.each(function(d, i){
var u = cities[i].values[index];
d3.select(this).select('.cursor')
.attr('transform', 'translate(' + x1(u.date) + ',' + y(u.temperature) + ')')
})
}
See here for the full working example: http://jsfiddle.net/jd5Ym/9/