D3.js: Trigger an event in a different chart - javascript

I have two identical charts. The graphics for them are built like so:
circles.enter().append("circle")
.attr("r", 0)
.attr("fill", function(d) { return fill_color; })
.attr("class", function(d) { return "circle_" + d.id; })
.on("mouseover", function(d, i) { build_tooltip(d, i, this); })
.on("mouseout", function(d, i) { hide_tooltip(d, i, this); });
On mouseover, it triggers the following function:
build_tooltip = function(data, i, element) {
var content = "Title: " + data.title;
show_tooltip(content, d3.event);
}
My question is: How can I make it so mousing over a circle in Chart #1 triggers the same mouseover event in Chart #2, but with unique data for each chart? Chart #2 must generate its own set of data (in this example, just a title). So, how can I make Chart #2's mouseover event fire whenever Chart #1's does?
In jQuery, this would be quite simple -- there is a literal .trigger() event. But how can I go about accomplishing the same with D3?

Have you tried using D3's dispatch? If not, see through this example for more details on how to use it.

Related

d3js sankey transition after drag event

I'm trying to build an interactive d3.js sankey chart that allows the user to do two things:
Drag each node across the x,y axes
Click an 'Update' button that randomises the values of each link and updates the sankey chart accordingly
The current status of my code is as follows:
The user is able to drag each node without issue
If the user has not dragged any nodes, the 'Update' button works without issue
However, if the user tries to click the 'Update' button after dragging a node, then the nodes and links become detached as the sankey has seemingly forgotten where the nodes were previously
My question is, is it possible to force a sankey recalculation that takes into account the 'new' position of each dragged node? If so, how would that be done?
My current code is here: https://plnkr.co/edit/F03OYn8e457NZZbD?preview
Snippet of the update() function in question
function update() {
//randomise the link values
sankeyData.links.forEach(function (link) {
link.value = Math.max(Math.ceil(Math.random() * 10), 1)
})
//rerun sankey
sankeyData = sankey(sankeyData)
//update node positions
d3.selectAll(".sankey-node")
.transition()
.duration(1000)
.attr("x", function (d) {
return d.x0;
})
.attr("y", function (d) {
return d.y0;
})
.attr("height", function (d) {
return d.y1 - d.y0;
})
//this only updates the y0 and y1 coords of each link?
sankey.update(sankeyData)
//update link positions
d3.selectAll(".sankey-link")
.transition()
.duration(1000)
.attr("d", d3.sankeyLinkHorizontal())
.style("stroke-width", function (d) {
return Math.max(1, d.width);
})
}

Underline node text on mouse over

I have a graph made with d3.js and I have the following attributes and properties for the nodes:
// Enter any new nodes at the parent's previous position
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.on("click", click)
.on("dblclick", dblclick)
I would like to add the ability to underline the node title when hovering over it. Something like this which unfortunately doesn't work:
var nodeEnter = node.enter().append("g")
.on("mouseover").style("text-decoration","underline")
.on("mouseout").style("text-decoration","none")
EDIT: I would prefer to put a condition to make this happen only for some nodes of the graph.
You aren't using the selection.on() method correctly. In order to do something on an event you need to provide the method with a second parameter: a function that describes the action taken on the event:
D3v6+
.on("mouseover", function(event, datum) { ... })
D3v5 and before
.on("mouseover", function(datum, index, nodes) { ... })
In all versions of D3 this will be the target element (unless using arrow notation). The datum is the data bound to the target element, one item in the array passed to selection.data().
If you only provide one parameter it returns the current event handling function assigned to that event. In your case you likely haven't done this already (because you are attempting to do so), so .on("mouseover").style(...) will return an error such as "Cannot find property style of null" because .on("mouseover") will return null: there is no event handling function assigned to this event to return.
So, to highlight nodes on mouseover with some logic so we can have different outcomes for different nodes, we can use something like:
selection.on("mouseover", function(event, datum) {
if(datum.property == "someValue") {
d3.select(this).style("text-decoration","underline");
}
})
.on("mouseout", function(event,datum) {
d3.select(this).style("text-decoration","none");
})
Where the if statement can be replaced with whatever logic you prefer.
I see you are probably using a hierarchical layout generator, D3's hierarchical layout generators nest the original datum's properties into a data property so that layout properties and non layout properties do not collide, so datum.property may reside at datum.data.property (log the datum if you are having trouble).
var svg = d3.select("body")
.append("svg");
var data = [
"Underline on mouse",
"Never underline"
];
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", 20)
.attr("y", (d,i)=>i*50+40)
.text(d=>d)
.on("mouseover", function(event, datum) {
if(datum == "Underline on mouse") {
d3.select(this).style("text-decoration","underline");
}
})
.on("mouseout", function(event,datum) {
d3.select(this).style("text-decoration","none");
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
You can add an underline on hover using CSS
.node:hover{
text-decoration: underline;
}

How to redraw chart : d3.js chaining function and crossfilter filtering

Edit : JSBin dont work go to JS fiddle to see my code
I'm training my self with d3 and crossfilter and i face some difficulties.
I'm trying to make a chaining function to create bar chart. The idea is to not write all the code again and again for each bar chart i want to create.
I was inspired by the example Fast Multidimensional Filtering for Coordinated Views which is what i wanted to do (you can find code Here).
But I want to bring some personal touches in my generic barChart function.
I decided to do it with chaining function like in the example.
I understood how to create as many graphics as you wish, but i dont understand, (when brushes event appears), how to redraw all the bar depending on the filter.
If i wanted to do it outside a function i would define again the all the properties like x, y, axis etc. depending on the new data which is the filtered data like this:
var updateRange = function(filt){
data = dimension.filter(filt) //assuming dimension is a crossfilter dimension
// Scale the range of the data again
x.domain([0, d3.max(data, function(d) { return d.key; })+1]);
// Select the section we want to apply our changes to
var chart = d3.select(".chart")
//Update all rects
chart.selectAll("rect.hidden")
.data(data)
.transition()
.delay(function(d, i) {
return i * 50;
})
.duration(500)
.attr("y", function(d) {
return y2(d.value);
})
.attr("height", function(d) {
return height - y2(d.value);
});
I made a JSBin to discuss on how we can make the chart updated.
And this is the brush functions i use.
brush.on("brushstart", function() {
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", null);
});
brush.on("brush", function() {
var g = d3.select(this.parentNode),
extent = brush.extent();
if (round) g.select(".brush")
.call(brush.extent(extent = extent.map(round)))
.selectAll(".resize")
.style("display", null);
g.select("#clip-" + id + " rect")
.attr("x", x(extent[0]))
.attr("width", x(extent[1]) - x(extent[0]));
dimension.filter(extent);
});
brush.on("brushend", function() {
if (brush.empty()) {
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", "none");
div.select("#clip-" + id + " rect").attr("x", null).attr("width", width);
dimension.filterAll();
}
)};
waiting for your comments,
Chris.
Edit : some clarification
To be clearer, when i render the chart and use the brush the data are correctly filtered. (if i put some console.log i see the data filtered depending on the brush)
But the chart are not updated depending on the brush. I know the issue come from th way i used the brush event (brush.on().
I think i need to call the render function in some way but dont know how to do it with chaining function to be applied to all the charts.
Edit : Chart updated with external brush
The chart are now successfully updated when the brush is set externally (by clicking on the link).
Just adding this lines
if (brushDirty) {
brushDirty = false;
g.selectAll(".brush").call(brush);
div.select(".title a").style("display", brush.empty() ? "none" : null);
if (brush.empty()) {
g.selectAll("#clip-" + id + " rect")
.attr("x", 0)
.attr("width", width);
} else {
var extent = brush.extent();
g.selectAll("#clip-" + id + " rect")
.attr("x", x(extent[0]))
.attr("width", x(extent[1]) - x(extent[0]));
}
}
In order to update charts you can remove it and then redraw with new filters.
Something like this :
d3.selectAll(".chart").selectAll("svg").remove();
Or
$('#chart'+chart_id+' svg').remove();
and then redraw it by calling again your drawing function with updated data.
Hope this will help you. Sorry If I misunderstood you. I need to train my english =P
EDIT :
I found these examples without remove. It might help you.
http://jsfiddle.net/sx9myywh/
https://bl.ocks.org/RandomEtc/cff3610e7dd47bef2d01
I found where the problem was. And i feel stupid.
The brush.onevent was placed incide the function which generate the chart and have to be outside.
function my(div){
}
brush.on(...
I have updated the JS Fiddle with the correct answer.
I will create a github repository it will be cleaner.

Refresh function not being called properly d3

I am working with a scatterplot in d3. Dots on the graph represent a paper. On right click of a dot I have a context menu where there are 2 options: 1) to add that paper to the library (change type to In_library) and 2) Remove from library (remove paper from data completely).
I call the refreshGraph() function after each of these updates which redraws the graph with the updated data. But nothing happens which I assume is because the refreshGraph() is not being called properly? Or for option 1 type library is not being set properly? When refreshGraph is called after option 1 the dot should turn blue and on calling it for option 2 the dot should disappear from display as it has been removed from the alldata which is the data that is being used to draw the circles. Here is the relevant code:
allData = [];
var menu = [{
title: 'Add to Library',
action: function addToLibrary(elem, d, i) {
d3.json("connection6.php?paperID="+d.ID, function(error, dataJson) {
for(i=0;i<allData.length;i++){
if (d.type === "In_library")
{
alert("The paper: " + d.TITLE + " is already in your Library!");
return;
}
}
d.type = "In_library"; // is this the correct way to change the type if the input has a different type??
refreshGraph();
})
refreshGraph();
}
},
{
title: 'Remove from Library',
action: function removeFromLibrary (elem, d, i) {
d3.json("connection9.php?paperID="+d.ID, function(error, dataJson) {
//loop through allData and if selected ID has type In_library, remove from allData
for(i=0;i<allData.length;i++){
if (d.type == "In_library"){
allData.splice(i--,1);
}
}
refreshGraph();
})
}
}
]
function refreshGraph() {
// draw dots
var circles = svg.selectAll("circle")
.data(allData)
circles.transition()
.attr("cx", function(d) {return x(YearFn(d))})
.attr("cy", function(d) {return y(Num_citationsFn(d))})
circles.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) {return x(YearFn(d))})
.attr("cy", function(d) {return y(Num_citationsFn(d))})
.style("fill",function(d){
var colour = "black"
switch(d.type){
case "In_library":
colour = "blue";
break;
case "cited by":
colour = "red";
break;
case "cites":
colour = "green";
break;
case "selected":
colour = "magenta";
break;
default:
colour = "black";
}
return colour;
})
.on("mouseover", mouseHandler)
.on("mouseout", mouseoutHandler)
.on("click", clickHandler)
.on("contextmenu", rightClickHandler);
svg.select(".y.axis").call(yAxis);
svg.select(".x.axis").call(xAxis);
//don't want dots overlapping axis, so add in buffer to data domain
x.domain([d3.min(allData, YearFn)-1, d3.max(allData, YearFn)+1]);
y.domain([d3.min(allData, Num_citationsFn)-1, d3.max(allData, Num_citationsFn)+1]);
}
Any help is much appreciated I am new to d3 so thanks in advance!
You don't need to re-plot all the data each time a single point changes. Just update that one point.
function rightClickHandler() {
// if option 1
d3.select(this).style("fill", "blue");
// if option 2
d3.select(this).remove();
}
Your problem likely arises because when you call refreshGraph a second time (or third) your aren't clearly the circles that are already plotted. Your refreshGraph function isn't updating the points already plotted it's recreating them each time, and if you aren't clearing the points that are already there, you won't see the new points (or the absence of them, or the change in color), because they are hidden behind your old points.
EDIT:
If you want to re-add the data each time, you first have to clear the existing data. At the start of your refreshGraph function, add this line:
if(!d3.selectAll("circle").empty()) d3.selectAll("circle").remove();
i.e. if there are circle elements, remove them. This assumes you are only creating circle elements within the refreshGraph function. If you create them elsewhere, the you should probably use the .dot selector instead.

Adding OnClick event to a bar chart

This is all the code that I have, there is something wrong with the "on click" part that I couldn't figure out.
It errors and says "drill not defined"
Isn't it the way we can call a method on click event of one of those bar charts that I am drawing in the D3 section?
$( document ).ready(function() {
gon.data.pop(); // get rid of query_time element.
var dataset = gon.data;
function drill(d, i) {
console.log(d.total_count); //data object
var url = "http://localhost:4567/drill/" + d.brand_name + "/" + d.generic_name;
window.location.href = url;
}
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.attr("onclick", "drill()")
.style("height", function(d) {
return d.brand_name + "px";
});
});
To add event listeners to selections you should use selection's on method.
You can write
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.on("click", drill)
.style("height", function(d) {
return d.brand_name + "px";
});
Move your declaration of the drill function to outside of your document-ready handler.
Better still, use the on method that d3 provides, rather than the onclick attribute.
you can also use jQuery .click() if you want, here's an example
$("#test").click(function(){
sayHello();
});
jsfiddle here:
http://jsfiddle.net/SGRmX/1/

Categories