I'm trying to add multiple vertical lines to the stacked area chart.
Adding multiple lines was achieved using a for-loop (maybe not the best way). The problem is, for-loop did not seem to work when updating the lines with nv.utils.windowResize() --- only the last line was updated eventually.
Maybe this could be done in a more D3 way without using a loop? Here is a fiddle for reproducing the problem (line 207 - 235).
I'd re-write your custom lines to be a little more d3-ish in nature (use data-binding, no explicit loops, etc...)
var xgrid = [1166996800000, 1186996800000, 1216996800000];
// add vertical lines
var custLine = d3.select('#stackedbarchart')
.select('.nv-areaWrap')
.append('g');
custLine.selectAll('line')
.data(xgrid)
.enter()
.append('line')
.attr({
x1: function(d){ return chart.xAxis.scale()(d) },
y1: function(d){ return chart.yAxis.scale()(0) },
x2: function(d){ return chart.xAxis.scale()(d) },
y2: function(d){ return chart.yAxis.scale()(1) }
})
.style("stroke", "#000000");
And then the update becomes:
nv.utils.windowResize(function() {
chart.update();
custLine.selectAll('line')
.transition()
.attr({
x1: function(d){ return chart.xAxis.scale()(d) },
y1: function(d){ return chart.yAxis.scale()(0) },
x2: function(d){ return chart.xAxis.scale()(d) },
y2: function(d){ return chart.yAxis.scale()(1) }
});
});
Note the use of the transition in there so the lines move a little better with the re-draw of the chart.
Updated fiddle.
You don't really need a for-loop to do this, just use selectAll. Here's one way:
First, to select the vertical lines easily I assigned them an id called vlines, in the for-loop on line 210
.attr('id', 'vlines')
Then use selectAll with this id to update the lines on resize. Here's your updated resize function:
nv.utils.windowResize(function() {
chart.update();
d3.selectAll('#vlines')
.attr('x1', function (d,i) { return chart.xAxis.scale()(xgrid[i]); })
.attr('y1', function (d,i) { return chart.yAxis.scale()(0); })
.attr('x2', function (d,i) { return chart.xAxis.scale()(xgrid[i]); })
.attr('y2', function (d,i) { return chart.yAxis.scale()(1); });
});
Using selectAll and functions, you can easily avoid loops in most cases.
Related
I have a d3.js line chart which may have negative values. My Y-span starts at 0 and goes up to the maximum value in the dataset. Thus, the line goes underneath the X-axis when there is a negative value.
This is fine, but what I need is to just hide the part of the line that is underneath the X-axis. I want to keep the values as they are, just use some CSS or JS to make the part of the line underneath the X-axis invisible.
I have tried with various overflow settings, but it doesn't seem to help. It is possible to make everything below the X-axis invisible by placing an element over that part, but then the X-axis labels are hidden too.
This is the code drawing the line:
x.domain([d3.min(data, function(d) { return d.date}), d3.max(data, function(d) { return d.date})]);
y.domain([0, 1.05 * d3.max(data, function (d) { return d.value; })]);
area.y0(y(0));
g.append("path")
.datum(data)
.attr("fill", "#f6f6f6")
.attr("d", area);
//create line
var valueline = d3.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.value); });
g.append("path")
.data([data])
.attr('fill', 'none')
.attr('stroke', '#068d46')
.attr("class", "line")
.attr("d", valueline);
Fiddle: https://jsfiddle.net/c1bvrd50/1/
One way to solve this problem is to clip the line inside a rectangle covering the positive values area. In SVG, this is done with clip-path.
This happens in two steps:
Define a rect inside clipPath, covering the chart area only:
g.append('clipPath')
.attr('id', 'clipRect')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height)
Clip the line path, using clip-path attribute:
g.append("path")
.data([data])
/* ... */
.attr("d", valueline)
.attr('clip-path', 'url("#clipRect")')
Updated jsFiddle implementing the solution: here.
What is left to do is update the tooltip's mousemove event listener in order not to display the tooltip whenever the value is lower than 0.
Recommendation as P.S.: Whatever the use case of the chart is, it is most likely desirable to show the portions of the chart with negative values. Therefore, the chart's y axis should cover values lower than 0, rather than omitting these.
Currently I'm working on a collapsible tree using D3.js. I successfully added a Tooltip so once I hover over an image information to said image will be added. The trouble is that unfortunately cannot use <br/> to add in a second line between both data points. Using quotation marks will simply print out the command and not using them will break the whole graph.
That's how it currently looks:
like with the command in place...
How can I add the second line of text?
function mouseover(d) {
d3.select(this).append("text")
.attr("class", "hover")
.attr('transform', function(d){
return 'translate(28, 0)';
})
.text(d.data.name + "<br/>" + d.data.hero);
}
function mouseout(d) {
d3.select(this).select("text.hover").remove();
}
I think you need to append the lines separately: http://plnkr.co/edit/nSANKfzNFtzyIthgVBWZ?p=preview
// Creates hover over effect
function mouseover(d) {
d3.select(this).append("text")
.attr("class", "hover")
.attr('transform', function(d){
return 'translate(28, 0)';
})
.text(d.data.name);
d3.select(this).append("text")
.attr("class", "hover2")
.attr('transform', function(d){
return 'translate(28, 10)';
})
.text(d.data.hero);
}
function mouseout(d) {
d3.select(this).select("text.hover").remove();
d3.select(this).select("text.hover2").remove();
}
I am trying to modify a code, and in the modifications that I have made, I have not been able to put a text in the middle of a circle. I've tried many things, and I've seen several examples but it does not work for me. How can I do it?
I know it should be done in this piece, and I add a text tag but it does not work.
bubbles.enter().append('circle')
.classed('bubble', true)
.attr('r', 0)
.attr('fill', function (d) { return fillColor(d.group); })
.attr('stroke', function (d) { return d3.rgb(fillColor(d.group)).darker();
})
.attr('stroke-width', 2)
.on('mouseover', function(){})
.on('mouseout', function(){});
http://plnkr.co/edit/2BCVxQ5n07Rd9GYIOz1c?p=preview
Create another selection for the texts:
var bubblesText = svg.selectAll('.bubbleText')
.data(nodes, function(d) {
return d.id;
});
bubblesText.enter().append('text')
.attr("text-anchor", "middle")
.classed('bubble', true)
.text(function(d) {
return d.name
})
And move them inside the tick function.
Here is the updated plunker: http://plnkr.co/edit/UgDjqNhzbvukTWU6J9Oy?p=preview
PS: This is a very generic answer, just showing you how to display the texts. This answer doesn't deal with details like size or transitions, which are out of the scope of the question and that you'll have to implement yourself.
While I've seen this question asked a few times, I'm having a bit trouble implementing. What I'd like to do is have the label attribute centered within each circle (as mentioned here). I believe I'd be adding the text attribute to:
canvas.selectAll('circles')
.data(nodes)
.enter()
.append('svg:circle')
.attr('cx', function (d) {
return d.x;
})
.attr('cy', function (d) {
return d.y;
})
.attr('r', function (d) {
return d.r;
})
.attr('fill', function (d) {
return d.color;
});
But am confused on why the instructions they gave in the previous example I linked to doesn't work with the setup I currently have. I believe it's the pack option that could be throwing me off (about the difference between the two), but any further examples would be a huge help. Thanks!
Update
Thanks for the answers/suggestions, I updated the Codepen with my progress (as I needed two lines of data; should have clarified) which seems to be working well. Now this is packing into a circle - at the end of the day, I'd love for this to be packed in the actual #canvas width/height (which is a rectangle). I saw this treemap example - would that be what I'm going for here?
Demo of what I have so far
Perhaps the confusion is that you can't add labels to the circle selection (because in SVG, a circle element can't contain a text element). You need to either make a g element that contains both circle and text, or a separate selection for the text, e.g.:
canvas.selectAll('text')
.data(nodes)
.enter()
.append('svg:text')
.attr('x', function (d) {
return d.x;
})
.attr('y', function (d) {
return d.y;
})
// sets the horizontal alignment to the middle
.attr('text-anchor', "middle")
// sets the vertical alignment to the middle of the line
.attr('dy', '0.35em')
.text(function(d) {
return d.label;
});
See the updated demo: http://codepen.io/anon/pen/djebv
In Mike Bostocks example http://bost.ocks.org/mike/nations/ there is so much data that putting the names of the countries there would make it chaotic, but for a smaller project I would like to display it.
I found this in the source:
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(position)
.sort(order);
dot.append("title")
.text(function(d) { return d.name; });
But somehow a title never shows up. Does anybody have an idea, how to display the name, next to the bubble?
As the other answer suggests, you need to group your elements together. In addition, you need to append a text element -- the title element only displays as a tooltip in SVG. The code you're looking for would look something like this.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter()
.append("g")
.attr("class", "dot")
.call(position)
.sort(order);
dot.append("circle")
.style("fill", function(d) { return color(d); });
dot.append("text")
.attr("y", 10)
.text(function(d) { return d.name; });
In the call to position, you would need to set the transform attribute. You may have to adjust the coordinates of the text element.
Unfortunately grouping the text and circles together will not help in this case. The bubbles are moved by changing their position attributes (cx and cy), but elements do not have x and y positions to move. They can only be moved with a transform-translate. See: https://www.dashingd3js.com/svg-group-element-and-d3js
Your options here are:
1) rewrite the position function to calculate the position difference (change in x and change in y) between the elements current position and its new position and apply that to the . THIS WOULD BE VERY DIFFICULT.
or 2) Write a parallel set of instructions to setup and move the tags. Something like:
var tag = svg.append("g")
.attr("class", "tag")
.selectAll(".tag")
.data(interpolateData(2004))
.enter().append("text")
.attr("class", "tag")
.attr("text-anchor", "left")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(tagposition)
.sort(order);
You will need a separate tagposition function since text needs 'x' and 'y' instead of 'cx', 'cy', and 'r' attributes. Don't forget to update the "displayYear" function to change the tag positions as well. You will probably want to offset the text from the bubbles, but making sure the text does not overlap is a much more complicated problem: http://bl.ocks.org/thudfactor/6688739
PS- I called them tags since 'label' already means something in that example.
you have to wrap the circle element and text together , it should look like
<country>
<circle ></circle>
<text></text>
</country>