d3.js adding circle focus points to multi-line series - arrays

After 6 long hours, I managed to add just a couple of more lines to my example, following my previous post (D3 tooltip show values from nested dataset) concerning the use of tooltips.
Now I am stuck at a different point - I can't make circle points that snap to the line points. Other users have already pointed me to a few directions (thanks #Mark), but still haven't been able to combine everything and make it work as I want it.
I have created one circle for each line with its corresponding line color. When hovering over with the mouse, a tooltip with all the lines' values appears and the circles must be positioned on the lines on the x and y axis.
My problem lies in the following piece of code, located inside the mousemove function, in line #106 of this fiddle edit: updated fiddle (https://jsfiddle.net/2en21Lqh/13/):
d3.selectAll(parent + ' .d3-focuspoint')
.classed("hidden", false)
.attr("cx", xz(lastDate))
.attr("cy", function(c) {
return d3.map(c.values, function(d,i) {
if (lastDate == d.date) {
console.log(d.value);
return d.value;
}
})
});
The circles are already bound to the existing data (two days ago I wouldn't have figured this on my own! - at least I am sligthly improving! :/ ), so I am trying to apply the correct cy value but can't figure out the way to do it. The function inside cy returns an object rather than the d.value I am looking for. What am I doing wrong? I've been trying for hours to find any similar examples but can't find any :/
edit: even pointers to the right direction would help :/

Try this:
var mousemoveFunc = function(d, i) {
var x0 = xz.invert(d3.mouse(this)[0]);
var lastDate,
cys = [], //<-- create array to hold y values for circles
ds = []; //<-- create array to hold tooltip text
dataGroup.forEach(function(e) { //<-- loop the data (I removed the map since we now need to return two things)
var i = bisectDate(e.values, x0, 1),
d0 = e.values[i - 1],
d1 = e.values[i];
var d = x0 - d0.date > d1.date - x0 ? d1 : d0;
lastDate = d.date; //<-- hold onto the date (same for all xs)
cys.push(d.value); //<-- hold onto the y value for all circles
ds.push(e.key + " " + d.value); //<-- make the tooltip line
});
var mouse = d3.mouse(svg.node()).map(function(d) {
return parseInt(d);
});
var left = Math.min(containerwidth, mouse[0]+margin.left+margin.right),
top = Math.min(containerheight, mouse[1]+margin.top+margin.right);
d3.selectAll(parent + ' .d3-focuspoint')
.classed("hidden", false)
.attr("cx", xz(lastDate)) //<-- set x position
.attr("cy", function(d,i) {
return yz(cys[i]); //<-- loop the 3 circles and set y position
});
tooltip
.html(lastDate.toString() + "<br/>" + ds.join("<br/>"))
.classed('hidden', false)
.style('left', left + 'px')
.style('top', top + 'px');
};
Updated fiddle.

Related

Accessing multi-dimensional D3 nest() rollup keys and values

I'm having a problem accessing data that I've nested in D3. I believe I've nested and rolled up the data correctly but subsequently I don't seem to be able to access the new key/value pairs I've created.
The original data is a JSON blob with each test, its date of completion, subject area and score. What I want to acheive is to plot a separate set of average score datapoints on a chart by day for each subject. I've used nest previously to get an overall daily score and have plotted this successfully with the scales referenced in the code below but a two level heirarchy is getting the better of me. When I log the new 'nestedData' object it looks correct to me (i.e. the expected values are all there and grouped as I would expect).
I've worked on a few different versions of this with no success. I'm wondering if I'm trying to do something nest isn't designed for or if it's simply that my array notation is wrong (quite possible!). The code below shows the basic framework I'm using. What I'm missing is a filter to select the appropriate subject and the correct way to then access the average daily score from the rollup.
// Takes original data and nest by date and then subject and rolls up on count of work and mean score
var dataToNest = data;
nestedData = d3.nest()
.key(function(el) {return el.dateCompleted})
.key(function(el) {return el.subject})
.rollup(function(leaves) {
return {"numberCompleted": leaves.length,
"averageScore": d3.mean(leaves, function(d) {return(d.score)})}
})
.entries(dataToNest);
// Format date as JS object
nestedData.forEach(function (el) {
el.key = new Date(el.key);
});
// Sort by date
nestedData.sort(function (a,b) {
return a.key - b.key;
});
// Code for scales etc not included
d3.select("svg")
.selectAll("circle")
.data(nestedData)
.filter(/* Filter by subject */)
.enter()
.append("circle")
.attr("class", "subject")
.attr("r", 5)
.attr("cy", function (d) {/* Get the average score for the selected subject */})
.attr("cx", function(d) {return xScale(d.key)});
Some things I've tried:
For the filter I've attempted to select the value of the second key (the subject key) using various iterations of d.values.key === 'Algebra'
For the average score I've tried accessing using iterations of d.values.values.averageScore as well as a function that iterated through the index of the second array.
My strong suspicion is that this is a problem with my understanding of how arrays are structured and referenced in javascript. I've read all the related posts on this but they seem to be mostly about using nest() rather than accessing the values from within it.
UPDATE
Got my foot in the door with the following:
svg.selectAll('.circle-group')
.data(nestedSubjectData)
.enter().append('g')
.attr('class', 'circle-group')
.attr('transform', function(d, i) {
return 'translate(' + xScale(d.key) + ',0)';
})
.selectAll('circle')
.data(function(d) {return d.values;})
.enter().append('circle')
.attr('class', function(d) {return d.key;})
.attr('cx', 0)
.attr('cy', function(d) { return d.values.averageScore; })
.attr('r', 5);
This adds a circle for the averageScore datapoint in each of the arrays at the second level and applies a class that can be used to differentiate the subjects.
Thanks to Lars and a few related questions on SO I got to the bottom of this. The code is below. Having tried this out a few different ways I changed the nesting order to something more logical. Since date and score are used to set x/y coordinates it was easier to keep them at one level and to set the subject as the first level. This makes it easier to split out each series for styling and other series-level interaction (like just switching an entire subject on or off in the visualisation).
My chart now shows a point for each daily average score within each subject with a line between them. You can remove one or the other and it will still work fine.
I haven't included the data but it's a JSON doc. Similarly I haven't included all my scales or the svg creation.
// Nest data by subject and day and get average overall
var subjectDataToNest = data;
nestedSubjectData = d3.nest()
.key(function(el) {return el.subject}) // Nest by subject first
.key(function(el) {return el.dateAppeared}) // Within each subject array create an array for each day
.sortKeys(d3.ascending) // Sort the daily arrays by date (but this doesn't work reliably)
.rollup(function(leaves) {
return {
"averageScore" : d3.mean(leaves, function(d) {return(d.score)}) // Return the average score
}
})
.entries(subjectDataToNest);
// Draw circles for each subject and each
svg.selectAll('.subject-group')
.data(nestedSubjectData)
.enter().append('g')
// Create a group to contain each circle and (eventually) the path
.attr('class', 'subject-group')
.attr("id", function(d) {return d.key;})
// Change the selection
.selectAll('circle')
// Change the data to return index of nested array
.data(function(d) {return d.values;})
.enter().append('circle')
// Convert string date (which is the key in the nested array) to object and apply scale
.attr('cx', function(d) {return xScale(new Date(d.key));})
.attr('cy', function(d) { return yScale(d.values.averageScore); })
.attr('r', 5);
//Draw line
// 1. d3.svg.line() line generator takes values and retuns x/y c-oords for each datapoint
var subjectPath = d3.svg.line()
.x(function(d) {
return xScale(new Date(d.key));})
.y(function(d) {
return yScale(d.values.averageScore);
})
// 2. Select the subject group and append a path to each
svg.selectAll(".subject-group")
.data(nestedSubjectData)
.append('path')
.attr('class', 'subject-line')
.attr("d", subjectPath)
// Pass the second level of the nested array to subjectPath to generate x/y co-ords for the
.attr("d",function(d) {return subjectPath(d.values);});
Useful Reading:
Nested Selections
jshanley's nested selection JS Bin - Relates to SO Question Accessing Nested Array in a D3 variable
Phoebe Bright's canonical D3 nest examples

D3.JS- Format tick values with an ordinal scale axis

I'm using an ordinal scale in my axis:
var xBarScale = d3.scale.ordinal()
.rangeRoundBands([0, width], .1)
.domain(d3.range(data.length));
var xBarAxis = d3.svg.axis()
.scale(xBarScale)
.orient("bottom");
My tick labels are in my data array. What is the best way to format my tick values (using a D3 method) than simply listing each one in the array as I've done here:
xBarAxis.tickValues([data[0].q,data[1].q,data[2].q,data[3].q,data[4].q]);
I'm using D3 v4 and this is how I would do it:
var xBarScale = d3.scale.ordinal()
.rangeRoundBands([0, width], .1)
.domain(d3.range(data.length));
var xBarAxis = d3.axisBottom( xBarScale )
.tickFormat((d)=> d + ' position' ) // just an example of formatting each Tick's text
.tickValues( data.map(d => d.q) ) // creates an Array of `q` values
As to my example, you can set the tickFormat to format your text labels however you wish

How to work with nested hard bracket in d3.js?

I'm a beginner in d3.js and I need help printing out the data from the arrays of arrays.
Every time I try to print out the data inside the nested hard bracket, the text doesn't show up in browser. I feel frustrated because I'm pretty this is easily fixable but I just can't figure it out. Then I figured I don't have the foundation for the d3.js yet. So far I got this:
//Width and height
var w = 500;
var h = 120;
var barPadding = 1;
var dataset =[
[5,23]
[10,23]
];
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[0]; })])
.range([0, w]);
//Create SVG element
d3.select("body").selectAll("p")
.data(dataset)
.enter()
.append("p")
.text(function(d) { return d; });
Also, if you were able to figure this out, then does your method apply for nested brackets that hold more than 2 digits?
You will be able to print as is if you fix the dataset declaration:
var dataset =[[5,23],[10,23]];
It needs a comma between array entries.

KineticJS - update drawFunc after an object on another stage has been moved

With the help of a fellow stackoverflow user, I am able to change the position of a two lines and a circle on the stage using the following:
var circle2 = new Kinetic.Circle({
drawFunc: function(canvas) {
var context2 = canvas.getContext();
var centerX2 = blueLine2.getPosition().x;
centerY2 = greenLine2.getPosition().y;
context2.drawImage(gArrow2, -156, -23 + centerY2, 11, 23);
context2.drawImage(gArrow2, 156, -23 + centerY2, 11, 23);
context2.drawImage(bArrow2, centerX2, 156, 23, 11);
context2.drawImage(bArrow2, centerX2, -156, 23, 11);
context2.beginPath();
context2.arc(centerX2, centerY2, this.getRadius(), 0, 2 * Math.PI, false);
context2.lineWidth = this.getStrokeWidth();
context2.strokeStyle = this.getStroke();
context2.stroke();
},
x: cx + gx,
y: cy + gy,
radius: 70,
stroke: '#00ffff',
strokeWidth: 3,
opacity: 0.5
});
layer2.add(circle2);
It works great, now my challenge is if I move a line on a second stage for example the horizontal line, I can also move the horizonal line on the first stage using:
greenLine2.on('dragend', function (event) {
var y1 = greenLine2.getPosition().y;
greenLine3.setPoints([0, 256 + y1, 512, 256 + y1]);
centerY3 = 256 + y1;
layer3.draw();
layer2.draw();
});
However I can't update the layer to move also the vertical line and the circle. I would appreciate your suggestions, thanks in advance.
Lets say that greenLine2 is the one you're moving, and you want greenLine3 to move to the same position on the other stage. I'm going to assume the stages are the same size, but you can change up the code to account for these changes.
greenLine2.on('dragmove', function (event) {
var userPos = stage.getUserPosition(); //if this doesnt work the way you need, try a different approach, such as below:
//var userPos = greenLine.getPosition(); //other possibility
greenLine3.setPosition(userPos);
layer3.draw();
layer2.draw();
});
and if you want other things to move as well, you can do the same kind of code using .setPosition() with some offset so that the drawing is relative.
Another approach would be to create a group in each stage, and make the group draggable, that way, you can drag all the items in a group at the same time, and synchronously across stages.

VirtualEarth: determine min/max visible latitude/longitude

Is there an easy way to determine the maximum and minimum visible latitude and longitude in a VirtualEarth map?
Given that it's not a flat surface (VE uses Mercator projection it looks like) I can see the math getting fairly complicated, I figured somebody may know of a snippet to accomplish this.
Found it! VEMap.GetMapView() returns the bounding rectangle, even works for 3D mode as well (where the boundary is not even a rectangle).
var view = map.GetMapView();
latMin = view.BottomRightLatLong.Latitude;
lonMin = view.TopLeftLatLong.Longitude;
latMax = view.TopLeftLatLong.Latitude;
lonMax = view.BottomRightLatLong.Longitude;
Using the Virtual Earth Interactive SDK, you can see how to convert a pixel point to a LatLong object:
function GetMap()
{
map = new VEMap('myMap');
map.LoadMap();
}
function DoPixelToLL(x, y)
{
var ll = map.PixelToLatLong(new VEPixel(x, y)).toString()
alert("The latitude,longitude of the pixel at (" + x + "," + y + ") is: " + ll)
}
Take a further look here: http://dev.live.com/virtualearth/sdk/ in the menu go to: Get map info --> Convert pixel to LatLong
To get the Max/Min visible LatLong's, you could call the DoPixelToLL method for each corner of the map.

Resources