d3 - Axis Label Transition - d3.js

In Mike Bostock's cubism demo (http://bost.ocks.org/mike/cubism/intro/demo-stocks.html), there is a cursor which displays the values of all horizon charts on display. Furthermore, the cursor text shows the time axis point in time. As the cursor text obscures an axis label, the label fades.
I am working on a similar display with d3.js (but not cubism). I have all working except that fade portion. I have searched through the CSS in the developer's window, searched the source code (as best I could), but I don't understand what manner of magic is being used to accomplish this feat. I've even looked through SO "axis label transition" questions, but I have failed to connect the dots on xaxis label transitions.
How does that fade in/out when obscured by text happen?
UPDATE:
I think I located the event script area where this happens - its just a little over my head at the moment - can anyone help me decipher what this event listener is doing? Specifically, in the second g.selectAll in the else clause below - what data (d) is being used here? What is causing this event to fire?
This is the coolest part of the display (outside of the horizon charts), I would love to figure this out ...
context.on("focus.axis-" + id, function(i) {
if (tick) {
if (i == null) {
tick.style("display", "none");
g.selectAll("text").style("fill-opacity", null);
} else {
tick.style("display", null).attr("x", i).text(format(scale.invert(i)));
var dx = tick.node().getComputedTextLength() + 6;
g.selectAll("text").style("fill-opacity", function(d) { return Math.abs(scale(d) - i) < dx ? 0 : 1; });
}
}
});

I used this as reference to accomplish the same effect.
I'm not sure what the context variable is or how the id's are set or what the tick flag references but what I did was simply update the opacity of the ticks according to their proximity to the mouse. With this, the vertical tick fades as well as the label text.
svg.selectAll('.x.axis .tick').style('opacity', function (d) {
return Math.min(1, (Math.round(Math.abs(d3.mouse(svg.node())[0] - x(d))) - 10) / 15.0);
});
This way, the opacity is set to 0 if it's within 10 pixels, and fades from 1-0 between 10 and 25. Above 25, the opacity would be set to an increasingly large number, so I clamp it to 1.0 using the Math.min function.
My labels are slightly rotated, so I also added an offset not shown inside the formula above (a +3 after [0]) just to make it look a bit nicer. A year late to answer your only question, but hey it's a nice effect.

same answer as Kevin Branigan's post, but using the d3 scale to calculate the opacity value.
var tickFadeScale = d3.scale.linear().domain([10,15]).range([0,1]).clamp(true);
svg.selectAll('.x.axis .tick').style('opacity', function (d) {
return tickFadeScale(Math.abs(d3.mouse(svg.node())[0] - x(d)));
}

Related

d3.brush does not work in Firefox or Edge when embedded

I'm using the Swiper API. When there's any d3 Chart embedded, the brush receives senseless mouse coordinates, in any case not relative to the container where the click occurs. (That's in fact at least the surrounded svg.)
I'm trying to find a solution but I don't know how I can force d3.brushX to use mouse coordinates which are really relative.
I don't know whether this is a bug or not, it has probably not really to do with the brush itself, rather how the browser pass mouse clicks top-down the DIV's until the SVG will be reached.
Here's the Fiddle.
(just for the annoying code rule:)
// Add brushing
var brush = d3.brushX()
The second slide contains an embedded d3 line chart example, taken from here.
The fiddle works only in Chrome 75+.
Not in Firefox 68+ nor in Edge 44+.
Running the chart example standalone, it works in all available browsers. So I designate this post for Swiper and D3 hopefully to get a hint for an solution.
According to the problem here I found out, that I can change the behavior in the point.js routines an an workaround.
If a D3 chart makes use of an brush **and ** the SVG of the chart element is embedded by a surrounding DIV with an explicite width, mouse clicks will not be interpreted correctly in Firefox or Edge. In Chrome it works perfectly.
I changed the code like this to achieve that it works in FF and Edge, but lose functionality with Chrome:
function reverseTraversal(node,targetTagName) {
var p = node;
while(p.tagName != targetTagName) p = p.parentNode;
return p;
}
function point(node, event) {
var svg = node.ownerSVGElement || node;
if (svg.createSVGPoint) {
var point = svg.createSVGPoint();
var p = reverseTraversal(node,"DIV");
var rect = p.getBoundingClientRect();
point.x = event.clientX + rect.width, point.y = event.clientY;
point = point.matrixTransform(node.getScreenCTM().inverse());
return [point.x, point.y];
}
var rect = node.getBoundingClientRect();
return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop];
}
As you can see, I have to traverse backwards until the closest DIV will be reached, get the bounding and add its width to the clientX coordinate.
Without adding the fixed width, the brush is unusable in the particular case.
To get working with all the browsers, maybe a switch is necessary.
It's not a perfect solution, just a workaround for d3.brushX behavior.

Rendering in the background of a dc.js chart with renderlet

I use dc.js for showing the results of multiple classification algorithms. More specifically, I want to show a precision recall chart (each point corresponds to a result of a classification system).
I already used a dc.js scatter chart for this which works fine.
Additionally I would like to have a d3 contour in the background of the chart which shows the F-measure.
This is already implemented. The only issue is that the contour part is in the foreground and not in the background of the chart.
Please have a look at the jsfiddle for a full example.
Two questions are still open for me because I'm not a dc.js or d3 expert:
Is there a way to put the contour in the background or the symbols(cycles) of the scatter chart in the foreground (I already tried it with the help of this stackoverflow question but with no success)
I used the 'g.brush' selector to get the area of the inner chart. This works fine as long as the brushing is turned on. Is the selector a good way to go or are there better alternatives (which may also work if brushing is switched off).
In my example I put the contour part in the upper left corner to see if it works but I also provide the code (currently uncommented) to increase the width and height of the contour to the correct size.
chart
.on('renderlet', function (chart) {
var innerChart = chart.select('g.brush');
var width = 300, height=300;
//getting the correct width, height
//var innerChartBoundingRect = innerChart.node().getBoundingClientRect();
//var width = innerChartBoundingRect.width, height=innerChartBoundingRect.height;
[contours, color] = generateFmeasureContours(width,height, 1);
innerChart
.selectAll("path")
.data(contours)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", d => color(d.value));
var symbols = chart.chartBodyG().selectAll('path.symbol');
symbols.moveToFront();
});
jsfiddle
Putting something in the background is a general purpose SVG skill.
SVG renders everything in the order it is declared, from back to front, so the key is to put your content syntactically before everything else in the chart.
I recommend encapsulating it in an svg <g> element, and to get the order right you can use d3-selection's insert method and the :first-child CSS selector instead of append:
.on('pretransition', function (chart) {
// add contour layer to back (beginning of svg) only if it doesn't exist
var contourLayer = chart.g().selectAll('g.contour-layer').data([0]);
contourLayer = contourLayer
.enter().insert('g', ':first-child')
.attr('class', 'contour-layer')
.attr('transform', 'translate(' + [chart.margins().left,chart.margins().top].join(',') + ')')
.merge(contourLayer);
A few more points on this implementation:
use dc's pretransition event because it happens immediately after rendering and redrawing (whereas renderlet waits for transitions to complete)
the pattern .data([0]).enter() adds the element only if it doesn't exist. (It binds a 1-element array; it doesn't matter what that element is.) This matters because the event handler will get called on every redraw and we don't want to keep adding layers.
we give our layer the distinct class name contour-layer so that we can identify it, and so the add-once pattern works
contourLayer = contourLayer.enter().insert(...)...merge(contourLayer) is another common D3 pattern to insert stuff and merge it back into the selection so that we treat insertion and modification the same later on. This would probably be simpler with the newer selection.join method but tbh I haven't tried that yet.
(I think there may also have been some improvements in ordering that might be easier than insert, but again, I'm going with what I know works.)
finally, we fetch the upper-left offset from the margin mixin
Next, we can retrieve the width and height of the actual chart body using
(sigh, undocumented) methods from dc.marginMixin:
var width = chart.effectiveWidth(), height = chart.effectiveHeight();
And we don't need to move dots to front or any of that; the rest of your code is as before except we use this new layer instead of drawing to the brushing layer:
contourLayer
.selectAll("path")
.data(contours)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", d => color(d.value));
Fork of your fiddle.
Again, if you'd like to collaborate on getting a contour example into dc.js, that would be awesome!

Interrupt scrolling transitions in D3.js

I'm using the scrollama javascript library to write a "scrollytelling" article that involves transitioning D3 graphs in and out of view as the user scrolls. It is mostly working, but the graphs pile up on top of each other if I scroll too quickly.
Here is a jsfiddle based on this example by the scrollama author. In my example, the colored dots should fade in one at a time. If you were to scroll quickly to the end, the intermittent dots should not show up. The following snippets show how I've set up the transitions:
I define some functions that create my "graphs", and then call them.
var makeCircle0 = function(){
g.append("circle")
.attr("cx", 50)
.attr("cy", 100)
.attr("r", 20)
.attr("fill", "red")
.attr("class", "redcircle")
g.selectAll(".redcircle")
.attr("opacity", 0)
}
makeCircle0();
// Do this for makeCircle1, 2, and 3, also.
Then, I make functions to handle the transitions. This one says to make the red circle fade in and put the other circles at 0 opacity. I do this for all the circles/stages.
var showCircle0 = function(){
g.selectAll(".redcircle")
.transition()
.duration(1000)
.attr("opacity", 1)
g.selectAll(".yellowcircle").attr("opacity", 0)
g.selectAll(".greencircle").attr("opacity", 0)
g.selectAll(".bluecircle").attr("opacity", 0)
}
This section creates an array of my transition functions so that I can call them at specific steps in the page as you scroll. This is similar to how Jim Vallandingham handled his scroller.
var activateFunctions = [];
activateFunctions[0] = showCircle0;
activateFunctions[1] = showCircle1;
activateFunctions[2] = showCircle2;
activateFunctions[3] = showCircle3;
Finally, this calls the desired function at the right step in the page. Which it does... but not without halting the other transitions that got triggered in a previous step, resulting in multiple dots showing up at various stages.
function handleStepEnter(response) {
step.classed('is-active', function (d, i) {
return i === response.index;
})
figure.call(activateFunctions[response.index])
}
How can I prevent this?
If you need to interrupt a transition, d3-transition has a method for that:
selection.interrupt();
This will cancel a transition on an selection. If using named transitions you can specify a name by providing interrupt with one argument indicating the name of the transition to cancel.
If this is a generic version of your function to show an element:
function show() {
selectionToHide.attr("opacity",0);
selectionToShow.transition()
.attr("opacity",1);
}
Without using selection.interrupt you set the opacity to zero, and then the next tick of any transition in progress continues to update the opacity and finishes carrying out the transition. By adding interrupt we avoid that. Here's an updated fiddle.
However, there is another solution - we can apply another transition on the elements that we want to not show. To do so we just replace the transition with a new one:
function show() {
selectionToHide.transition()
.attr("opacity",0);
selectionToShow.transition()
.attr("opacity",1);
}
This will replace existing unnamed transitions (as yours are not named) and fade out elements, rather than simply hiding them all at once. Here's a fiddle of that. Of course if you have many elements this can be refined as to only apply a transition on any elements that are transitioning (not those that are already hidden) to reduce the amount of active transitions.
I haven't touched the scrolling, the circle that is shown should have its index match the displayed number, but it seems the number doesn't always match the scroll position, but this is a separate issue

Stroke rendering inside of Topojson area. How to change the style of the full shape?

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();
});

how to animate and play over time in d3.js?

I am a novice while working on d3.js.
I wanted to know how can we Animate some data (eg. Change colors) with respect to time.
eg. Let's say, in Monitoring app, I am projecting cluster data over US Map. Projection is done by drawing a circle and filling it by RED, GREEN or YELLOW color depending on it's status.
When we start monitoring, ideally all circles will be filled with "GREEN" color and then over time color can change to "YELLOW" or "RED" depending on how cluster is behaving.
So if I need to play these color changes over time in some time window, how can it be done ?
If you can point me to any of the similar examples , that will help too ?
Thanks
Take a look at http://mbostock.github.com/d3/tutorial/bar-2.html. Basically you'll need a redraw function that you'll call whenever you want to update your chart. (Note: there is nothing special about the name of this function, you can call it whatever you want.)
You can use setInterval to create a basic timer, this is the rate that your chart will be updated.
setInterval(function() {
redraw(); // call the function you created to update the chart
}, 1500);
Then you define redraw to update the chart data. This is a redraw function for a bar chart, but yours would be similar. You would just be adjusting the color based on the data instead of the y position and height.
function redraw() {
// Update…
chart.selectAll("rect")
.data(data)
.transition()
.duration(1000)
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("height", function(d) { return y(d.value); });
}
Note that this is a simplified version, I recommend reading the page that I linked above for a more complete example.

Resources