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.
Related
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.
So I am attempting to draw a simple line graph. The path to the arrays are fairly long, It seems the forEach function is not looping through the array? Am I missing a function to successfully create d.date which comes out as 'null' and d.air which comes out as 'NaN'?
data[0].STATION[0].OBSERVATIONS.date_time, and data[0].STATION[0].OBSERVATIONS.air_temp_set_1 are the arrays I want to plot
Here it is on blockbuilder:
http://blockbuilder.org/TMoore24/b9906fd3893034b91f13e0c86acb2f75
Many thanks!
There are two things going on here.
(1):
data.forEach(function(d) {
d.date = format(data[0].STATION[0].OBSERVATIONS.date_time);
d.air = +data[0].STATION[0].OBSERVATIONS.air_temp_set_1;
});
These don't work because both data[0].STATION[0].OBSERVATIONS.date_time, and data[0].STATION[0].OBSERVATIONS.air_temp_set_1 are arrays.
What you probably want is this.
var obs = data[0].STATION[0].OBSERVATIONS;
var dates = obs.date_time.map(format);
var air = obs.air_temp_set_1.map(function(d){ return +d; });
data.date = dates;
data.air = air;
What this leaves you with is an array of dates and and array of air temps.
Unfortunately, this leads to issue #2.
(2)
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.air); });
This function assumes that each object, d, takes the form {date: SINGLE_DATE_VALUE, air: SINGLE_NUMERIC_VALUE, etc} but what you actually have is data = {date:[ARRAY_OF_DATES, air: ARRAY_OF_TEMPERATURES, etc...}
To use the line function above, then you need to create an array of objects of the form {date: DATE_VALUE, air: AIR_TEMPERATURE_VALUE}. There are various ways you can do that depending on how you want your final data structured. Here's one way:
data.airTempByDate = data.date.map(function(d, i){
return {
date: d,
air: air[i]
};
});
Hope this makes sense! Here's a working block on blockbuilder with the fixes above.
Hello I apologize in advance for my question which I'm sure is pretty basic.
On a map are set 33 landmarks with an array calling a class in the library.
A second array defines the coordinates of those landmarks.
for (var i:uint = 0; i < 33; i++) {
mark[i] = new landMark();
landMarks.addChild(mark[i]);
mark[i].x = lmxy[i]['x'];
mark[i].y = lmxy[i]['y'];
}
var lmxy:Array = [{x:1620,y:880},{x:1850, y:1050},etc...];
So far so good, the landmarks show each in its right place.
The third array contains different legends supposed to show when a landmark is clicked.
So the landmark [1] should show the legend [1] and the landmark [31] the legend [31]
var lgd:Array = [lgdA, lgdB, etc... ];
var legends:MovieClip;
for (var j:uint=0;j<lgd.length;j++) {
legends = new lgd[j]();
legends.x = 300;legends.y = 170;
}
Edit cause obviously that was unclear :
I tried that in the loop to link the marks to the legends but I get an error :
mark[i].addEventListener(MouseEvent.CLICK, getLgd);
function getLgd(e:Event):void {stage.addChild (lgd[i]);}
Any help would be very welcome !
The problem is that the variable i doesn't have a definition. The only way for you to find out which of the landmarks were clicked is to find its index in the array, then you can add the legend that has the same index. Because the index isn't passed from the event listener, you need to use e which has the target property.
This should do the trick:
mark[i].addEventListener(MouseEvent.CLICK, getLgd);
function getLgd(e:Event):void
{
var i:int = mark.indexOf(e.target);
stage.addChild(lgd[i]);
}
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
I found a snowing image script on the web (http://www.jqueryrain.com/?GKBtfF4Q) and I am trying to modify the script to have multiple images.
So far, I figured out that it's necessary to change:
$(document).snow({ SnowImage: "snow.gif" });
to
$(document).snow({ SnowImage:[ "snow.gif", "2.gif", "3.gif", "4.gif"] });
but I am not sure how to make the script accept an array. Any suggestions would be much appreciated! (full disclosure: I am a coding noob)
Edit: pulled the script into JSFiddle: http://jsfiddle.net/px6w1xdm/
function __ShowSnow(settings)
{
var snowsrc = settings.SnowImage;
var no = settings.Quantity;
var dx, xp, yp; // coordinate and position variables
var am, stx, sty; // amplitude and step variables
var i;
etc.
Add this in the loop (above var flake):
var randno = Math.floor(snowsrc.length*Math.random());
var randsnowsrc = snowsrc[randno];
Then change snowsrc to randsnowsrc in this line:
flake.append("<img src='" + randsnowsrc + "'>");