Two tooltip points on one path in d3.js line chart - html

I've made this simple line chart in d3, but as well as showing the tooltip data for mouseover, I would also like to display the data for where the blue dotted line of the tooltip intersects with the data path in the first instance.
For intsance at the link below, if the mouse hovers at 2012, data for 2005 would show at the first intersection between the tooltip line and the data path.
http://bl.ocks.org/anonymous/49f04076adbec7e2c2f9
Any ideas? Thanks

In the linked example, you would want to search for months with CPI intervals that contain the CPI value that is currently displayed. So, create a list of such intervals, with references to the month that contains them, then find matching intervals on hover.
Something like this, for example (untested):
// after loading data
var cpi_intervals = [];
data.forEach(function(d, i) {
if (i > 0) {
cpi_intervals.push({
cpis: d3.extent([data[i-1].cpi, d.cpi]),
date: d.date
});
}
});
...
// function to find months containing specified cpi
function monthsContainingCPI(cpi) {
return cpi_intervals.filter(function(d) {
return cpi >= d.cpis[0] && cpi < d.cpis[1];
}).map(function(d) {return d.date;})
}
If needed, you can improve the performance of the monthsContainingCPI function by using a more complex data structure, like an interval tree, to store and access the CPI intervals.

Related

In Google Earth Engine: Most efficiently reduceRegions over each image in ImageCollection, saving mean as a Feature property?

I have a FeatureCollection made up of many (100-200) polygons ('ftr_polygons'). I also have an ImageCollection made up of monthly median Landsat8 bands and indices ('byMonth'). I want to ReduceRegions and save a median (or mean) spatial average from each polygon in the FeatureCollection. End goal is to export to csv a timeseries of monthly mean bands/indices within each polygons over multiple years (2013-2019).
With the code below, I am able to do this for ~1 year, but any more than that, and I get an error: 'FeatureCollection (Error) Computation timed out’. Is there a better way to do this?
// define the function that will grab median (or mean) spatial reductions for each polygon, for each month
var extractdata = function(medianImage,ftr_polygons) {
var date_start = ee.Date(medianImage.get('system:time_start')).format("YYYY-MM"); // get date as string to append to each property
// spatial MEDIAN
ftr_polygons = medianImage.reduceRegions({ // create feature collection with new properties, bands for each month, uniquely named
collection: ftr_polygons,
reducer: ee.Reducer.median(),
scale: 30,
tileScale: 1}); // tile scale
var ftr_polygons_propnames = ftr_polygons.first().propertyNames(); // get property names first
var ftr_polygons_newnames = ftr_polygons_propnames.replace('NDVI_median',
ee.String('NDVI_median_').cat(date_start)); //replace property names with band+date
ftr_polygons_newnames = ftr_polygons_newnames.replace('EVI_median',
ee.String('EVI_median_').cat(date_start)); //replace property names with band+date
ftr_polygons_newnames = ftr_polygons_newnames.replace('NIRv_median',
ee.String('NIRv_median_').cat(date_start)) ; //replace property names with band+date
ftr_polygons = ftr_polygons.map(function(f) {return f.select(ftr_polygons_propnames,ftr_polygons_newnames)});
return ftr_polygons;
};
// apply the function over ImageCollection byMonth, beginning with feature collection ftr_polygons
var ftr_polygons = ee.FeatureCollection(byMonth.iterate(extractdata,ftr_polygons));
// remove geometry on each feature before printing or exporting
var myproperties=function(feature){
feature=ee.Feature(feature).setGeometry(null);
return feature;
};
var ftr_polygon_export = ftr_polygon.map(myproperties)
print(ftr_polygon_export.limit(1), 'For export w monthly properties');
Maybe this answer: https://stackoverflow.com/a/48412324/12393507 alludes to a better way:
The same approach can be used with reduceRegions() as well, mapping over images and then over regions. However, you will have to map over the resulting features to set dates.
I would appreciate more info on this approach.
Thanks.
For computationally intensive operations that will run for a long time you should always export your results instead of visualizing/printing them.
For more info read through this section of the debugging page in the Earth Engine manual.

AngularJS how to sum data from database in the view

I have a table, with one column for each day of the week, in which with a ng-repeat, I put the hours worked on a specific project in that week.
The data gets pulled from a database.
What I'm having trouble with is, each project can have different subprojects, and when this happens, they all get put in a rowspan under the same main project, as you can see in the image ( the "SU" Project has 2 subprojects "ghfgh" and "testtesttest").
Now, the total hours in the last column gets pulled as well from the database, where all the hours of a project in a specific week are summed.
How can I make that IF a project has subprojects, their hours that week get summed in a single rowspan as well? (in the image I put an arrow showing the result wanted)
If I use the same code to give the rowspan to the subprojects, it gives the right rowspan to the total column, but obviously only shows the hours of the first subproject (in this case "ghfgh")
<tr ng-repeat="p in vm.presences">
<td ng-if="p.showProject" rowspan="{{p.projectCount}}">{{p.projectName}}</td>
<td>{{p.description}}</td>
<td>{{p.monday}}</td>
<td>{{p.tuesday}}</td>
<td>{{p.wednesday}}</td>
<td>{{p.thursday}}</td>
<td>{{p.friday}}</td>
<td>{{p.saturday}}</td>
<td>{{p.sunday}}</td>
<td ng-if="p.showProject" rowspan="{{p.projectCount}}">{{p.total}}</td>
</tr>
and the code used to check if there are multiple entries of the same project in a given week, to group them in the same rowspan is
weeklyPresences.forEach((p, i) => {
const differentProject = i === 0 || weeklyPresences[i - 1].projectName !== p.projectName;
p.showProject = differentProject;
if (differentProject) {
p.projectCount = weeklyPresences.filter(pres => pres.projectName === p.projectName).length;
} else {
p.projectCount = weeklyPresences[i - 1].projectCount;
}
});
return weeklyPresences;
any idea on how I could group the hours of the subprojects?
I was thinking about some loop that checks if a project has multiple entries like the code above, if it doesn't, the var total stays the same, otherwise, it takes them and sums them in a new var.
var arr= [1, 2, 3];
var sum = arr.reduce((a, b) => a + b, 0);
console.log(sum); // 6

D3 reusable multi-line chart with JSON data

I'm trying to do some re-factoring on my charts to make them re-usable using this as a guide: http://bost.ocks.org/mike/chart/
I'm having problems drawing the lines in my multi-line graph though - specifically passing the data to the x and y values. If I hard code the element names it works, but if I try to use the xValue and yValue objects this does not work. I'm assuming that this is because I'm trying to call a function within the parameter of an other object, but I'm not sure how to get around this. In the exmaple Mike uses d[0] and d[1], but this won't work with JSON data (or I'm not sure how to make it work).
I've posted this JSFiddle so you can see the code. The problem lines are 125 to 131 which in turn is being called from line 165.
var main_line = d3.svg.line()
.interpolate("cardinal")
// Hard coding the elements works
//.x(function(d) { return main_x(d.date); })
//.y(function(d) { return main_y(d.buildFixTime); });
// Passing xValue and yValue does not work
.x(function(d) { return main_x(xValue); })
.y(function(d) { return main_y(yValue); });
http://jsfiddle.net/goodspeedj/fDyLY/
Thank you in advance.
You need to redefine your accessor method within .x() and .y(). The accessor method defines the way that a datum is pulled out of the data that is bound to the selection that you call the line generator on.
Suppose you have a relatively flat data structure such as the following.
data = [{x : 1, y : 2}, {x:1, y:3}, {x:4, y:5}];
You then bind the data to a selection with the following statement
d3.select("body").datum(data).append("path").attr("d",lineGenerator);
Quite a bit is going on underneath this statement. I'll give you a bit more of a walkthrough after showing you a commonly used example.
The important aspect to understand is that similarly to other calls in d3 such as
var exampleRectangles = d3.select("body")
.data(data).enter()
.append("rect")
.attr("width",2)
.attr("height", 3)
.attr("x",function(datum){return datum.x}) // pay attention to this line
.attr("y",0);
d3 is implicitly iterating over each element in your data. For each datum in your data array, in this case there is a total of three datum, you are going to add a rectangle to the dom.
In the line that I tell you to pay attention to you notice that you're defining an anonymous (unnamed) function. What is that datum parameter coming from? It's implicitly being passed to your anonymous function.
So each rectangle has it's own corresponding datum {x : 1, y : 2}, {x:1, y:3}, {x:4, y:5} respectively. Each rectangle's x coordinate is defined by the respective datum.x attribute. Under the sheets, d3 is implicitly looping over the data array that you've defined. A similar approach to the example d3 code could be written as above.
for (var i = 0; i < data.length; i++)
{
d3.select("body").append("rect")
.attr("width",2)
.attr("height", 3)
.attr("x",data[i].x)
.attr("y",0);
}
This follows from the notion of data driven documents (d3). For each item added (a rectangle in the above example a piece of data is tied to it. In the above example you see that there is something kind of similar to your .x() and .y() accessor functions :
.attr("x",function(datum){return datum.x})
This function is telling d3 how to filter over the total datum that's being passed to the .attr() accessor method.
So, you need to determine which data you need to get a hold of to make your .attr("d", lineGenerator)call make sense. The difference between your.datum(data)call and the typical.data(data)call is that instead of parceling the data that's being passed to.data(data)`, the whole array is given as a single piece of data to the line generator function (similar to main_line(data), wherein it will again implicitly loop over the points to construct your path.
So, what you need to do is determine what a single datum will be defined as for your function to operate on.
I'm not going to define that as I don't seem to know quite which information you are operating on, but I would hazard a guess at something like.
.x(xAccessor)
.y(yAccessor)
function xAccessor(datum)
{
return xScale(datum._id.month);
}
function yAccessor(datum)
{
return yScale(datum.buildFixTime);
}
The way you have it set up, xValue and yValue are functions; you have to actually execute them on something to get a value back.
.x(function(d) { return main_x( xValue(d) ); })
.y(function(d) { return main_y( yValue(d) ); });
If you weren't using a scale, you could use
.x(xValue)
.y(yValue);
but only because if you pass in a function d3 executes it for you with the data as a parameter. And that only works for d3 methods that expect functions as possible input -- the scale functions expect data values as input.
I wrote a long piece work for another user last week that you may find useful, explaining methods that accept functions as parameters.

getting a random element from an array of movieclips(or labels in a timeline) in Flash CC . Actionscript 3

I am making a pretty neat quiz-game in flashCC right now and I definitely need your help.
My skills are more on the design then the programming side. So to many of you this might seem a baby question (and asked many times before) but from all the answers I saw so far, I couldn't get any results for my project.
So here is the thing :
I need the EXACT script for creating an array (with movieclips inside? or instance names of mcs? How does this even work?)
and a method, to pick a random element of this array without repeats until the "game is over".
Paul
The easiest way to pick a random element from an array without repeating is to first sort the array with a "random" function, then pop or shift items out of it until the array is empty.
Let's say you have an array of items which can be filled with either instances or instance names, you've chosen instance names: :
var FirstArray:Array = ["blau", "orange", "green"];
Now, you'll need a random sort function:
// you do not need to modify this function in any way.
// the Array.sort method accepts a function that takes in 2 objects and returns an int
// this function has been written to comply with that
function randomSort(a:Object, b:Object):int
{
return Math.random() > .5 ? -1 : 1;
}
The way a sort function normally works is it compares two objects and returns -1 if the first item precedes the second item, 1 if the opposite is true, and 0 if they are the same.
So what we're doing in the function above is returning -1 or 1 randomly. This should get the array all jumbled up when you call:
FirstArray.sort(randomSort);
Now that the array is randomly sorted, you can begin pulling items from it like so:
if(FirstArray.length) // make sure there's at least one item in there
{
// since you are using instance names, you'll need to use that to grab a reference to the actual instance:
var currentQuizItem:MovieClip = this[FirstArray.pop()];
// if you had filled your array with the actual instances instead, you would just be assigning FirstArray.pop() to currentQuizItem
// every time you call pop on an array, you're removing the last item
// this will ensure that you won't repeat any items
// do what you need to do with your MovieClip here
}
else
{
// if there aren't any items left, the game is over
}
When strung together, the above code should be enough to get you up and running.
You could try something like:
var array:Array = [1, 2, 3, 4, 5];
var shuffledArray:Array = [];
while (array.length > 0)
{
shuffledArray.push(array.splice(Math.round(Math.random() * (array.length - 1)), 1)[0]);
}
trace('shuffledArray: ', shuffledArray, '\nrandom item: ', shuffledArray[0]);

error in reading csv file to plot graph

i am just not able to read a csv file .I want to display a graph for it.
I am getting error:
TypeError: n is undefined
please help me out!!
d3.csv("example.csv", function(dataset){
var svg=d3.select("body").append("svg").attr("width",w).attr("height",h);
var xScale=d3.scale.ordinal().domain(d3.range(dataset.length)).rangeRoundBands([0,w],0.05);
var yScale=d3.scale.linear().domain([0,d3.max(dataset.value)]).range([0,h]);
svg.selectAll("rect").data(dataset).enter().append("rect").attr({x:function(d,i) {return xScale(i);}, y:function(d){
return h-yScale(d);}, width:xScale.rangeBand(),height:function(d){return yScale(d);},fill:function(d){return "rgb(0,0,"+(d.value*10)+")";}});
d3.select("svg").selectAll("text").data(dataset).enter().append("text").text(function(d) {return d.value;}).attr("x",function(d,i){
return xScale(i)+xScale.rangeBand()/2;}).attr("y",function(d){return h-yScale(d)+14;}).attr("font-family","sans-serif").attr("font-size","10px").attr
("fill","white").attr("text-anchor","middle");
d3.select("p").on("click",function(){
var numValues=dataset.length;
dataset=[];
for(var i=0;i<numValues;i++)
{var newNumber=Math.floor(Math.random()*25);
dataset.push(newNumber,newNumber);
}
yScale.domain([0,d3.max(dataset)]);
svg.selectAll("rect").data(dataset).transition().delay(function(d,i){return i/dataset.length*1000;})
.duration(500).attr("y",function(d){return h-yScale(d); }).attr("height",function(d) {return yScale(d);}).attr("fill",function(d){
return "rgb(0,0,"+(d.value*10)+")";});
svg.selectAll("text").data(dataset).transition().delay(function(d,i){return i/dataset.length*1000;}).duration(500).text(function(d){return d.value;})
.attr("x",function(d,i){return xScale(i)+xScale.rangeBand()/2;}).attr("y",function(d) {return h-yScale(d)+14;})
.attr("font-family","sans-serif").attr("font- size","10px").attr("fill","white").attr("text-anchor","middle")
;});
});
here is my csv file
names,value
john,78
brad,105
amber,103
james,2
dean,74
pat,45
matt,6
andrew,18
ashley,15
As mentioned by cuckovic there are quite a few errors in your code, not to mention that you seem to be using different styles to achieve similar things which is confusing. Anyway there are 3 fundamental things that are casuing you trouble, the first is your dataset. The value column of the csv is being read as a string. You need to convert it to a number by:
dataset.forEach(function (d,i) {
d.value = +d.value;
});
The next issue you have is the yScale where you have set the range to .range([0,h]);. This is the wrong way round for an svg viewport in which the y direction starts from top and increases towards the bottom. So you need to swap the 0 and h around in the range.
The next thing to address is the difference between d and d.value. When you bind data through the data() operator you are generally binding an array. In this case it is an array of objects. So after you have bound the data, d refers to each element of that array which in this case is an object containing a name and a value. This can be seen if you console.log your dataset. When d is passed to yScale it doesn't know what to do with it as it is not a number, what you really want to do is to pass d.value to yScale. So replace your d's with d.value.
Finally, the last part of your code starting at d3.select("p") does not seem to add anything.
I'd recommend reading Scott Murray's tutorials, particularly this one if you haven't already.