Is there a way in d3 to not draw overlapping tick labels? For example, if I have a bar chart, but the bars are only 5 pixels wide and the labels are 10 pixels wide, I end up with a cluttered mess. I'm currently working on an implementation to only draw the labels when they do not overlap. I can't find any existing way to do that, but wasn't sure if anyone else had dealt with this problem.
There is no way of doing this automatically in D3. You can set the number of ticks or the tick values explicitly (see the documentation), but you'll have to figure out the respective numbers/values yourself. Another option would be to rotate the labels such that there is less chance of them overlapping.
Alternatively, like suggested in the other answer, you could try using a force layout to place the labels. To clarify, you would use the force layout on the labels only -- this is completely independent of the type of chart. I have done this in this example, which is slightly more relevant than the one linked in the other answer.
Note that if you go with the force layout solution, you don't have to animate the position of the labels. You could simply compute the force layout until it converges and then plot the labels.
I've had a similar problem with multiple (sub-)axis, where the last tick overlaps my vertical axis in some situations (depending on the screen width), so I've just wrote a little function that compares the position of the end of the text label with the position of the next axis. This code is very specific to my use case, but could adapted easily to your needs:
var $svg = $('#svg');
// get the last tick of each of my sub-axis
$('.tick-axis').find('.tick:last-of-type').each(function() {
// get position of the end of this text field
var endOfTextField = $(this).offset().left + $(this).find('text').width();
// get the next vertical axis
var $nextAxis = $('line[data-axis="' + $(this).closest('.tick-axis').attr('data-axis') + '"]');
// there is no axis on the very right, so just use the svg width
var positionOfAxis = ($nextAxis.length > 0) ? $nextAxis.offset().left : $svg.offset().left + $svg.width();
// hide the ugly ones!
if (endOfTextField > positionOfAxis) {
$(this).attr('class', 'tick hide');
}
});
The ticks with color: aqua are the hidden ones:
Related
I want to put the same legend top center of the chart. Legend items will be dynamic, likes sometimes 2,5 or 9, etc.
So, it should take space dynamically like how it is acting on the bottom.
I tried with inset functionality but it seems this is not looking better like the bottom one.
and there are few more complexity like I want it like a flat, so now if I define step size 3 then maybe, for now, it looks good for 9 items. but when there will be 2 items, it will show as a list!!
Although I solved that problem through the following solution:
// get parent elements
let parentEle = d3.selectAll("#chartID svg");
let childrenEle = parentEle
//Convert selection to selection representing the children
.selectAll(function () { return this.childNodes; })
.filter('g');
// putting legends position on TOP
d3.select(childrenEle._groups[0][2]).attr('transform', 'translate(0, 5)');
you have to keep eye on chart width - height and according to this, you may have to control padding in TOP for the chart.
might be this is not a good solution but it works fine for me :D
I have a rectangle and a text created through D3 which are grouped together like below.
<g>
<rect></rect>
<text></text>
</g>
In a javascript function I'm expanding this rectangle's width to allow the the value of text to be displayed in one go. (If the value is too long as the text value is set dynamically). This is working fine. However even though the rectangle width is expanding depending on the text value size , D3 text element is not showing the complete text value that is added, eventually the text is moved to left and the first bit of it can't be seen.
Do I need to increase the text element's width too or what would be the solution for this?
[UPDATE]
Following is the current code.
var elms = d3.selectAll("[id=s]"); // has the rectangle and text elements
var s = d3.selectAll("[id=s]").selectAll("text"); // just the text elements
var minimumValue = 100;
// updating rectangle width
elms.attr('width', function() { return dynamic < minimumValue ? minimumValue : dynamic;});
Now I need a way to update the position of the text element when the rectangle is expanded. Hope this is more clear. I just tried manually updating 'x' property of text element that works. But can I derive this value dynamically to set it within this function?
In case someone is looking for this, was able to get the text new 'x' value by x of rectangle + half of rectangle's width (and similar for y but with the height).
Got the answer from this link (How to place and center text in an SVG rectangle)
I am having an issue with changing the stroke of an individual element in a Topojson file where my mouseover is not altering the stroke of the full shape.
A visual is probably best:
I would like every border element from the county shape area to receive the same stroke on mouse-over. Instead, I am getting this odd effect where only part of the border changes stroke.
I ended up using a solution like this, referenced here.
.on("mouseover", function(d,i) {
d3.select(this.parentNode.appendChild(this)).transition().duration(300)
.style({'stroke-opacity':1,'stroke':'#F00'});
})
Say all your drawn shapes are in a data-bound d3 selection called shapes (which you create using the usual enter, update, exit flow). Then something like this should work:
shapes.on('mouseover', function(d, i) {
// d is the datum of the hovered shape
// data is all the data currently bound to shapes
var data = shapes.data();
// this'll sort the data such that the hovered d is last in the array
data.sort(function(a,b) { return d3.ascending(a == d, b == d); })
// now that the data is sorted, reorder the shapes to match
// the order within data.
shapes.data(data);// NOTE: there's a good chance this line is not necessary. Try taking it out.
shapes.order();
});
I am using NVD3.js multiChart to show multiple lines and bars in the chart. All is working fine, but the x-axis labels is aligned only to the line points, not bars. I want to correctly align labels directly below the bars as it should. But I get this:
With red lines I marked where the labels should be.
I made jsFiddle: http://jsfiddle.net/n2hfN/
Thanks!
As #Miichi mentioned, this is a bug in nvd3...
I'm surprised that they have a TODO to "figure out why the value appears to be shifted" because it's pretty obvious... The bars use an ordinal scale with .rangeBands() and the line uses a linear scale, and the two scales are never made to relate to one another, except in that they share the same endpoints.
One solution would be to take the ordinal scale from the bars, and simply adjust it by half of the bar width to make the line's x-scale. That would put the line points in the center of the bars. I imagine that something similar is done in the nv.models.linePlusBarChart that #LarsKotthoff mentioned.
Basically, your line's x-scale would look something like this:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
...where xScaleBars is the x-scale used for the bar portion of the chart.
By combing through the source code for nvd3, it seems that this scale is accessible as chart.bars1.scale().
Maybe someday the authors of nvd3 will decide that their kludge of a library deserves some documentation. For now, I can show you the kind of thing that would solve the problem, by making a custom chart, and showing how the two scales would relate.
First, I'll use your data, but separate the line and bar data into two arrays:
var barData = [
{"x":0,"y":6500},
{"x":1,"y":8600},
{"x":2,"y":17200},
{"x":3,"y":15597},
{"x":4,"y":8600},
{"x":5,"y":814}
];
var lineData = [
{"x":0,"y":2},
{"x":1,"y":2},
{"x":2,"y":4},
{"x":3,"y":6},
{"x":4,"y":2},
{"x":5,"y":5}
];
Then set up the scales for the bars. For the x-scale, I'll use an ordinal scale and rangeRoundBands with the default group spacing for nvd3's multiBar which is 0.1. For the y-scale I'll use a regular linear scale, using .nice() so that the scale doesn't end on an awkward value as it does by default in nvd3. Having some space above the largest value gives you some context, which is "nice" to have when trying to interpret a chart.
var xScaleBars = d3.scale.ordinal()
.domain(d3.range(barData.length))
.rangeRoundBands([0, w], 0.1);
var yScaleBars = d3.scale.linear()
.domain([0, d3.max(barData, function(d) {return d.y;})])
.range([h, 0])
.nice(10);
Now here's the important part. For the line's x-scale, don't make a separate scale, but just make it a function of the bars' x-scale:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
Here's the complete example as a JSBin. I've tried to document the major sections with comments so it's easy to follow the overall logic of it. If you can figure out from the nvd3 source code exactly what each of the elements of the multiChart are called and how to set the individual scales of the constituent parts, then you might be able to just plug in the new scale.
My feeling on it is that you need to have a pretty good handle on how d3 works to do anything useful with nvd3, and if you want to customize it, you're probably better off just rolling your own chart. That way you have complete knowledge and control of what the element classes and variable names of the parts of your chart are, and can do whatever you want with them. If nvd3 ever gets proper documentation, maybe this will become a simple fix. Good luck, and I hope this at least helps you get started.
I am using D3.js to draw an axis with ticks. I would like to hide only the last tick on the y-axis.
Maybe a picture will make it clearer. This is what my axis looks like at the moment - I'd like to hide the "21" and its associated tick.
Here is my current code:
var yAxisScale = d3.svg.axis().orient("left");
yAxisScale.scale(y_position);
yAxisScale.ticks(20).tickFormat(function(d) { return d+1; });
var yAxis = vis.selectAll("g.y.axis").data([1]);
Is there a way I can hide only the last tick, regardless of how many ticks there are on the y-axis?
I tried adding this line, but it doesn't work, even though the selectAll expression appears to return the right element.
d3.select(d3.selectAll("g.y.axis g")[0].pop()).style('opacity', 1e-6);
The opacity is still set to 1.
you should to take a look at axis.tickSize(). it allows you to set the size of the major, minor, and end ticks.
also see here for similar question:
Remove end-ticks from D3.js axis