D3: How to validate data during data binding? - javascript

I am trying to render a gantt chart, where I am binding my data in d3 and rendering circle in both end. My data is somewhat similar to this structure:
function Event(start, end) {
this.startTime = start;
this.endTime = end;
}
I bind my data as usual:
myplot.selectAll(".EventStart")
.data(EventList).enter()
.append("circle")
.attr("class", "EventStart")
.attr("cx", function (d) { return scaleX(d.startTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
myplot.selectAll(".EventEnd")
.data(EventList).enter()
.append("circle")
.attr("class", "EventEnd")
.attr("cx", function (d) { return scaleX(d.endTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
Now, this will render two white circle at teh begining and end of my events.
But I want to omit rendering the 2nd circle if startTime and EndTime is same.
How can I do it?
Thanks.

You can either filter the dataList before binding
myplot.selectAll(".EventEnd")
.data(EventList.filter(function(d){ return d.startTime!=d.endTime }))
.enter()
.append("circle")
.attr("class", "EventEnd")
.attr("cx", function (d) { return scaleX(d.endTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
OR
Filter as shown below.
myplot.selectAll(".EventEnd")
.data(EventList)
.enter()
.append("circle")
.filter(function(d) { return d.startTime!=d.endTime })
.attr("class", "EventEnd")
.attr("cx", function (d) { return scaleX(d.endTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");

Related

Dispatching drag-end event

I want to programmatically fire the "end" event of d3-drag.
I have some circles and have the drag-handling of them implemented like so:
...
.call(d3.drag()
.on("drag", function () {...})
.on("end", function () {...})
)
Now, later in my code, I would like to trigger the "end" part of this programmatically.
I have already tried something like this:
d3.select("#myID").dispatch("end");
d3.select("#myID").dispatch("dragend");
d3.select("#myID").call(d3.drag().dispatch("end"));
If you don't need to generate any actual event data, and I understand the question correctly, you can do this relatively easily without d3.dispatch directly. The below will give you this and the node data itself (in d3v5 it will also give you i and nodes).
D3v5 and earlier
In d3v5 and earlier, the signatures for a function passed to selection.each() and drag.on() were the same. In this case you can easily assign the function to a variable and pass it to both. Alternatively, you could access the drag event function with drag.on("typeName").
Here's a quick example:
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height",300);
var data = [{x:40,y:100},{x:250,y:100}];
var circles = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", 10)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("fill", function(d,i) {
return ["steelblue","crimson"][i]
})
var drag = d3.drag()
.on("drag", function(d) {
d.x = d3.event.x; d.y = d3.event.y;
d3.select(this)
.attr("cx", d.x)
.attr("cy", d.y);
})
.on("end", function(d) {
console.log(d.x+","+d.y);
d3.select(this)
.transition()
.attr("r", 30)
.transition()
.attr("r", 10);
})
circles.call(drag);
d3.select("button").on("click", function() {
var circle = d3.select("circle")
.each(drag.on("end"));
})
circle {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button>Trigger Drag End On Blue Circle</button>
D3v6
In d3v6 the signatures of functions passed to selection.each() and drag.on() are different. The datum is the first parameter of the former and the second parameter of the latter. So we could use Function.apply() within selection.each() to trigger the end function and pass the proper this and d while passing null for the event data.
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height",300);
var data = [{x:40,y:100},{x:250,y:100}];
var circles = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", 10)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("fill", function(d,i) {
return ["steelblue","crimson"][i]
})
var drag = d3.drag()
.on("drag", drag)
.on("end", dragend)
circles.call(drag);
d3.select("button").on("click", function() {
var circle = d3.select("circle")
.each(function(d) {
dragend.apply(this,[null,d])
})
})
function dragend(event,d) {
console.log(d.x+","+d.y);
d3.select(this)
.transition()
.attr("r", 30)
.transition()
.attr("r", 10);
}
function drag(event,d) {
d.x = event.x; d.y = event.y;
d3.select(this)
.attr("cx", d.x)
.attr("cy", d.y);
}
circle {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.0.0/d3.min.js"></script>
<button>Trigger Drag End On Blue Circle</button>

Find original color of element before mouseover

I have a bubble chart in which I make bubbles in the following way:
var circles = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("opacity", 0.3)
.attr("r", 20)
.style("fill", function(d){
if(+d.student_percentile <= 40){
return "red";
}
else if(+d.student_percentile > 40 && +d.student_percentile <= 70){
return "yellow";
}
else{
return "green";
}
})
.attr("cx", function(d) {
return xscale(+d.student_percentile);
})
.attr("cy", function(d) {
return yscale(+d.rank);
})
.on('mouseover', function(d, i) {
d3.select(this)
.transition()
.duration(1000)
.ease(d3.easeBounce)
.attr("r", 32)
.style("fill", "orange")
.style("cursor", "pointer")
.attr("text-anchor", "middle");
texts.filter(function(e) {
return +e.rank === +d.rank;
})
.attr("font-size", "20px");
}
)
.on('mouseout', function(d, i) {
d3.select(this).transition()
.style("opacity", 0.3)
.attr("r", 20)
.style("fill", "blue")
.style("cursor", "default");
texts.filter(function(e) {
return e.rank === d.rank;
})
.transition()
.duration(1000)
.ease(d3.easeBounce)
.attr("font-size", "10px")
});
I have given colors red, yellow, green to the bubbles based on the student percentile. On mouseover, I change the color of bubble to 'orange'. Now the issue is, on mouseout, currently I am making colors of bubbles as 'blue' but I want to assign the same color to them as they had before mouseover, i.e., red/green/yellow. How do I find out what color, the bubbles had?
One way is to obviously check the percentile of student and then give color based on that(like I have initially assigned green/yellow/red colors), but is there any direct way of finding the actual color of bubble?
Thanks in advance!
There are several ways for doing this.
Solution 1:
The most obvious one is declaring a variable...
var previous;
... to which you assign to the colour of the element on the mouseover...
previous = d3.select(this).style("fill");
... and reuse in the mouseout:
d3.select(this).style("fill", previous)
Here is a demo:
var svg = d3.select("svg");
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var previous;
var circles = svg.selectAll(null)
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d, i) {
return 50 + 50 * i
})
.attr("r", 20)
.style("fill", function(d, i) {
return colors(i)
})
.on("mouseover", function() {
previous = d3.select(this).style("fill");
d3.select(this).style("fill", "#222");
}).on("mouseout", function() {
d3.select(this).style("fill", previous)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Solution 2:
However, D3 has a nice feature, called local variables. You simply have to define the local...
var local = d3.local();
..., set it on the mouseover:
local.set(this, d3.select(this).style("fill"));
And then, get its value on the mouseout:
d3.select(this).style("fill", local.get(this));
Here is the demo:
var svg = d3.select("svg");
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var local = d3.local();
var circles = svg.selectAll(null)
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d, i) {
return 50 + 50 * i
})
.attr("r", 20)
.style("fill", function(d, i) {
return colors(i)
})
.on("mouseover", function() {
local.set(this, d3.select(this).style("fill"));
d3.select(this).style("fill", "#222");
}).on("mouseout", function() {
d3.select(this).style("fill", local.get(this));
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Solution 3:
Since DDD (also known as D3) means data-driven documents, you can use the bound datum to get the previous colour.
First, you set it (in my demo, using the colors scale):
.style("fill", function(d, i) {
return d.fill = colors(i);
})
And then you use it in the mouseout. Check the demo:
var svg = d3.select("svg");
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var circles = svg.selectAll(null)
.data(d3.range(5).map(function(d) {
return {
x: d
}
}))
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d) {
return 50 + 50 * d.x
})
.attr("r", 20)
.style("fill", function(d, i) {
return d.fill = colors(i);
})
.on("mouseover", function() {
d3.select(this).style("fill", "#222");
}).on("mouseout", function(d) {
d3.select(this).style("fill", d.fill);
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
For using this solution #3, the element's datum has to be an object.
PS: drop that bunch of if...else for setting the style of the bubbles. Use a scale instead.

Add points to D3 chart

I was trying to add points to this chart http://bl.ocks.org/nsonnad/4175202
countryEnter.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.year); })
.attr("cy", function(d) { return y(d.name); });
But it is didn't work https://plnkr.co/edit/ADuZkJQrq7mjZqDZwrBe
May be someone can help?
When working with a nested selection, your data call can return a part of the data. In this case the values array:
countryEnter.selectAll("dot")
.data(function(d){
return d.values; //<-- return just the values of your larger data-binding
})
.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.year); })
.attr("cy", function(d) { return y(d.stat); });
Updated code.

d3 Multiline Graph Update

I have a multiline graph that displays 10 series of data, I am trying to get the lines to update with new data but for some reason I can't get that happening.
The transition with the new data is working for the points on the lines so I assume I am not selecting the right elements but for the life of me I can't figure out where my mistake is.
At one point I had one line changing which indicated it was only updating from the first index of the data array.
Any insight would be appreciated:
Initial Series creation-
var series = svg.selectAll(".series")
.data(seriesData)
.enter().append("g")
.attr("class", "series");
series.append("path")
.attr("id", function (d) {
return d.name;
})
.attr("stay", "false")
.attr("class", "line")
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
Update function:
This is where I am stuck, the points respond to the new data but the paths do not.
series.data(newseriesData);
series.selectAll("path")
.attr("id", function (d) {
return d.name;
})
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
series.selectAll(".point")
.data(function (d) {
return d.values;
})
.transition()
.attr("cx", function (d) {
return x(d.label) + x.rangeBand() / 2;
})
.attr("cy", function (d) {
return y(d.value);
})
.style("fill", function (d) {
return color(d.name);
})
.style("stroke", "grey")
.style("stroke-width", "2px")
.on("mouseover", function (d) {
showPopover.call(this, d);
})
.on("mouseout", function (d) {
removePopovers();
})
Yes this is a university project, this is the last piece of work in a solid 50+ hour effort on this and I'd just like to get it knocked out.
The short answer is that instead of series.selectAll("path") you should use series.select("path"). Remember that series is already a selection, and the subselection is done for each element in it. You've appended exactly one element to each of the selection, so .select() is fine and no .selectAll() is required.
The main difference this makes is that .select() inherits the data from the parent selection, while .selectAll() doesn't -- when doing .selectAll() the data is simply not updated and therefore no change occurs.

D3 Get Variable Value

This is a very basic question, but how do I access the value of attributes in d3?
I just started learning today, so I haven't figured this out yet
Suppose I have this as part of my code here
http://jsfiddle.net/matthewpiatetsky/nCNyE/9/
var node = svg.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function (d) {
if (width < height){
return d.count * width/100;
} else {
return d.count * height/100;
}
})
.on("mouseover", animateFirstStep)
.on("mouseout",animateSecondStep)
.style("fill", function(d,i){return color(i);})
.call(force.drag);
For my animation the circle gets bigger when you mouse over it, and I want the circle to return to its normal size when you move the mouse away. However, i'm not sure how to get the value of the radius.
i set the value here
.attr("r", function (d) {
if (width < height){
return d.count * width/100;
} else {
return d.count * height/100;
}
I tried to do node.r and things like that, but i'm not sure what the correct syntax is
Thanks!
You can access an attribute of a selection with:
var node = svg.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function (d) { return rScale(d.count); })
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(1000)
.attr('r', 1.8 * rScale(d.count));
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(1000)
.attr('r', rScale(d.count));
})
.style("fill", function (d, i) {
return color(i);
})
.call(force.drag);
in this context, this points to the DOM element binded with d. Normally, the area of a circle must be proportional to the quantities that you are showing, take a look at the documentation of Quantitative Scales. A fork of your fiddle is here.

Categories

Resources