Add graph in dc.js tooltip - d3.js

I'm building a number of graphs using crossfilter and dc.js. Among others, there is a row chart and an histogram (a bar chart).
What I am trying to do is to create a tooltip on the row chart which will show the histogram.
Looking at this SO-question I saw an example using d3-tip. I have made an attempt in this jsfiddle. However, I cannot see how to embed a div in the tooltip.
Any suggestion? (If using plain d3 is better, I'm ok with that.)
Snippet of code is:
function draw_row(div_id){ ...; return row_chart; }
function draw_hist(div_id){ ...; return bar_chart; }
var rate_chart = draw_row('#rate').title(function(){return'';});
dc.renderAll();
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function () {
// What to put in here???
draw_hist('#distr').render();
return "<div id='distr'>Distribution<br></div>"
});
d3.selectAll("#rate g.row")
.call(tip)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);

Fun project!
Yes, as you noticed, you're not going to be able to render the chart while you're in the .html() callback - that only returns static HTML, and I don't think you can give it an element instead.
So we'll have to find a place to render after the HTML has already been generated. Luckily, d3-tip doesn't try to handle mouse events or anything like that - the code which displays the tip is right there in the code you've posted:
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
So we can wrap tip.show in a function of our own, and then render the chart into the tip once it's on the screen.
We have to watch out because mouseover will fire every time the mouse moves, and we probably don't want to replace the tip-chart until we hover over another bar. So we'll remember the id of the last bar we hovered:
var last_tip = null;
d3.selectAll("#rate g.row")
.call(tip)
.on('mouseover', function(d) {
if(d.key !== last_tip) {
tip.show(d);
draw_hist('#distr').render();
last_tip = d.key;
}
})
.on('mouseout', function(d) {
last_tip = null;
tip.hide(d);
});
Finally, d3-tip needs to know the size of the tip content in order to render in the right place. (If it accidentally renders on top of the element, this can cause horrible flickering when the mouse goes over the tip, registering mouseout on the element.)
So we'll just hard-code that, since we're hard-coding the chart size anyway. 20 extra pixels to fit the title:
.html(function (d) {
return "<div id='distr' style='min-width:300px; min-height: 320px'>Distribution<br></div>"
});
Looks pretty cool with the default translucent black style from d3-tip:
Here's the fork of your fiddle: https://jsfiddle.net/gordonwoodhull/hkx7j3r5/10/

Related

d3 (v4) zoom using transitions doesn't seem to work

I am using the d3 zoom behaviour and attempting to use a transition for both a translateTo and a scaleTo at the same time. If I make the calls to zoom without a transition everything works fine. If I use a transition for just one of the transforms it also works, but if I attempt to use a transition for both it fails (it appears to only apply the first transform). I have a JSFiddle with several combinations here: JSFiddle
Here's the code that isn't working as I expect
svg.transition()
.duration(750)
.call(zoom.scaleTo, 2)
.call(zoom.translateTo, 50, 50)
You can do like this:
svg.transition()
.duration(750)
.call(zoom.scaleTo, 2)
.transition() <--- this is missing.
.call(zoom.translateTo, 50, 50)
First zoom then translate.
working code here
EDIT
Performing zoom and translate both # same time you need to tween.
function twizzle(selection, duration) {
d3.select(selection).transition()
.duration(duration)
.tween("attr:transform", function() {
//interpolate from start to end state
var i = d3.interpolateString("scale(1)translate(0,0)", "scale(2)translate(50,50)");
return function(t) {
selection.attr("transform", i(t));
};
});
}
Now call the function like this:
d3.select('body')
.append('button')
.text('transition both - scale first')
.on('click', function() {
//on click call the function created above
group.call(twizzle, 750) <-- perform the scale and translate on the group not on the SVG.
})
working code here

D3 Selection Highlight (efficiency?)

I have a simple visual of many rects, over 100 I'd say. For aesthetic purposes I want to create a high light effect on mouse click. I also wanted to make this effect somewhat intuitive by removing that effect once the user clicks on a new rect. However I couldn't get this to work without resorting to a d3.selectAll() call, so I'm thinking this approach might not be ideal if this project gets any bigger. Here is the code:
.on('click.highlight', function() {
//set any previously highlighted rects back to normal color/brightness
d3.selectAll('.highlight').transition().duration(250)
.style('fill', function(d) { return d3.rgb(d.color)})
d3.select(this).classed('highlight',true);
//now it's safe to assign the current highlighted rect a brighter hue... i think
d3.select(this).transition().duration(250)
.style('fill', function(d) { return d3.rgb(d.color).brighter(.5)})
})
Though this code does what I wanted it to do, but presumably there could only ever be 1 other highlight rect to worry about at any give time. So again, I'm not sure that using d3.selectAll() is warranted here.
So anyway, is there a more efficient way? I'd like to keep it all within one .on('click') function if possible.
If you are looking to avoid use of .selectAll, you could create a selection of one rect that contains the last clicked rectangle. Each time you click on a rectangle:
unhighlight the previously selected highlighted rect
update that selection to reflect the most recently clicked rectangle
highlight the newly selected rect
I use the variable highlightedRect to hold the selection that will allow the above workflow:
var svg = d3.select("body").append("svg")
.attr("width",600)
.attr("height",400);
var highlightedRect = d3.select(null);
var rects = svg.selectAll("rect")
.data(d3.range(1600))
.enter()
.append("rect")
.attr("y",function(d) { return Math.floor(d/50)*12; })
.attr("x",function(d) { return d%50 * 12 })
.attr("width",11)
.attr("height",11)
.attr("stroke","white")
.on("click",function(d) {
// Recolor the last clicked rect.
highlightedRect.attr("fill","black");
// Color the new one:
highlightedRect = d3.select(this);
highlightedRect.attr("fill","steelblue");
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

how to transition a multiseries line chart to a new dataset

I could really use some guidance setting up a transition on my multiseries line chart. As an example of what I need, I've started with this great multiseries line chart: http://bl.ocks.org/mbostock/3884955. To that code, I've added an update() function that's called once using setInterval(). I've also created a new data set called data2.csv which is similar to data.tsv but has different values.
The update function should change the data that the line chart is displaying. Forget about making a nice smooth transition, I can't even get the data to update in the chart view. When I try using the update function, it looks like the new data is loaded properly into the javascript variables, but the lines on the chart don't change at all.
I've seen variations on this question asked a few times but haven't found an answer yet. Can anyone help me figure out how to transition this multi-series line chart to a new dataset (also multiseries)?
function update() {
d3.csv("data2.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
// format the date
data.forEach(function(d) {
d.date = parseDate(d.date);
});
// rearrange the data, same as in the original example code
var cities2 = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, temperature: +d[name]};
})
};
});
// update the .city g's to the new dataset
var city2 = svg.selectAll(".city")
.data(cities2);
// redraw the lines with the new data???
city2.selectAll("path")
.attr("d", function(d) { return line(d.values); });
clearInterval(transitionInterval);
});
}
UPDATE: NikhilS's answer contains the key to the solution in the comment trail.
You should make sure you are following the enter + update process as outlined by Mike Bostock in his stuff on the General Update Pattern. It looks like you haven't invoked any kind of d3 transition. You also haven't specified an enter or exit for the update function, which will cause problems if you have new data coming in and/or old data going out. Try changing this:
var city2 = svg.selectAll(".city")
.data(cities2);
city2.selectAll("path")
.attr("d", function(d) { return line(d.values); });
to the following:
var city2 = svg.selectAll('.city')
.data(cities2);
var cityGroups = city2.enter().append('g')
.attr('class', 'city');
cityGroups.append('path')
.attr('class', 'line');
d3.transition().selectAll('.line')
.attr('d', function(d) { return line(d.values); });
city2.exit().remove();
I made a basic data re-join and update demo a while back, which you can view here.
use d3 Transition, you can make some sort of animation.
If you want to select a sub-interval of the data to plot the graph, no need manipulation on the data, just use a d3 brush and clip the graph
For a multi-series line graph with most of the line graph elements, you could refer to this example: http://mpf.vis.ywng.cloudbees.net/

Dragging on force layout prevents other mouseup listeners

I want to enable dragging in a d3.js force layout. When dragging a circle and release the mouse button, I want to call a specific function via callback, like this:
this.force = d3.layout.force()
.nodes(this.nodes)
.size([this.width, this.height]);
// enable dragging
this.circle
.call(this.force.drag)
.on("dragend", function() {
console.log("You should see this, when releasing a circle.");
})
.on("mouseup.drag",function(d,i) {
console.log("Or see this.");
});
Unfortunately the event is never fired/consumed completely by the force.drag handler.
So how can I execute a given callback function in a d3 force layout at the end of a drag?
You are not calling the "dragend" event on this.force.drag here.
This also depends on how you have defined this.force.drag.
This should work for you
myCustomDrag = d3.behavior.drag()
.on("dragstart", function(d,i){
//do something when drag has just started
})
.on("drag", function(d,i){
//do something while dragging
})
.on("dragend", function(d,i){
//do something just after drag has ended
});
In the above code, just use call(myCustomDrag) on an element (circle here) on which you want this drag behaviour to be present.

nvd3.js chart ajax data redraw - missing hovereffect + former yAxis scale

I am using nvd3 to draw a simple line chart with data receiving via an ajax request. It is working perfectly with the first drawing request but not on redrawing. The chart redraws by calling the same drawing function but with different data + differen max/min values.
When redrawing the chart with new data the "hover circle" does not appear, whereas the tooltip does. Furthermore when clicking on the legend of the chart and force a redraw by that the hover appears again, but the values of the yAxis are changed to these of the first drawn chart.
So far I assume that when redrawing the chart still holds the old max/min values - but only concerning the "hover" effect. The general chart looks fine so far also on redraw - the problem just faces the hover and that's it.
Sounds pretty confusing, but hopefully you will get the point.
Some code:
d3.json(queryurl, function(data2){
nv.addGraph(function(jsonData) {
if(chart){
chart.remove();
}
chart = nv.models.lineChart()
.x(function(d) { return d[0] })
.y(function(d) { return d[1] })
.color(d3.scale.category10().range());
chart.xAxis
.tickFormat(function(d) {
return d3.time.format('%x')(new Date(d))
});
chart.yAxis
.scale()
.tickFormat(d3.format(''));
chart.lines.yDomain([maxmin.max,maxmin.min]);
d3.select('#chart1 #chartsvg')
.datum(data2)
.transition().duration(600)
.call(chart);
nv.utils.windowResize(chart.update);
});
});
return chart;}
Try using .empty() on the svg element before redrawing.
I've only just started with NVD3 and D3 myself, however am doing a similar thing. What worked for me is to separate the data update function with the chart creation function. Do note the caveat below though...
I have the following to create the graph:
initGraph = function(url) {
d3.json(url, function(data) {
nv.addGraph(function() {
chart = nv.models.multiBarChart();
d3.select('#chart svg').datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
});
};
And the following function to update it:
redrawGraph = function(url) {
d3.json(url, function(data) {
d3.select('#chart svg').datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
});
};
I don't know if this is the recommended solution as I'm still at the "hack until it works" stage. With this, all the functions of the chart work after invocation of redrawGraph() (including axes redraw and tooltips).
Caveat: this seems to occasionally result in miscalculated ticks on recalculation:

Resources