Donut Pie Chart - Add a Title - NVd3.js - javascript

I'm exploring the NVD3.js library. It amazes me how quickly things can be produced in it.
But it seems like it's difficult to alter the chart sometimes. In this case, I would like to add a title to my chart.
The other thing, I would like to add additional data in the tool-tip. So on hover, It would also include the note in my data.
Data sample:
var data = [
{
key: "Loans",
y: 52.24
note: "Expect greatest value"
}];
This is the code I'm playing with:
nv.addGraph(function() {
var width = 500,
height = 500;
var chart = nv.models.pieChart()
.x(function(d) { return d.key; })
.values(function(d) { return d; })
.color(d3.scale.category10().range())
.width(width)
.height(height)
.donut(true);
chart.pie
.startAngle(function(d) { return d.startAngle/2 -Math.PI/2; })
.endAngle(function(d) { return d.endAngle/2 -Math.PI/2 ;});
d3.select("#chart")
//.datum(historicalBarChart)
.datum([data])
.transition().duration(1200)
.attr('width', width)
.attr('height', height)
.call(chart);
return chart;
});
Update: The original code for the tooltip is located within src->models->pieChart.js:
tooltip = function(key, y, e, graph) {
return '<h3>' + key + '</h3>' +
'<p> Confidence: ' + y + '%</p>'
}
I've tried overriding this with my own function. But either get errors or no change.
Title Update: I typically use the following code (or something similar) for titles.
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
.text("Awesome Title");
But of course, this isn't valid in NVD3. I'm not aware of what function is used to specify a title.

I think your looking for chart.tooltipContent() JSFiddle: http://jsbin.com/idosop/7/edit
var tp = function(key, y, e, graph) {
console.log(key, e, y);
return '<h3>' + key + '</h3>' +
'<p>!!' + y + '!!</p>' +
'<p>Likes: ' + e.point.likes + '</p>';
};
chart.tooltipContent(tp);

Related

How to display most recent value in line chart

I have trouble accessing the last value (row) of my CSV to display it on hover in my line chart. I need the value to be displayed as text, but also to be used as Y coordinate to line up with the end of the line.
This is what I have. The first part works, but not the second (in between ///):
function mouseover(d) {
d3.select(d.corporation.line).classed("corporation--hover", true);
d.corporation.line.parentNode.appendChild(d.corporation.line);
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.select(".corpname").text(d.corporation.name);
focus.select(".ranking").text(d.value);
/////
focus.append("text")
.datum(function(d) { return {name: d.corporation.name, value: d.corporation.value[d.corporation.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.values) + ")"; })
.text(function(d) { return d.value; });
/////
}
I tried few different variations without success. Would love if someone could take a peak.
PLUNK is here: http://plnkr.co/edit/1Nf992jYjSGyKhLhaij5?p=preview
Thanks!
Here is how you access the data bound to your line element: this.__data__
First, let's create a variable to access your data, specifically the array of values:
var that = this.__data__.corporation.values;
Then, we can get the date and the value of the end of the line:
var thatLength = that.length;
var thatValue = that[thatLength - 1].value;
var thatDate = that[thatLength - 1].date;
And display the text:
someLegend
.attr("x", (x(yearFormat(thatDate))*-1)-90)
.attr("y", y(thatValue))
.text(thatValue);
Here is the plunker: http://plnkr.co/edit/4HquzuJ6FJeZvEVWuUS5?p=preview
PS: I created this variable someLegend because I didn't have time to understand the translate logic of your focus.

How to scale text in a D3 bubble chart

I have a working, zoomable D3 bubble chart.
See fiddle here.
var theData = {
children:[{"source":3,"value":2367257,"formattedValue":"€2,367,257","name":"Legacies","tooltip":"Legacies: €2,367,257","colour":"#3182bd","$$hashKey":"object:106"},{"source":4,"value":1199595,"formattedValue":"€1,199,595","name":"Donations including donations in kind","tooltip":"Donations including donations in kind: €1,199,595","colour":"#6baed6","$$hashKey":"object:101"},{"source":2,"value":1154618,"formattedValue":"€1,154,618","name":"Tax relief income","tooltip":"Tax relief income: €1,154,618","colour":"#9ecae1","$$hashKey":"object:110"},{"source":2,"value":81447065,"formattedValue":"€81,447,065","name":"Grants and service fees from government sources","tooltip":"Grants and service fees from government sources: €81,447,065","colour":"#c6dbef","$$hashKey":"object:104"},{"source":3,"value":151798455,"formattedValue":"€151,798,455","name":"Non-government grants and donations","tooltip":"Non-government grants and donations: €151,798,455","colour":"#e6550d","$$hashKey":"object:108"},{"source":4,"value":15039907,"formattedValue":"€15,039,907","name":"Memberships and subscriptions","tooltip":"Memberships and subscriptions: €15,039,907","colour":"#fd8d3c","$$hashKey":"object:107"},{"source":2,"value":278004,"formattedValue":"€278,004","name":"Church collection","tooltip":"Church collection: €278,004","colour":"#fdae6b","$$hashKey":"object:100"},{"source":4,"value":113941393,"formattedValue":"€113,941,393","name":"Unspecified voluntary income","tooltip":"Unspecified voluntary income: €113,941,393","colour":"#fdd0a2","$$hashKey":"object:114"},{"source":1,"value":22890793,"formattedValue":"€22,890,793","name":"Fundraising events and activities","tooltip":"Fundraising events and activities: €22,890,793","colour":"#31a354","$$hashKey":"object:103"},{"source":1,"value":10713266,"formattedValue":"€10,713,266","name":"Charity shop income","tooltip":"Charity shop income: €10,713,266","colour":"#74c476","$$hashKey":"object:99"},{"source":2,"value":3800759,"formattedValue":"€3,800,759","name":"Unspecified activities for generating funds","tooltip":"Unspecified activities for generating funds: €3,800,759","colour":"#a1d99b","$$hashKey":"object:112"},{"source":2,"value":26174523,"formattedValue":"€26,174,523","name":"Investment income (including deposit interest)","tooltip":"Investment income (including deposit interest): €26,174,523","colour":"#c7e9c0","$$hashKey":"object:105"},{"source":3,"value":1605097,"formattedValue":"€1,605,097","name":"Unspecified incoming resources from generated funds","tooltip":"Unspecified incoming resources from generated funds: €1,605,097","colour":"#756bb1","$$hashKey":"object:113"},{"source":1,"value":150535745,"formattedValue":"€150,535,745","name":"Fees and income from trading activities","tooltip":"Fees and income from trading activities: €150,535,745","colour":"#9e9ac8","$$hashKey":"object:102"},{"source":1,"value":14580809,"formattedValue":"€14,580,809","name":"Other activities","tooltip":"Other activities: €14,580,809","colour":"#bcbddc","$$hashKey":"object:109"},{"source":4,"value":147269606,"formattedValue":"€147,269,606","name":"Uncategorized and other income","tooltip":"Uncategorized and other income: €147,269,606","colour":"#dadaeb","$$hashKey":"object:111"}]
};
function randomComparator (a, b) {
return Math.floor(Math.random() * 10) + 1
}
function clipText (d, t) {
if (d.r < 40) {
return "";
}
var name = t.substring(0, d.r / 5);
if (name.length < t.length) {
name = name.substring (0, name.length - Math.min(2, name.length)) + "...";
}
return name;
}
var diameter = 577,
width = 577,
height = diameter,
format = d3.format(",d");
var bubble = d3.layout.pack()
.sort(randomComparator)
.size([width, height])
.padding(3);
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", diameter)
.attr("class", "bubble");
var container = svg.append("g");
var node = container.selectAll(".node")
.data(bubble.nodes(theData)
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) {
return d.name + ": €" + format(d.value);
});
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) {
return d.colour;
//return color(d.source);
})
.style("pointer-events", "all");
var text = node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("fill", "#fff");
text.append("tspan")
.attr("x", "0")
.attr("dy", "0")
.style("font-weight", "600")
.text(function(d) {
return clipText(d, d.formattedValue);
});
text.append("tspan")
.attr("x", "0")
.attr("dy", "1.2em")
.text(function(d) {
return clipText(d, d.name);
});
// Setup zooming
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
var zoom = d3.behavior.zoom()
.scaleExtent([-10, 50])
.on("zoom", zoomed);
zoom(svg);
However, not all the bubbles have descriptive text in them, as the text wont fit inside the radius of the bubble; My "algorithm" is pretty blunt; I either don't return any text if the radius is too small or I truncate it.
How do I scale the text so that it shows up when I zoom in?
TIA,
Jeff
Managed to solve it I think. Basically what you wanted to do was when zooming show more and more of the name yes ?
So what I did was when zooming, get the scale and change the font size and the amount of letters that get outputted via your 'cliptext' function depending on the scale value. I also used a general fontsize so the sizing stays consistent.
Updated fiddle : https://jsfiddle.net/24y0qL5e/7/
I changed the cliptext function to get a scale value :
function clipText (d, t, scale) {
if (d.r < fontsize/scale) {
return "";
}
console.log(scale)
var name = t.substring(0, d.r/scale);
if (name.length < t.length) {
name = name.substring (0, name.length - Math.min(2, name.length)) + "...";
}
return name;
}
I added a class for the text that you wish to change just so it's easily selected in future :
text.append("tspan").attr('class', "nodeTextToClip") //added class
.attr("x", "0")
.attr("dy", "1.2em").style("font-size", fontsize)
.text(function(d) {
return clipText(d, d.name,8);
And then changed the size of the text and amount of letters outputted by the cliptext function like so:
d3.selectAll('.node text .nodeTextToClip') //select text that you want to change
.style('font-size', fontsize/scale).text(function(d){return clipText(d, d.name,fontsize/scale/3 );})
This is just a quick try out, obviously there is some simple changes that need to be made, but I think this should help you get an idea of what's needed :)

How to statically position elements in D3

I have currently have a line graph that looks like this:
on jsfiddle http://jsfiddle.net/vertaire/kttndjgc/1/
I've been trying to manually position the values on the graph so they get get printed next to the legend looking something like this:
Unintentional Injuries: 1980, 388437
I tried to set the positions manually, but it seems when I try and adjust to positioning, that positioning is relative to the that of the circle on the line like this:
How can I set the coordinates so that the values appear next to the legend?
Here is the code snippet for printing the values:
var mouseCircle = causation.append("g") // for each line, add group to hold text and circle
.attr("class","mouseCircle");
mouseCircle.append("circle") // add a circle to follow along path
.attr("r", 7)
.style("stroke", function(d) { console.log(d); return color(d.key); })
.style("fill", function(d) { console.log(d); return color(d.key); })
.style("stroke-width", "1px");
mouseCircle.append("text")
.attr("transform", "translate(10,3)"); // text to hold coordinates
.on('mousemove', function() { // mouse moving over canvas
if(!frozen) {
d3.select(".mouseLine")
.attr("d", function(){
yRange = y.range(); // range of y axis
var xCoor = d3.mouse(this)[0]; // mouse position in x
var xDate = x.invert(xCoor); // date corresponding to mouse x
d3.selectAll('.mouseCircle') // for each circle group
.each(function(d,i){
var rightIdx = bisect(data[1].values, xDate); // find date in data that right off mouse
yVal = data[i].values[rightIdx-1].VALUE;
yCoor = y(yVal);
var interSect = get_line_intersection(xCoor, // get the intersection of our vertical line and the data line
yRange[0],
xCoor,
yRange[1],
x(data[i].values[rightIdx-1].YEAR),
y(data[i].values[rightIdx-1].VALUE),
x(data[i].values[rightIdx].YEAR),
y(data[i].values[rightIdx].VALUE));
d3.select(this) // move the circle to intersection
.attr('transform', 'translate(' + interSect.x + ',' + interSect.y + ')');
d3.select(this.children[1]) // write coordinates out
.text(xDate.getFullYear() + "," + yVal);
yearCurrent = xDate.getFullYear();
console.log(yearCurrent)
return yearCurrent;
});
return "M"+ xCoor +"," + yRange[0] + "L" + xCoor + "," + yRange[1]; // position vertical line
});
}
});
First thing I would do is create the legend dynamically instead of hard coding each item:
var legEnter = chart1.append("g")
.attr("class","legend")
.selectAll('.legendItem')
.data(data)
.enter();
legEnter.append("text")
.attr("class","legendItem")
.attr("x",750)
.attr("y", function(d,i){
return 6 + (20 * i);
})
.text(function(d){
return d.key;
});
legEnter.append("circle")
.attr("cx",740)
.attr("cy", function(d,i){
return 4 + (20 * i);
})
.attr("r", 7)
.attr("fill", function(d,i){
return color(d.key);
});
Even if you leave it as you have it, the key here is to assign each text a class of legendItem. Then in your mouseover, find it and update it's value:
d3.select(d3.selectAll(".legendItem")[0][i]) // find it by index
.text(function(d,i){
return d.key + ": " + xDate.getFullYear() + "," + yVal;
});
Updated fiddle.

Dynamically sized word-cloud using d3-cloud

I am creating a wordcloud by modifying code from : https://github.com/jasondavies/d3-cloud. I can change the size by modifying w & h but I want to scale the word cloud as the browser window changes. What would be the best method to achieve this?
Code also posted at http://plnkr.co/edit/AZIi1gFuq1Vdt06VIETn?p=preview
<script>
myArray = [{"text":"First","size":15},{"text":"Not","size":29},{"text":"Bird","size":80}, {"text":"Hello","size":40},{"text":"Word","size":76},{"text":"Marketplaces","size":75}]
var fillColor = d3.scale.category20b();
var w = 400, // if you modify this also modify .append("g") .attr -- as half of this
h = 600;
d3.layout.cloud().size([w, h])
.words(myArray) // from list.js
.padding(5)
.rotate(0)
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", drawCloud)
.start();
function drawCloud(words) {
d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w/2 + "," + h/2 + ")")
.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return (d.size) + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fillColor(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d,i) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
}
)
.text(function(d) { return d.text; });
}
</script>
Solution # 1:
On line 37:
.style("font-size", function(d) { return (d.size) + "px"; })
Replace
.style("font-size", function(d) { return (d.size/3) + "vh"; }) // "d.size/3" is an assumption use your appropriate relative width or height.
Instead of using px use vw which is view port width. It is a css3 feature that will resize the text according to the viewport. However, you will need to adjust the real width and height properly.
Try reading this article: http://css-tricks.com/viewport-sized-typography/
Solution # 2:
On line 37:
.style("font-size", function(d) { return (d.size) + "px"; })
Use
.attr("class", nameOfClass) // use class names here like 'big-font', 'med-font', 'small-font'
and in the CSS define the styles using media queries, the classes will be assigned depending upon the d.size in the condition so do it like if (d.size > 10) nameOfClass = "big-font" etc.
Instead of giving words width and height using JS, allocate classes to them using media queries breakpoints.
Read : http://www.w3schools.com/cssref/css3_pr_mediaquery.asp
I recommend solution 2 as i believe vw and vh is not supported by all the browsers. http://caniuse.com/#feat=viewport-units. There are some issues reported related to that.
Solution # 3:
To calculate the font-size, you have to create this scale:
var fontSizeScale = d3.scale.pow().exponent(5).domain([0,1]).range([ minFont, maxFont]);
and call it in fontSize function:
var maxSize = d3.max(that.data, function (d) {return d.size;});
.fontSize(function (d) {
return fontSizeScale(d.size/maxSize);
})
To fit the bounds to your screen/div:
in the .on("end", drawCloud) function, call this function:
function zoomToFitBounds() {
var X0 = d3.min( words, function (d) {
return d.x - (d.width/2);
}),
X1 = d3.max( words, function (d) {
return d.x + (d.width/2);
});
var Y0 = d3.min( words, function (d) {
return d.y - (d.height/2);
}),
Y1 = d3.max( words, function (d) {
return d.y + (d.height/2);
});
var scaleX = (X1 - X0) / (width);
var scaleY = (Y1 - Y0) / (height);
var scale = 1 / Math.max(scaleX, scaleY);
var translateX = Math.abs(X0) * scale;
var translateY = Math.abs(Y0) * scale;
cloud.attr("transform", "translate(" +
translateX + "," + translateY + ")" +
" scale(" + scale + ")");
}

D3 X-value mouseover - function returns NaN

So I am trying to adapt M Bostock's x-value mouseover example to my own graph, the main difference being that I have multiple series instead of his one. For the moment I'm just trying to get the circles to work. My problem is that when I mouseover the graph (in Firebug) I get the message: Unexpected value translate(<my_x>, NaN) parsing transform attribute. I've tried several different ways to fix it but I get the same response each time. What am I doing wrong?
I have a jsFiddle, and the issue is at the bottom:
var focus = main.append('g')
.attr('class', 'focus')
.style('display', 'none');
var circles = focus.selectAll('circle')
.data(sets) // sets = [{name: ..., values:[{date:..., value:...}, ]}, ]
.enter()
.append('circle')
.attr('class', 'circle')
.attr('r', 4)
.attr('stroke', function (d) {return colour(d.name);});
main.append('rect')
.attr('class', 'overlay')
.attr('width', w)
.attr('height', h)
.on('mouseover', function () {focus.style('dispaly', null);})
.on('mouseout', function () {focus.style('display', 'none');})
.on('mousemove', mousemove);
function mousemove() {
var x0 = x_main.invert(d3.mouse(this)[0]),
i = bisectDate(dataset, x0, 1),
d0 = dataset[i - 1].date,
d1 = dataset[i].date,
c = x0 - d0 > d1 - x0 ? [d1, i] : [d0, i - 1];
circles.attr('transform', 'translate(' +
x_main(c[0]) + ',' +
y_main(function (d) {return d.values[c[1]].value;}) + ')'
);
== EDIT ==
Working jsFiddle
You're passing in a function definition into your y_main scale:
circles.attr('transform', 'translate(' +
x_main(c[0]) + ',' +
y_main(function (d) {return d.values[c[1]].value;}) + ')'
);
selection.attr can take a string value or a callback function but this is trying mixing both of those. You're passing in a string and as the string is constructed it tries to scale the function itself as a value, which will return NaN.
The function version should look like this (returning the entire transform value):
circles.attr('transform', function(d) {
return 'translate(' +
x_main(c[0]) + ',' +
y_main(d.values[c[1]].value) + ')';
});

Categories

Resources