D3 X-value mouseover - function returns NaN - javascript

So I am trying to adapt M Bostock's x-value mouseover example to my own graph, the main difference being that I have multiple series instead of his one. For the moment I'm just trying to get the circles to work. My problem is that when I mouseover the graph (in Firebug) I get the message: Unexpected value translate(<my_x>, NaN) parsing transform attribute. I've tried several different ways to fix it but I get the same response each time. What am I doing wrong?
I have a jsFiddle, and the issue is at the bottom:
var focus = main.append('g')
.attr('class', 'focus')
.style('display', 'none');
var circles = focus.selectAll('circle')
.data(sets) // sets = [{name: ..., values:[{date:..., value:...}, ]}, ]
.enter()
.append('circle')
.attr('class', 'circle')
.attr('r', 4)
.attr('stroke', function (d) {return colour(d.name);});
main.append('rect')
.attr('class', 'overlay')
.attr('width', w)
.attr('height', h)
.on('mouseover', function () {focus.style('dispaly', null);})
.on('mouseout', function () {focus.style('display', 'none');})
.on('mousemove', mousemove);
function mousemove() {
var x0 = x_main.invert(d3.mouse(this)[0]),
i = bisectDate(dataset, x0, 1),
d0 = dataset[i - 1].date,
d1 = dataset[i].date,
c = x0 - d0 > d1 - x0 ? [d1, i] : [d0, i - 1];
circles.attr('transform', 'translate(' +
x_main(c[0]) + ',' +
y_main(function (d) {return d.values[c[1]].value;}) + ')'
);
== EDIT ==
Working jsFiddle

You're passing in a function definition into your y_main scale:
circles.attr('transform', 'translate(' +
x_main(c[0]) + ',' +
y_main(function (d) {return d.values[c[1]].value;}) + ')'
);
selection.attr can take a string value or a callback function but this is trying mixing both of those. You're passing in a string and as the string is constructed it tries to scale the function itself as a value, which will return NaN.
The function version should look like this (returning the entire transform value):
circles.attr('transform', function(d) {
return 'translate(' +
x_main(c[0]) + ',' +
y_main(d.values[c[1]].value) + ')';
});

Related

Hover on data point returns undefined in D3.js line chart

The problem lies here- d[0] and d[1] return undefined, but I don't know how to access the data.
I've tried d.Wavelength (name of column at d[0]) but that doesn't work. In the example (link below) d.value is used, but I need to access values at d[0] and d[1].
function m_over(data){
ttip.datum(data)
.style("visibility","visible")
.text(function(d){
return ("Wavelength: " + d[0] + ", Sample_1_Absorbance: " + d[1])
})
}
function m_out(data){
ttip.style("visibility","hidden");
}
function m_move(data){
ttip.style("top", (event.pageY - 10) + "px").style("left", (event.pageX + 10) + "px");
}
svg.selectAll('circle_samp_1')
.data(data_in_range)
.enter()
.append('circle')
.attr('cx', (d) => xScale(d[0]))
.attr('cy', (d) => yScale(d[1]))
.attr('r', 7)
.attr('fill', 'rgba(0,0,0,0.1)')
.attr('stroke', 'rgba(0,0,0,0.9)')
.attr('stroke-width', 1)
.attr('class', 'points')
.style('pointer-events', 'all')
.on("mouseover",function (d){
return m_over(d)
})
.on("mousemove", function (d) {
return m_move(d)
})
.on("mouseout", function (d) {
return m_out(d)
});
I'm trying to recreate this example. This is a similar issue to another example I've tried to recreate, where the issue lies here. In this example, the code is d.date, so I tried adapting it as below (last line), and with d0[0] but that doesn't work.
function mousemove() {
var x0 = xScale.invert(d3.pointer(event, this)[0]),
i = d3.bisect(data, x0, 1),
d0 = xScale(data[i - 1]),
d1 = xScale(data[i]),
d = x0 - d0.Wavelength > d1.Wavelength - x0 ? d1 : d0;
Here is a screenshot of my graph:
If you would like to see my data, or the rest of the code, let me know and I'll add it in. Thank you in advance.
I think you need to adjust your event listeners.
As an example, in the "mouseover" function, the first argument is the mouse event, and there is a second argument which is the data attached to the element that dispatched that event...
.on("mouseover",(event, d)=>{
//check what we're passing to the m_over function
console.log('data:', d);
m_over(d);
})
Here's a really simple example illustrating the concept
https://codepen.io/tomp/pen/wveRNmV

Animating circles along multiple paths

I am trying to create an animation where circles are being animated on multiple paths.
I am able to get the animation I want for one of the paths but am not sure why the circles are only animating on that particular path, instead of being distributed according to the path they belong.
The full code can be found on my bl.ocks page: https://bl.ocks.org/JulienAssouline/4a11b54fc68c3255a85b31f34e171649
This is the main part of it
var path = svg.selectAll("path")
.data(data.filter(function(d){
return d.From > 2010
}))
.enter()
.append("path")
.style("stroke", "#832129")
.attr("class", "arc")
.attr("d", function(d){
var To_scale = xScale(d.experience),
From_scale = xScale(0),
y = yScale(0),
dx = To_scale - From_scale,
dy = y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + From_scale + " " + y + " A 43 50 0 0 1 " + To_scale + " " + y;
})
.style("fill", "none")
.style("opacity", 0)
.call(transition)
.on("mouseover", function(d){
var thisClass = d3.select(this).attr("class")
d3.selectAll(".path").style("opacity", 0.1)
d3.select(this).style("stroke", "white").style("opacity", 1).style("stroke-width", 2)
})
.on("mouseout", function(d){
d3.select(this).style("stroke", "#832129").style("opacity", 1)
})
function transition(path){
path.each(function(PathItem, index){
d3.select(this).transition()
// .delay(index + 200)
.duration(index * 5 + 1000)
.on("start", function(){
d3.select(this).style("opacity", 1)
})
.attrTween("stroke-dasharray", tweenDash)
})
}
function tweenDash(){
var l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l)
return function(t){ return i(t); };
}
console.log(data[0])
var circle = svg.selectAll("circle")
.data(data.filter(function(d){
return d.From > 2010
}))
.enter()
.append("circle")
.attr("r", 5)
.attr("cx", function(d){
return xScale(d.experience)
})
.style("fill", "red")
.attr("transform", "translate(" + 0 + ")")
.style("opacity", 0)
transition_circles();
function transition_circles(){
circle.each(function(pathItem, index){
d3.select(this)
.transition()
.delay(index * 200)
.duration(index * 10 + 1000)
.on("start", function(){
d3.select(this).style("opacity", 1)
})
.on("end",function(){
d3.select(this).style("opacity", 0)
})
.attrTween("transform", translateAlong(path.node(), index))
})
}
function translateAlong(path, index){
var l = path.getTotalLength();
return function(d, i , a){
return function(t){
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
}
}
}
Basically, I followed this https://bl.ocks.org/mbostock/1705868 example to get the point-along-path interpolation, but am having trouble adapting it to get the same effect on multiple lines.
I also tried adding .attr("cx", function(d){ return d.experience} to the circles but that didn't work.
You're always passing the same path (the first one) to the translateAlong function:
.attrTween("transform", translateAlong(path.node(), index))
//this is always the first path ---------^
You have to pass different paths to the translateAlong function. There are different ways for doing that (I don't know which one you want), one of those is:
.attrTween("transform", translateAlong(path.nodes()[index], index))
In this approach, the indices of the circles go from 0 to the data array length minus 1. So, since path.nodes() is an array of elements, it's selecting different ones by their indices.
Here is the updated bl.ocks: https://bl.ocks.org/anonymous/f54345ed04e1a66b7cff3ebeef271428/76fc9fbaeed5dfa867fdd57b24c6451346852568
PS: regarding optimisation, you don't need to draw several paths at the same position! Right now you have dozens of paths which are exactly the same. Just draw the different paths (in your case, only 3).

Use getCtm() at end of d3 translate event

I have the following function that translates a set of circle objects along a predefined SVG path. Per this post, I am attempting to use the getCTM() function to capture the new position of each circle element after each transition runs on each of the respective elements. However, when the below code is executed, it isn't returning the updated transition after each transform. When I look at the matrix values that the getCTM() function is returning for each element, they are:
SVGMatrix {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
Each circle moves along the SVG path without a hitch, but I can't figure out why the transform values aren't being returned in the SVGMatrix using the code below. Here is a sample of the data being bound to each circle:
trip_headsign
:
"Ashmont"
trip_id
:
"31562570"
trip_name
:
"11:05 pm from Alewife to Ashmont - Outbound"
vehicle_lat
:
42.33035301964327
vehicle_lon
:
-71.0570772306528
stops
:
Array[5]
0
:
Array[6]
0
:
"130"
1
:
"70085"
2
:
124
3
:
Array1
4
:
124
5
:
0
var map = L.map('map').setView([42.365, -71.10], 12),
svg = d3.select(map.getPanes().overlayPane).append("svg"),
ashmontG = svg.append("g").attr("class", "leaflet-zoom-hide"),
inboundG = svg.append("g").attr("class", "leaflet-zoom-hide");
var transform = d3.geo.transform({point: projectPoint}),
path = d3.geo.path().projection(transform);
var track = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return applyLatLngToLayer(d).x
})
.y(function(d) {
return applyLatLngToLayer(d).y
});
var ashmontPath = ashmontG.selectAll("path")
.data([ashmont.features])
.enter()
.append("path")
.style("fill", "none")
.style("stroke", "black")
.style("stroke-width", 2)
.style("opacity", 0.1)
.attr("d", track)
var trains = inboundG.selectAll("circle")
.data(a_live_trains)
.enter()
.append("circle")
.attr("r", 6)
.style("fill", "blue")
.attr("class", "train");
d3.selectAll(".train").each(function(d) {
//the convertCoords function takes a lat/lng pair bound to the circle element and returns the coordinates in pixels using the leaflet latlngtolayerpoint function
var x = convertCoords(d).x,
y = convertCoords(d).y;
console.log(x, y);
for(j=0; j<d.stops.length; j++){
var matrix, xn, xy;
d3.select(this).transition()
.duration(d.stops[j][4]*50)
.delay(d.stops[j][5]*50)
.attrTween("transform", pathMove(d.stops[j][3]))
.each("end", ctm(this))
function ctm(x) {
console.log(x);
matrix = x.getCTM();
xn = matrix.e + x*matrix.a + y*matrix.c,
yn = matrix.f + x*matrix.b + y*matrix.d;
console.log(xn, yn)
}
}
})
function pathMove(path) {
return function (d, i, a) {
return function(t) {
var length = path.node().getTotalLength();
var p = path.node().getPointAtLength(t*length);
//var ptoPoint = map.layerPointToLatLng(new L.Point(p.x, p.y
return "translate(" + p.x + "," + p.y + ")";
}
}
}
moveTrains();
map.on("viewreset", reset);
reset();
function reset() {
svg.attr("width", bottomRight[0] - topLeft[0] + padding)
.attr("height", bottomRight[1] - topLeft[1] + padding)
.style("left", (topLeft[0]-(padding/2)) + "px")
.style("top", (topLeft[1]-(padding/2)) + "px");
ashmontG.attr("transform", "translate(" + (-topLeft[0] + (padding/2)) + ","
+ (-topLeft[1] + (padding/2)) + ")");
inboundG.attr("transform", "translate(" + (-topLeft[0] + (padding/2)) + ","
+ (-topLeft[1] + (padding/2)) + ")");
ashmontPath.attr("d", track);}
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x))
//var point = latLngToPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y)
}
Writing the code as shown immediately below returns the pre-transformation svg matrix. I believe this is happening because the 'this' keyword for each circle object was being selected pre-transform, and was passing the pre-transform SVG position into the ctm function. Additionally, the ctm function was executing before the pathMove function was even being called.
d3.select(this).transition()
.duration(d.stops[j][4]*50)
.delay(d.stops[j][5]*50)
.attrTween("transform", pathMove(d.stops[j][3]))
.each("end", ctm(this))
function ctm(x) {
console.log(x);
matrix = x.getCTM();
Slightly modifying the code using the example Mark provided above executes properly. I believe this is due to the fact that the 'this' keyword is being called a second time post-transform within the ctm function. Writing it this way grabs the SVGmatrix each time after the pathMove function has been called.
d3.select(this).transition()
.duration(d.stops[j][4]*50)
.delay(d.stops[j][5]*50)
.attrTween("transform", pathMove(d.stops[j][3]))
.each("end", ctm);
function ctm(x) {
console.log(this);
matrix = this.getCTM();

Donut Pie Chart - Add a Title - NVd3.js

I'm exploring the NVD3.js library. It amazes me how quickly things can be produced in it.
But it seems like it's difficult to alter the chart sometimes. In this case, I would like to add a title to my chart.
The other thing, I would like to add additional data in the tool-tip. So on hover, It would also include the note in my data.
Data sample:
var data = [
{
key: "Loans",
y: 52.24
note: "Expect greatest value"
}];
This is the code I'm playing with:
nv.addGraph(function() {
var width = 500,
height = 500;
var chart = nv.models.pieChart()
.x(function(d) { return d.key; })
.values(function(d) { return d; })
.color(d3.scale.category10().range())
.width(width)
.height(height)
.donut(true);
chart.pie
.startAngle(function(d) { return d.startAngle/2 -Math.PI/2; })
.endAngle(function(d) { return d.endAngle/2 -Math.PI/2 ;});
d3.select("#chart")
//.datum(historicalBarChart)
.datum([data])
.transition().duration(1200)
.attr('width', width)
.attr('height', height)
.call(chart);
return chart;
});
Update: The original code for the tooltip is located within src->models->pieChart.js:
tooltip = function(key, y, e, graph) {
return '<h3>' + key + '</h3>' +
'<p> Confidence: ' + y + '%</p>'
}
I've tried overriding this with my own function. But either get errors or no change.
Title Update: I typically use the following code (or something similar) for titles.
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
.text("Awesome Title");
But of course, this isn't valid in NVD3. I'm not aware of what function is used to specify a title.
I think your looking for chart.tooltipContent() JSFiddle: http://jsbin.com/idosop/7/edit
var tp = function(key, y, e, graph) {
console.log(key, e, y);
return '<h3>' + key + '</h3>' +
'<p>!!' + y + '!!</p>' +
'<p>Likes: ' + e.point.likes + '</p>';
};
chart.tooltipContent(tp);

How can I efficiently convert data from string to int within a d3 method chain?

I'm making an interactive bar chart in d3, and have come across a problem. The bar chart reads data from a form, but it reads the data as a string. When I am using the data to draw bars like this:
var bars = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d,i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
})
.attr("fill", function(d) {
return "rgb(" + (d * redLevel) + ", 0, " + (d * blueLevel) + ")";
});
the data is read as a string. I could use parseInt(d) every time I wanted to use d, but that would be grossly inefficient. It would be easy to do var d = parseInt(d) outside of the method chain, but that wouldn't work with d3. Suggestions?
You could map the data before you bind it:
.data(dataset.map(function(d) { return +d; }))
Then unary + operator converts a numeric string into a number.

Categories

Resources