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)
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 an heatmap that show some data and a sparkline for each line of the heatmap.
If the user click on a row label, then the data are ordered in decreasing order, so each rect is placed in the right position.
Viceversa, if the user click on a column label.
Each react is placed in the right way but I'm not able to place the sparkline.
Here the code.
When the user click on a row label, also the path inside the svg containing the sparkline should be updated.
And then, when the user click on a column label, the svg containing the sparkline should be placed in the correct line.
To place the svg in the right place, I try to use the x and y attributes of svg. They are updated but the svg doesn't change its position. Why?
Here is a piece of code related to that:
var t = svg.transition().duration(1000);
var values = [];
var sorted;
sorted = d3.range(numRegions).sort(function(a, b) {
if(sortOrder) {
return values[b] - values[a];
}
else {
return values[a] - values[b];
}
});
t.selectAll('.rowLabel')
.attr('y', function(d, k) {
return sorted.indexOf(k) * cellSize;
});
Also, I don't know how to change the path of every sparkline svg. I could take the data and order them manually, but this is only good for the row on which the user has clicked and not for all the others.
How can I do?
The vertical and horizontal re-positioning/redrawing of those sparklines require different approaches:
Vertical adjustment
For this solution I'm using selection.sort, which:
Returns a new selection that contains a copy of each group in this selection sorted according to the compare function. After sorting, re-inserts elements to match the resulting order.
So, first, we set our selection:
var sortedSVG = d3.selectAll(".data-svg")
Then, since selection.sort deals with data, we bind the datum, which is the index of the SVG regarding your sorted array:
.datum(function(d){
return sorted.indexOf(+this.dataset.r)
})
Finally, we compare them in ascending order:
.sort(function(a,b){
return d3.ascending(a,b)
});
Have in mind that the change is immediate, not a slow and nice transition. This is because the elements are re-positioned in the DOM, and the new structure is painted immediately. For having a slow transition, you'll have to deal with HTML and CSS inside the container div (which may be worth a new specific question).
Horizontal adjustment
The issue here is getting all the relevant data from the selection:
var sel = d3.selectAll('rect[data-r=\'' + k + '\']')
.each(function() {
arr.push({value:+d3.select(this).attr('data-value'),
pos: +d3.select(this).attr('data-c')});
});
And sorting it according to data-c. After that, we map the result to a simple array:
var result = arr.sort(function(a,b){
return sorted.indexOf(a.pos) - sorted.indexOf(b.pos)
}).map(function(d){
return d.value
});
Conclusion
Here is the updated Plunker: http://next.plnkr.co/edit/85fIXWxmX0l42cHx or http://plnkr.co/edit/85fIXWxmX0l42cHx
PS: You'll need to re-position the circles as well.
I have a D3 text added inside a rectangle , where the value of text field is updated programtically. Now I need to expand the width based on the text value length but making sure to keep the default width as min width.
I have tried getting the new value.length and updating the d3 text width like below.
var length = //length of the updated text value
var elm = d3.selectAll("[id=s1]");
elm.attr('width', function(d) { return length; });
This works fine but when i delete the text D3 text box also shrinks all the way. I need to keep a default width and then just increase if the value is longer than that. Any idea how to do this?
You can use a ternary operator to check for a minimum value:
elm.attr('width', function() {
return length < minimumValue ? minimumValue : length;
});
Where minimumValue is, of course, your minimum value.
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();
});
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: