using CSV property to set link length, node size d3 - graph

Following the example given by mbostock for creating a force graph with a csv:
How to convert to D3's JSON format?
http://bl.ocks.org/2949937
I'm creating a force graph with D3 but am unsure how / where to call upon a value from the CSV line to set node size, color, or link length.
I tried a few things e.g.:
links.forEach(function(link) {
link.source = nodeByName(link.user1);
link.target = nodeByName(link.user2);
link.size = nodeByName(link.somevaluefromcsv)
link.distance = nodeByName(link.somevaluefromcsv);
});
This is just wrong. From what I can tell, it just generates empty nodes and the values aren't callable elsewhere.
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) {return d[3];}) //this is not returing any value as far as I can tell.
.call(force.drag);
or further down in the tick function:
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) {return d[7];});
There are probably a few things causing problems:
1. I don't seem to have a good conceptual model of the links function or the nodesbyName array or function.
Typical lines from the CSV (as it is now), in the order of:
time, user1, user2, similarity score, total points, points against, points for, length
are:
1223.8167,john6,john5,0.153846153846,1,0,1,5
1223.9166,john6,john5,0.185185185185,8,0,8,6
1223.9667,bobby4,bobby3,0.402777777778,224,320,-96,15
1224.1167,bobby4,bobby3,0.402777777778,226,310,-84,15
1224.2,bobby4,bobby3,0.402777777778,240,283,-43,15
1224.2,john6,john5,0.185185185185,2,0,2,5
1224.2,john6,john5,0.153846153846,2,0,2,5
1224.2667,bobby4,bobby3,0.397058823529,0,24,-24,13
1224.2833,john6,john5,0.153846153846,1,0,1,5
1224.45,bobby4,bobby3,0.397058823529,0,21,-21,13
1224.55,bobby4,bobby3,0.442857142857,0,18,-18,14

function nodeByName(name) - create new node or return existing node with name specified as argument.
if you need to pass node properties from links, you can do it in forEach
links.forEach(function(link) {
link.source = nodeByName(link.user1);
link.target = nodeByName(link.user2);
link.source.radius = link.radius
link.size = link.somevaluefromcsv;
link.distance = link.somevaluefromcsv;
});
links - array of links between nodes.
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) {return d.radius;}) // radius property of node
.call(force.drag);

Related

apply a function over 2 consecutive images in an imageCollection in google earth engine

the function .map applies a function to every individual image in an ImageCollection. And the function .iterate applies a function to one image and the output of the calculation done to the precedent image on an ImageCollection.
The first only works with one image each time, and the second implies modifying each image and utilize it to any calculation with the next one.
I need a function that works like .iterate, but does not modify the precedent image. I just need to do:
image (time -1) / image (time 0).
I can not find a way to do it,
thanks for your help
i have tried,
var first = ee.List([
ee.Image(1).set('system:time_start', time0).select([0], ['pc1'])
]);
var changeDET = function(image, list) {
var previous = ee.Image(ee.List(list).get(-1));
var change = previous.divide(image.select('pc1'))
.set('system:time_start', image.get('system:time_start'));
return ee.List(list).add(change);
};
var cumulative = ee.ImageCollection(ee.List(imageCollection.iterate(changeDET, first)))
.sort('system:time_start', false)
What you can do is to convert your imageCollection into a ee.List object, then map over that list with an index variable to access the previous image. Example code is below:
var length = yourImageCollection.size();
var list = yourImageCollection.toList(length);
var calculated_list = list.map(function(img) {
var index = list.indexOf(img)
img = ee.Image(img);
var previousIndex = ee.Algorithms.If(index.eq(0), index, index.subtract(1));
var previousImage = ee.Image(list.get(previousIndex)):
var change = ee.Image(previousImage.divide(img)
.copyProperties(img, ["system:time_start"]));
return change;
})
I'm not sure what you want to do with the first image, so when map function reach the first image, previousIndex will equal to index. In other words, the first image will be divided by itself (as there is no image before it).
Hope this helps.

How to separate multiple columns from a range in an array?

I have a range of data in a Google Sheet and I want to store that data into an array using the app script. At the moment I can bring in the data easily enough and put it into an array with this code:
var sheetData = sheet.getSheetByName('Fruit').getRange('A1:C2').getValues()
However, this puts each row into an array. For example, [[Apple,Red,Round],[Banana,Yellow,Long]].
How can I arrange the array by columns so it would look: [[Apple,Banana],[Red,Yellow],[Round,Long]].
Thanks.
It looks like you have to transpose the array. You can create a function
function transpose(data) {
return (data[0] || []).map (function (col , colIndex) {
return data.map (function (row) {
return row[colIndex];
});
});
}
and then pass the values obtained by .getValues() to that function..
var sheetData = transpose(sheet.getSheetByName('Fruit').getRange('A1:C2').getValues())
and check the log. See if that works for you?
Use the Google Sheets API, which allows you to specify the primary dimension of the response. To do so, first you must enable the API and the advanced service
To acquire values most efficiently, use the spreadsheets.values endpoints, either get or batchGet as appropriate. You are able to supply optional arguments to both calls, and one of which controls the orientation of the response:
const wb = SpreadsheetApp.getActive();
const valService = Sheets.Spreadsheets.Values;
const asColumn2D = { majorDimension: SpreadsheetApp.Dimension.COLUMNS };
const asRow2D = { majorDimension: SpreadsheetApp.Dimension.ROWS }; // this is the default
var sheet = wb.getSheetByName("some name");
var rgPrefix = "'" + sheet.getName() + "'!";
// spreadsheetId, range string, {optional arguments}
var single = valService.get(wb.getId(), rgPrefix + "A1:C30");
var singleAsCols = valService.get(wb.getId(), rgPrefix + "A1:C30", asColumn2D);
// spreadsheetId, {other arguments}
var batchAsCols = valService.batchGet(wb.getId(), {
ranges: [
rgPrefix + "A1:C30",
rgPrefix + "J8",
...
],
majorDimension: SpreadsheetApp.Dimension.COLUMNS
});
console.log({rowResp: single, colResp: singleAsCols, batchResponse: batchAsCols});
The reply will either be a ValueRange (using get) or an object wrapping several ValueRanges (if using batchGet). You can access the data (if any was present) at the ValueRange's values property. Note that trailing blanks are omitted.
You can find more information in the Sheets API documentation, and other relevant Stack Overflow questions such as this one.

How to query exactly selected items in Paper.js?

According to my understanding, project.getItems({selected: true}) returns wrong results: I'm selecting a curve, it returns the parent Path: Sketch
Try clicking on a curve or a segment. Whole path will be moved. Then try changing the behavior by setting var workaround = false to var workaround = true to observe desired behavior.
How can I get exactly what is really selected?
Current workaround
I'm currently adding those objects into an array on selection and use those items instead of project.getItems({selected: true}).
The thing is that in Paper.js architecture, curves and segments are not items (they are part of a specific item which is the path). So you shouldn't expect project.getItems() to return anything else than items.
Another thing you have to know is that a path is assumed selected if any of its part is selected (curves, segments, points, handles, position, bounds, ...). And a curve is assumed selected if all of its parts are selected (points and handles).
With that in mind, you can create an algorithm to retrieve "what is really selected" based on project.getItems({selected: true}) as its first part. Then, you need to loop through curves and segments to check if they are selected.
Here is a sketch demonstrating a possible solution.
var vector = new Point(10, 10);
// Create path.
var path = new Path({
segments: [
[100, 100],
[200, 100],
[260, 170],
[360, 170],
[420, 250]
],
strokeColor: 'red',
strokeWidth: 10
});
// Translate given thing along global vector.
function translateThing(thing) {
switch (thing.getClassName()) {
case 'Path':
thing.position += vector;
break;
case 'Curve':
thing.segment1.point += vector;
thing.segment2.point += vector;
break;
case 'Segment':
thing.point += vector;
break;
}
}
// On mouse down...
function onMouseDown(event) {
// ...only select what was clicked.
path.selected = false;
hit = paper.project.hitTest(event.point);
if (hit && hit.location) {
hit.location.curve.selected = true;
}
else if (hit && hit.segment) {
hit.segment.selected = true;
}
// We check all items for demo purpose.
// Move all selected things.
// First get selected items in active layer...
project.activeLayer.getItems({ selected: true })
// ...then map them to what is really selected...
.map(getSelectedThing)
// ...then translate them.
.forEach(translateThing);
}
// This method returns what is really selected in a given item.
// Here we assume that only one thing can be selected at the same time.
// Returned thing can be either a Curve, a Segment or an Item.
function getSelectedThing(item) {
// Only check curves and segments if item is a path.
if (item.getClassName() === 'Path') {
// Check curves.
for (var i = 0, l = item.curves.length; i < l; i++) {
if (item.curves[i].selected) {
return item.curves[i];
}
}
// Check segments.
for (var i = 0, l = item.segments.length; i < l; i++) {
if (item.segments[i].selected) {
return item.segments[i];
}
}
}
// return item by default.
return item;
}
That said, depending on your real use case, your current workaround could be more appropriate than this approach.

d3 - Data loads partially

I have a problem with a data string returning 'undefined' in my tooltip, while that same piece of data does affect the colors of my svg.
Objective: I want to display a map, chloropleth style, that displays a tooltip for each country with
a) country names, loaded from a .json object (works fine)
b) some values corresponding to each country, loaded from a tsv (works partially so far)
File structure :
The main .js file calls 1 topojson file (with country paths and names) as well as a .tsv file (with specific area data for each country)
Resources used:
1- MBostock's chloropleth (see note below)
2- d3.tip by Justin Palmer
Play with it
Here is a Plunker for people to play with it (due to the heaviness of the world.json I use, it may take a while to load).
---------------
Relevant code bit :
queue()
.defer(d3.json, "world.json")
.defer(d3.tsv, "winemap.tsv", function setMaptoTotal(d) { rate.set(d.name, +d.total); })
.await(ready);
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(function mylabel(d) { return d.properties.name + "<table><tr><td><span style='color: #fcf772'> Total area cultivated: " + d.total +" ha</span></td></tr><tr><td> <span style='color:#bf2a2a'> Global rank: "+ d.rank + " </span></td></tr></table>";})
.direction('sw')
.offset([0, 2]);
var svg = d3.selectAll('body')
.append('svg')
.attr('width',width)
.attr('height',height)
.call(zoom)
.call(tip);
// ACTION STARTS HERE //
function ready(error, world) {
svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(world, world.objects.countries).features)
.enter().append("path")
.attr("class", function (d) { return quantize(rate.get(d.properties.name)); })
.attr("d", path)
.call(tip)
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
;
Options tried:
1- Moving .call(tip) after or inside the ready function which triggers data load via queue(). >>> This still returns undefined in my tooltip.
2- Use queue.defer(mylabel(d)). >>> Nothing appears on screen.
3- Taking into account the asynchronous nature of d3.tsv, I went through this question. If I understand well, you have to refer to the function you want to call both inside and after d3.tsv (with or without setting a global variable). I thus planned to refer to the mylabel(d) function both inside and after d3.tsv. However I noticed that function (d) { return quantize(rate.get(d.properties.name)); }) is not written in my d3.tsv yet it works fine.
Note: sorry I could not post more links at this stage, e.g. to MBostick's chloropleth or d3.tip, but it requires more reputation :)
In case anyone faces the same issue, I finally found the answer.
There were 2 problems:
1- Data should be first associated with an array.
First you need to pair, inside the ready function, the id with each data entry
function ready (error, world, data) {
var Total= {};
var Name= {};
var Rank= {};
data.forEach(function(d) {
Total[d.id] = +d.total;
Name[d.id] = d.name;
Rank[d.id] = +d.rank;
});
2- This method only works with referring explicitly to "id"
The command .data(topojson.feature(world, world.objects.countries).features) will search for the id property of each json object that you want to apply a transformation with. The bridge between your .tsv and .json MUST be id, both in your .json and .tsv files as well as the code.
The reason you can't replace id with just any name while working with topoJSON is that it holds a specific function in the topojson code itself (see below).
{var r={type:"Feature",id:t.id,properties:t.properties||{},geometry:u(n,t)};return null==t.id&&delete r.id,r}
I solved this by adding into my .tsv file a column with the id attributes of all countries objects inside my .json file. I named this column idso the pairing described in 1- can function.
Careful, you want to represent all countries that are in your .json on your .tsv, otherwise "undefined" will appear again when you hover the countries that would not be mentioned on your .tsv.
Final, working code -
function ready (error, world, data) {
var pairDataWithId= {};
var pairNameWithId= {};
var pairRankWithId= {};
// “d” refers to that data parameter, which in this case is our .csv that (while not necessary) is also named “data.”
data.forEach(function(d) {
pairDataWithId[d.id] = +d.total;
pairNameWithId[d.id] = d.name;
pairRankWithId[d.id] = +d.rank;
});
// Below my tooltip vars
var tipin = function(d) { d3.select(this).transition().duration(300).style('opacity', 1);
div.transition().duration(300).style('opacity', 1);
div.text(pairNameWithId[d.id] + " Total Area: " + pairDataWithId[d.id]+ " Rank: " + pairRankWithId[d.id])
.style('left', (d3.event.pageX) + "px")
.style('top', (d3.event.pageY -30) + "px");
};
var tipout = function() {
d3.select(this)
.transition().duration(300).style('opacity', 0.8);
div.transition().duration(300)
.style('opacity', 0);
};
// Resume
function colorme(d) { return color (pairDataWithId[d.id]);
}
svg.append('g')
.attr('class', 'region')
.selectAll('path')
.data(topojson.feature(world,world.objects.countries).features)
.enter()
.append('path')
.attr('d', path)
.style('fill', colorme)
.style('opacity', 0.8)
.on ('mouseover', tipin)
.on('mouseout', tipout);
};
Note: I stopped using d3.tip for a simpler version with full code above.

Colors in dc.js charts not linked with each other

I have a choropleth(world map) and a bubble chart in dc.js. The colors in the bubbles and the map should be the same (country wise). On selection of a country, the filtered bubble should have the same color as of the map because the map and bubbles are linked with the same country.
How am i suppose to achieve it.
Any suggestion will be helpful.
Thanks in advance.
You should be able to set the same color scale for all the charts, as long as the keys (country names) are the same across charts.
EDIT: because of the limitations below, probably the best approach is to use a custom reduce function that produces an object or tuple. Something like (untested):
that.countrywiseInvGroup = that.countries.group().reduce(
function(d, p) {
p.inv += d.initial_inv;
p.country = d.country;
return p;
},
function(d, p) {
p.inv -= d.initial_inv;
return p;
},
function() {
return {inv: 0};
});
// ...
.colorAccessor(function (d) {
return d.country;
})
.title(function (d) {
if(d.value){
return "Country: " + d.key + "\nTotal Initial Investment: USD $" + that.formatCurrency(d.value.inv);
}
})
```

Resources