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.
Related
I'm fairly new to d3.js and I'm exploring it slowly. So far I've only been trying to create a bar chart on where I load the data from a json file and when I click the bars, it changes the data to another column of the json.
The json file is formatted like this:
[
{
"ym": "201801",
"clients": 179,
"trans": 987,
},
{
"ym": "201802",
"clients": 178,
"trans": 334,
}
]
Whenever I load up the page, everything is fine, the chart is loaded up and everything works well. However, when I click on the bars, the axis change, the text changes but the bars do not appear. I checked the console and I have this, multiple times:
d3.v4.min.js:2 Error: <rect> attribute y: Expected length, "NaN".
(anonymous) # d3.v4.min.js:2
d3.v4.min.js:2 Error: <rect> attribute height: Expected length, "NaN".
I don't really know what the issued might be here. There was another thread with the same error in which the problem was that the data was a string and not a number, but that doesn't seem to be an issue here. Basically I have two identical functions, so when I click the bars it should only change the color of the bars, However, the bars still go missing.
Here the code of the function (which is similar to the code that generates the chart when the page loads up):
function alter_show() {
d3.json("testV.json", function(data) {
var y = d3.scaleLinear().range([height,1]);
var x = d3.scaleBand().rangeRound([0, width]);
y.domain([1,d3.max(data,function(d) {return d.clients})]);
x.domain(data.map(function(d) {return d.ym}));
svg.select(".x.axis").call(d3.axisBottom(x))
svg.select(".y.axis").call(d3.axisLeft(y))
bar = svg.selectAll("rect")
bar.data(data).enter().append("rect")
.attr('class','bar')
.attr("x", function (d) {return x(d.ym); })
.attr("y", function (d) {return y(d.clients); })
.attr("height", function (d) {return height - y(d.clients)})
.attr("width", x.bandwidth()*0.5)
.attr("color","black")
.attr("transform",
"translate(" + (margin.left + x.bandwidth()*0.25) + "," + margin.top + ")")
.on('mouseenter', function() {
d3.select(this).transition().duration(100).attr('opacity',0.5).attr('color', 'orange')})
.on('mouseleave', function() {
d3.select(this).transition().duration(100).attr('opacity',1)})
.on('click',alter_show);
svg.append("g").selectAll("text").data(data).enter().append("text")
.attr('class','value')
.attr("x", function (d) {return x(d.ym); })
.attr("y", function (d) {return y(d.trans); })
.text(function (d) {return d.trans})
.attr('font-size','0.7em')
.attr("transform",
"translate(" + (margin.left + x.bandwidth()*0.25) + "," + (margin.top + 30) + ")");
bar.exit().remove();
// updated data:
bar.transition()
.duration(750)
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
})});}
So to sum it up, this function works when I load the page, but when I click the bars, it doesn't show the bars. Thanks for you help, let me know if there's any additional info you need :)
Where is the field value in your data?
bar.transition()
.duration(750)
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
You have to fix all the other problems with the chart yourself.
I have sliders that modify the S command of a path. I want the source name to appear on the path which it does; however how do I remove the previously created text element? I have tried to remove it (see code below) but it doesn't work. The dom just fills up with extra text elements and the text on the path gets darker and darker as they start to pile up on each other. I have even tried to check for the text element by id as shown but no go. Hope you can shed any light on how to remove the text element so there is just one as each S command is modified.
I have added a fiddle here (append text at very bottom of code window):
fiddle...
graph.links.forEach(function (d, i) {
//console.log(obj[0].text, graph.links[i].source.name, graph.links[i].linkid);
if (graph.links[i].source.name == obj[0].text) {
var linkid = graph.links[i].linkid;
var the_path = $("#" + linkid).attr("d");
var arr = $("#" + linkid).attr("d").split("S");
//update S command
$("#" + linkid).attr("d", arr[0] + " S" + scommand_x2 + "," + scommand_y2 + " " + scommand_x + "," + scommand_y);
svg.select("#txt_" + linkid).remove();
svg.selectAll("#" + linkid).data(graph.links).enter()
.append("text")
.attr("id", "txt_" + linkid)
.append("textPath")
.attr("xlink:href", function (d) {
return "#" + linkid;
})
.style("font-size", fontSize + "px")
.attr("startOffset", "50%")
.text("")
.text(graph.links[i].source.name);
}
});
Here is a solution:
https://jsfiddle.net/kx3u23oe/
I did a couple of things. First, you don't need to bind this text to data the way you did. Second, I move the variable outside the update function, with all the append:
var someText = svg.append("text").append("textPath");
Then I kept only this inside update function:
someText.attr("xlink:href", "#L0")
.style("font-size", "12px")
.attr("startOffset", "50%")
.text("some text");
You can remove a text element using 'remove' function. Here is a working code snippet for the same.
var text = d3.select("body")
.append("text")
.text("Hello...");
setTimeout(function() {
text.remove();
}, 800);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
By the way, the problem in your code is, you are iterating over each link (graph.links.forEach(function (d, i) {) and creates a text element for all links(.data(graph.links).enter()) in each iteration. This creates n*n number of text labels; where n is the number of links. So I assume your code should be as follows.
svg.select("#txt_" + linkid).remove();
svg.selectAll("#" + linkid)
.append("text")
.attr("id", "txt_" + linkid)
.append("textPath")
.attr("xlink:href", function (d) {
return "#" + linkid;
})
.style("font-size", fontSize + "px")
.attr("startOffset", "50%")
.text(graph.links[i].source.name);
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.
I am trying to implement an extended version of the Grouped Bar Chart. Everything works fine, except updating the plot. In a regular bar chart, I would do something like this:
function draw(data) {
// Join the data with the selection
var bars = svg.selectAll(".bar")
.data(data);
// Create new bars if needed
bars.enter().append("rect")
...
// Update existing bars if needed
bars.transition()
...
// Remove bars if needed
bars.exit().remove();
}
Works like a charm. Now i tried the same with my groups:
var groups = chart.selectAll(".group")
.data(data);
groups.enter().append("g")
.attr("class", "group")
.attr("transform", function (d) {
return "translate(" + 0 + "," + y(d.name) + ")";
});
var bars = groups.selectAll(".bar").data(function (d) {
return d.values;
});
bars.enter().append("rect")
...
groups.transition().duration(750).attr("transform", function (d) {
return "translate(" + 0 + "," + y(d.name) + ")";
});
groups.exit().remove();
But: it only appends more and more groups on every update, no transitions or exits.
I am aware of this question: D3: update data with multiple elements in a group. However, I think did the setup just as described in the answer, without success.
EDIT: I am still trying to figure this out. I updated the fiddles.
Here is the working example without groups: http://jsfiddle.net/w2q0kjgd/2/
This is the not working example with groups: http://jsfiddle.net/o3fpaz2d/9/
I know it has been almost a month since the original posting, I tried to do something else in between and then have a look at this problem again, sometimes this helps. But I just cannot figure it out...
Please update the group before you select all the bars..
var groups = chart.selectAll(".group")
.data(data);
groups.enter()...
groups.transition().duration(750).attr("transform", function (d) {
return "translate(" + 0 + "," + y(d.name) + ")";
});
var bars = groups.selectAll(".bar").data(function (d) {
return d.values;
});
//add bars
//update bars
bars.exit().remove();
groups.exit().remove();
I was also playing around with the same block and was trying to figure it out. In the end, this solution worked for me:
var bars = g
.selectAll("g")
.data(data, function(d) {return d ? d.format_date : this.id; });
var enter = bars
.enter().append("g");
bars = enter.merge(bars)
.attr("transform", function(d) { return "translate(" + x0(d.format_date) + ",0)"; });
var rect = bars
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
rect
.enter().append("rect").merge(rect)
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x1.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return z(d.key); });
I think the trick here is that you need to update and merge it twice. One is for your x0 axes overall group and the other is for the bars within that group. Hope this also works out for you.
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);