How to transition D3 axis without tick text attribute reset? - javascript

I have a D3 project where I'm drawing a time axis along the left side of the screen. I want to have it smoothly transition on window resize so I'm using D3 transitions. However the axis setup appears to be changing the "dy" attribute on the tick labels immediately causing the tick labels to jump downward and then transition back into their normal place any time the SVG is transitioned. Is there any way to set the "dy" attribute of the tick text as part of the axis call or a better way to transition?
My initial (one-time) axis setup:
var timeScale = d3.time.scale().domain([minTime, maxTime]);
var yAxis = d3.svg.axis().scale(timeScale).tickFormat(d3.time.format("%-m/%-d %-I:%M%p")).orient("right");
I have a function to update/transition the SVG elements I'm using. The first time the SVG is drawn init is set to true, false afterwards.
function updateSVG(init) {
...
timeScale.rangeRound([topPadding, svgHeight]);
// Use a transition to update the axis if this is an update
var t = (init) ? svgContainer : svgContainer.transition().duration(750);
// {1}: Create Y axis
t.select("g.axis").call(yAxis);
// {2}: Move Y axis labels to the left side
t.selectAll("g.tick > text")
.attr("x", 4)
.attr("dy", -4);
...
}
On an update at {1} tick labels all have a "dy" attribute of "-4" from the previous attr() call. At {2} applying the axis resets the "dy" attribute of these elements to a default of ".32em" after which they transition slowly back to "-4" causing them to jitter up and down as the window is resized and the axis is redrawn.
Here is a working JSFiddle that demonstrates the jump on the y-axis when the Result box is resized, resize just by a few pixels and it should be obvious: http://jsfiddle.net/YkDk4/1/

Just figured this out. By applying a "transform" attribute instead of a "dy" attribute the axis call() does not overwrite the value. So:
t.selectAll("g.tick > text")
.attr("x", 4)
.attr("dy", -4);
becomes:
t.selectAll("g.tick > text")
.attr("x", 4)
.attr("transform", "translate(0,-4)");
and everything transitions smoothly.

According to the bug fix made in response to this problem with the text-anchor attribute:
How to tweak d3 axis attributes when transition is present?
It looks like the dy attribute is supposed to update immediately during transitions...but it's not.
In any case, the easiest solution is simply to take the dy update OUT of the transition and apply it directly:
t.select(".y")
.call(yAxis);
chartSvg.selectAll(".y g.tick > text")
.attr("dy", -4);
That should avoid the "bounce".

Related

Brush functionality in my d3 line chart is not working as expected

My implementation for Brush & Zoom functionality in my d3 line chart is not working as expected,
I followed this link - https://bl.ocks.org/EfratVil/92f894ac0ba265192411e73f633a3e2f,
Problems what I am facing is -
chart is not showing all the values, I have 4 data but it only shows 3 data
onClick of dot I am showing the rect which is not moving with the brush functionality
minor thing but chart always goes out of the box
My code sandbox - https://codesandbox.io/s/proud-firefly-xy1py
Can someone point out what I am doing wrong? thanks.
Please suggest me what I am doing wrong, thanks.
Your first point is going behind your clip area. For example, if you right click on the first visible circle and inspect element you will see all 4 circle elements are present in the dom. The first circle element is behind the axis.
This means you have to move your plot to the right. Unfortunately, the way you have coded the chart you have not appended a g element for the main chart and then appended the circles and path to that g element. As a result this has to be done in multiple places.
First we adjust your clip path as:
svg
.append("defs")
.append("SVG:clipPath")
.attr("id", "clip")
.append("SVG:rect")
.attr("width", containerWidth)
.attr("height", height)
.attr("x", 40)
.attr("y", 0);
next we adjust your circles
scatter
.selectAll(".foo")
.data(data)
.enter()
.append("circle")
.attr("class", "foo")
.attr("transform", "translate(40,0)")
and then your line
scatter
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line)
.attr("transform", "translate(40,0)");
You will have to account for this 40 px translate for your other elements as well. Although I am having a hard time destructuring your svg. I think this should give you the idea though. Check the axis matches the time points as well.
Check the code sand box
Update
To make the rectangles move with the brush, you will have to add code to your brushed const function to recalculate the x, y, width and height using the updated scales.
Update2
After going through the codesandbox presented in the comments I was able to add the code to update the rectangles to the brushed const as below to make the rects also move with the brushing:
// update rectangles
scatter
.selectAll(".rect-elements")
.attr("x", d => {
console.log(d);
return xScale(d.startTime) - 12.5;
})
.attr("y", 0)
.attr("width", 24)
.attr("height", height + 5);
Full working Code Sandbox.

Issue with rectangles not drawing equal with yaxis labels in d3v4

I am new to d3v4 and working on a chart where i need to show little rectangle on certain date matching to its title on yaxis. The problem i am facing is rectangles in the chart area not drawing equal to the yaxis point labels, i have tried changing the y value by hardcoding, it works fine but the point is the number of data object will change in real time like it could be any number of objects in an array. Here is the plunker
To draw the graph dynamically with limited data objects i've created few buttons on top of chart so that rectangles in the chart can draw equal to y-axis labels.
Any help is much appreciated.
You are using a band scale: that being the case, you should not change the y position, which should be just...
.attr('y', function(d) {
return yScale(d.title);
})
.. and you should not hardcode the height: use the bandwidth() instead:
.attr('height', yScale.bandwidth())
The issue now is setting the paddingInner and paddingOuter of the scale until you have the desired result. For instance:
var yScale = d3.scaleBand().domain(data.map(function(d) {
return d.title
}))
.range([height - 20, 0])
.paddingInner(0.75)
.paddingOuter(.2);
Here is the plunker with those changes: https://plnkr.co/edit/ZxGCeDGYwDGzUCYiSztQ?p=preview
However, if you still want (for whatever reason) hardcode the height or the width of the rectangles, use a point scale instead, and move the y position by half the height.

Rendering order for group elements (<g>) with d3.js

After reading the spec and this question I've understood, that the rendering order is actually a timing order.
The sub-elements of an <svg> are ordered in the z-axis regarding the timing of the rendering, meaning the element, which has been rendered first lies on bottom, the lastly rendered element on top of all others in the same area.
Is this differently for group elements (<g>)?
If you look at this code (a JsFiddle) and the outcome, the helper lines (<line> elements) lie above the graph lines (<path> elements). The helper lines are grouped within the axis group, each graph line would be drawn in a separate group.
But the axes are rendered first, at least the function addYAxis is called before handleLine runs. What am I missing here?
Note: in this answer, dealing with the rendering of red circles and blue squares in a chart, is stated, that
In this case the red circle will be visible above the blue square even though the blue square was created last. This is because the circle and the square are children of different group elements, which are in a pre-defined order.
But I don't get, why this is possible and how I can set this pre-defined order.
Ok if you want the axis lines to be behind the line charts.
First make a x-axis group and y axis group then make the line group.
This ensures the oder is preserved.
Inside your setSVG function do:
parts.svg.append('defs').append('clipPath') //add a clip
.attr('id', lineWrapperId)
.append('rect')
.attr({
width: basics.width + 'px',
height: basics.height + 'px'
});
parts.svg.append('g') // add x axis g
.attr('class', 'x-axis');
parts.svg.append('g') // add y axis g
.attr('class', 'y-axis');
//finally add line group
parts.pathFrame = parts.svg.append('g') // add lines group
.attr('clip-path', 'url(#' + lineWrapperId + ')')
.attr('class', 'line-wrapper');
Now when you make your x axis select its group and make the axis like below in function addXAxis and addYAxis:
if (!parts.axisX) {
parts.axisX = d3.select(".x-axis") //selectin the x axis group
.attr({
'class': 'x-axis',
transform: 'translate(0,' + (basics.height - basics.margin) + ')'
})
.call(xAxis); // leave out tick sizes for now
} else {
parts.axisX = d3.select('g.x-axis')
.call(xAxis);
}
Working example here

Avoid dx/dy attributes in d3 axis labels

I experienced problems with the position of text elements when exporting SVG files and opening it in Corel Draw (some older version). I fixed it by setting every dx/dy attribute to zero and added its value to the corresponding x/y attribute.
I wrote a helper function which is called with .each on every text element I use.
transformDXYtoXY: function(d, i) {
var that = d3.select(this);
var y = that.attr("y") == null ? 0 : parseFloat(that.attr("y"));
var dy = that.attr("dy") == null ? 0 : parseFloat(that.attr("dy"));
that.attr("y", y + dy);
that.attr("dy", 0);
// doing the same with dx/x
...
},
This was working great until I decided to transition axis on input change instead of redraw them:
axis = d3.svg.axis().scale(someScale);
d3.select('.axis')
.transition()
.call(axis)
.selectAll("text")
.each(transformDXYtoXY);
Without the call to transformDXYtoXY() the tick label position is off
The y/dy attributes are not being set, even though when I check for it inside transformDXYtoXY() it seems allright.
Is there a way to tell d3 to avoid using dx/dy? It looks like the problem occurs during transition().
The use of the dx and dy attributes is hardcoded in the source of D3 -- changing it would be a significant effort. However, there's an easy workaround. D3 transitions allow you to set up a listener for the end of the transition. You can leverage this to run your code to fix the attribute values (with minimal changes to your existing code):
d3.select('.axis')
.transition()
.call(axis)
.selectAll("text")
.each("end", transformDXYtoXY);
To clarify, the code that you have at the moment runs the function to fix the attributes immediately after setting up the transition which then runs and overwrites the attribute values. The code above runs the function after the transition is complete, i.e. no further attribute changes will occur.

axis label restoring to original position after hiding series

In my NVD3 chart here, I have shifted the X-axis label downward so as to go with the vertically aligned dates. For that I did:
xTicks.select('.nv-axislabel').attr("y", 90); // line # 81 in JS
Now the problem is that if I hide any of the series using the control on the top right, this X-axis label restores to its original position which is behing the dates.
How do I get it to stay at the new position, unaltered?
jsFiddle
Somewhat of a dirty solution, but may be good enough:
After changing the y attribute of the label, remove the class which is used by d3 internally to pick the label and reset its attributes:
xTicks.select('.nv-axislabel')
.attr("y", 90)
.classed({'nv-axislabel':false});
Then, as it would appear d3 just creates a new label in the unwanted position, you can add this to the CSS file:
.nv-x .nv-axislabel {
display:none;
}
I added it as a rule because doing it programatically (using .style()) takes too long and you can see it flicker.
FIDDLE
If you update your NVD3 library, to version 1.1.11b
You can use the in-built feature of rotating the labels. Try this :
chart.xAxis
.tickPadding(-5).rotateLabels(-90);
Change the x attribute of .tick major > text to 10 to move the position a bit lower.
Then you will NOT need the following code :
// translate and rotate x-axis ticks
var xTicks = d3.select('.nv-x.nv-axis > g').selectAll('g');
xTicks.selectAll('g > .tick > text')
.attr('transform', function(d, i, j) {
return 'translate (-10, 40) rotate(-90 0,0)'
});
// move x-axis label down
xTicks.select('.nv-axislabel').attr("y", 90)
Hope it helps

Categories

Resources