Query a backbone collection (or json) from a Filter model (or json) - json

I'm trying to render a google map from data I have in a LocationCollection.
Users will define filterParameters in a Model, here is the JSON from a filter
var filter = {
"id_ref_type_category":[2,4],
"pricemin":5,
"pricemax":15,
"distmin":10, // meters
"distmax":150 // meters
}
I need to query my collection, here is a sample of json I have for my location
var location = [
{
"id":"1",
"name":"Sushi 1",
"price_min":"10",
"price_max":"20",
"price_avg":"15",
"id_ref_type_category":"1",
"latitude":"48.863831",
"longitude":"2.356215"
},
{
"id":"2",
"description":"Miam ",
"price_min":"15",
"price_max":"35",
"price_avg":"25",
"id_ref_type_category":"4",
"latitude":"48.864269",
"longitude":"2.355153"
},
{
"id":"3",
"name":"Restaurant 1",
"price_min":"5",
"price_max":"20",
"price_avg":"12.5",
"street_number":"60",
"id_ref_type_category":"1",
"latitude":"48.863407",
"longitude":"2.350938"
},
{
"id":"4",
"name":"Chez gigi",
"price_min":"0",
"price_max":"17",
"price_avg":"8.5",
"id_ref_type_category":"2",
"latitude":"48.861824",
"longitude":"2.350901"
}
]
Regarding to my filter parameter, i am looking for
a location with a id_ref_type_category equal 2 OR 4
a location with an average price around 5 and 15
a location within a distance around 10 and 150 (it's in meters) (if user allow geolocation)
I can calculate the distance from my position to the location with this js function from geocoding http://www.geodatasource.com/developers/javascript
I have looked for backbone collection filtering but didn't find a lot, I have looked for json query systems, but couldn't find any stuff.
I takes any suggestions.

How about applying a underscore filter for all the attributes like in here:
http://jsfiddle.net/cnDeu/1/
I have relaxed a bit your filter object so that a location makes it through.
Essentially the filter looks like this:
var filtered = loc.filter(function (el) {
var dist = distance(el.get('latitude'), el.get('longitude'),
position.latitude, position.longitude, 'K') / 1000;
return ((el.get('id') == filter.id_ref_type_category[0]) ||
(el.get('id') == filter.id_ref_type_category[1])) &&
el.get('price_avg') >= filter.pricemin &&
el.get('price_avg') <= filter.pricemax &&
dist >= filter.distmin &&
dist <= filter.distmax;
});

Related

How to add legend for a bar chart with different colors in dc.js?

Below is the code snippet for a barchart with colored bars:
var Dim2 = ndx.dimension(function(d){return [d.SNo, d.something ]});
var Group2 = Dim2.group().reduceSum(function(d){ return d.someId; });
var someColors = d3.scale.ordinal().domain(["a1","a2","a3","a4","a5","a6","a7","a8"])
.range(["#2980B9","#00FFFF","#008000","#FFC300","#FF5733","#D1AEF1","#C0C0C0","#000000"]);
barChart2
.height(250)
.width(1000)
.brushOn(false)
.mouseZoomable(true)
.x(d3.scale.linear().domain([600,800]))
.elasticY(false)
.dimension(Dim2)
.group(Group2)
.keyAccessor(function(d){ return d.key[0]; })
.valueAccessor(function(d){return d.value; })
.colors(someColors)
.colorAccessor(function(d){return d.key[1]; });
How do I add a legend to this chart?
Using composite keys in crossfilter is really tricky, and I don't recommend it unless you really need it.
Crossfilter only understands scalars, so even though you can produce dimension and group keys which are arrays, and retrieve them correctly, crossfilter is going to coerce those arrays to strings, and that can cause trouble.
Here, what is happening is that Group2.all() iterates over your data in string order, so you get keys in the order
[1, "a1"], [10, "a3"], [11, "a4"], [12, "a5"], [2, "a3"], ...
Without changing the shape of your data, one way around this is to sort the data in your legendables function:
barChart2.legendables = function() {
return Group2.all().sort((a,b) => a.key[0] - b.key[0])
.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
An unrelated problem is that dc.js takes the X domain very literally, so even though [1,12] contains all the values, the last bar was not shown because the right side ends right at 12 and the bar is drawn between 12 and 13.
So:
.x(d3.scale.linear().domain([1,13]))
Now the legend matches the data!
Fork of your fiddle (also with dc.css).
EDIT: Of course, you want the legend items unique, too. You can define uniq like this:
function uniq(a, kf) {
var seen = [];
return a.filter(x => seen[kf(x)] ? false : (seen[kf(x)] = true));
}
Adding a step to legendables:
barChart2.legendables = function() {
var vals = uniq(Group2.all(), kv => kv.key[1]),
sorted = vals.sort((a,b) => a.key[1] > b.key[1] ? 1 : -1);
// or in X order: sorted = vals.sort((a,b) => a.key[0] - b.key[0]);
return sorted.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
Note that we're sorting by the string value of d.something which lands in key[1]. As shown in the comment, sorting by x order (d.SNo, key[0]) is possible too. I wouldn't recommend sorting by y since that's a reduceSum.
Result, sorted and uniq'd:
New fiddle.

Google Spreadsheets: generate all combinations of 4 columns with 8 rows each

I'd like to create a function for Google Sheets that allows me to list all possible combinations of the numbers 1 to 8, concatenated 4 times (from 1111 to 8888, I think that is 8^4 = 4096).
(I add a screenshot for clarity).
So far I tried with:
=ArrayFormula(transpose(split(concatenate(A2:A9&B2:B9&C2:C9&D2:D9& char(9)),char(9))))
...but this gives me only 8 combinations: 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888.
I'm slightly bad at programming, specially with new languages, so any help is very much appreciated!
Here is a little custom function that creates all combinations of rows (it's just easier to write for rows):
function combinations(arr) {
return arr.reduce(function(prod, row) {
var out = [];
for (i in row) {
out = out.concat(prod.map(function(x) {
return x.concat(row[i]);
}));
}
return out;
}, [[]]);
}
Using it as =combinations(A2:D9) would create 4^8 combinations, each of length 8, and that is not what you want. But it's easy enough to transpose:
=combinations(transpose(A2:D9))
The above function returns combinations as a rectangular array, so in your example the output would be 4 columns wide. If you want to join the combinations in one cell (so the output is a single column), use this modified version:
function joincombinations(arr) {
return arr.reduce(function(prod, row) {
var out = [];
for (i in row) {
out = out.concat(prod.map(function(x) {
return x.concat(row[i]);
}));
}
return out;
}, [[]]).map(function(row) {
return row.join("");
});
}
Usage: =joincombinations(transpose(A2:D9))
Try
=arrayformula(if(row(A:A)>4096,"",int((row(A:A)-1)/512)+1&mod(int((row(A:A)-1)/64),8)+1&mod(int((row(A:A)-1)/8),8)+1&mod(int((row(A:A)-1)/1),8)+1))
(needs at least 4096 rows in the sheet).

Bubble chart in d3.js

http://plnkr.co/edit/7aw93EnMyCR3HjTu1uHa?p=preview
I have added the working fiddle.
I need to plot bubble chart on the basis of "profit" value in properties of "taluks.geojson" file.
This is working for flare.json but not for taluks.geojson file.
I have tried modifying the code in index.html as-
d3.json("taluks.geojson", function(error, root) { if (error) throw error;
var node = svg.selectAll(".node") .data(bubble.nodes(classes(root)) .filter(function(d) { return !d.properties; }))
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) { if (node.properties) node.properties.forEach(function(child) { recurse(node.name, child); }); else classes.push({packageName: name, className: node.NAME_2, value: node.profit}); }
recurse(null, root); return {properties: classes}; }
But the code is not working for taluks.geojson but only working for flare.json.
Please help regarding how to plot bubble chart on the basis of profit in properties of taluks.geojson file.
Please kindly suggest any further modificatons.
Thank you.
To render bubble chart or tree chart data should be in relational format parent and child, If you see flare.json data
{
"name": "flare",//root parent or level 0
"children": [
{
"name": "analytics",//child node or level 1
"children": [
{
"name": "cluster", //child node to analytics or level 2
"children": [
{"name": "AgglomerativeCluster", "size": 3938},
{"name": "CommunityStructure", "size": 3812},
{"name": "HierarchicalCluster", "size": 6714},
{"name": "MergeEdge", "size": 743}
]
}]
}]
}
/*
* In the above the data is hierarchical here flare will be like parent and children are in children array, name will be name of the node and at the end node or leaf node we don't have any children so here we reached to leaf
and taluks.geojson is not having the structure of desired manner. So we have to make it into our desired structure.
*
*/
var data= d3.nest().key(function(d){ return d.properties.NAME_2;}).key(function(d){ return d.properties.NAME_3;}).entries(root.features);
/*
* d3.nest function will perform the grouping operation mentioned/returned by the key function, this function is used to group elements by using key and it takes the data from entries function.
* so as per above code we are grouping by NAME_2 which is district name and again we are grouping that data based on NAME_3
* from above we'll get data in below format
* [{"key":"East Godavari","values":[
* {"key":"Kottapeta","values":[
* {"type":"Feature","properties":{"NAME_2":"East Godavari","NAME_3":"Kottapeta","profit":326,"npa":174},
* "geometry":{"type":"MultiPolygon","coordinates":[
* [[[81.75195312500006,16.772489547729492],[81.76336669921892,16.7611598968507],[81.7764129638673,16.755041122436637],[81.76336669921875,16.7611598968507],[81.75195312500006,16.772489547729492]]]
* ]}}]
* },
* {"key":"Rajahmundry","values":[{"type":"Feature","properties":{"NAME_2":"East Godavari","NAME_3":"Rajahmundry","profit":1762,"npa":1683},"geometry":{"type":"MultiPolygon","coordinates":[
* [[[81.71717071533203,17.0606307983399],[81.72284698486357,17.047969818115348],[81.72709655761736,17.035369873046875],[81.72931671142607,17.02490997314453],[81.73168945312517,17.009309768676758],[81.73249053955084,16.990680694580135],[81.731689453125,17.009309768676758],[81.7293167114259,17.02490997314453],[81.72709655761719,17.035369873046875],[81.7228469848634,17.047969818115348],[81.71717071533203,17.0606307983399]]],
* ]}}]
* },
* {"key":"Rampa Chodavaram","values":[{"type":"Feature","properties":{"NAME_2":"East Godavari","NAME_3":"Rampa Chodavaram","profit":376,"npa":362},"geometry":{"type":"MultiPolygon","coordinates":[[[[81.64000701904303,17.217769622802678],[81.63854217529308,17.24398994445812],[81.64405822753918,17.25922966003418],[81.64591217041021,17.293151855468864],[81.64405822753935,17.25922966003418],[81.63854217529325,17.24398994445812],[81.64000701904303,17.217769622802678]]],
* [[[81.51127624511724,17.463871002197266],[81.51648712158232,17.458469390869084],[81.52410888671903,17.454042434692326],[81.53122711181658,17.4517498016358],[81.55007171630854,17.447589874267692],[81.5312271118164,17.4517498016358],[81.52410888671886,17.454042434692326],[81.51648712158214,17.458469390869084],[81.51127624511724,17.463871002197266]]]]}}]
* },
* {"key":"Razole","values":[
* {"type":"Feature","properties":{"NAME_2":"East Godavari","NAME_3":"Razole","profit":1185,"npa":1141},"geometry":{"type":"MultiPolygon","coordinates":[
* [[[81.6993026733399,16.41020011901867],[81.70881652832048,16.383939743041992],[81.7134628295899,16.35638809204113],[81.70881652832031,16.383939743041992],[81.6993026733399,16.41020011901867]]],
* ]}}]
* }
* .........
* ]}]
To know more about d3 nest function go this link https://github.com/mbostock/d3/wiki/Arrays
* above structure is generated by d3.nest function, now we need to arrange that data into parent child hierarchical data, for this we are using below code
*/
var myData={name:'flare', children:[]};//this will be root
data.forEach(function(distc){
var dis={};
dis.name=distc.key;//here distc will hold one level write console.log(distc); and observe the data
dis.children = [];
myData.children.push(dis);
distc.values.forEach(function(chil){
var chis={};
chis.name=chil.key;
// chis.children=chil.values;
chis.size=chil.values[0].properties.profit;//we are using size as value in recurse function of classes function and we want to display bubble chart on profit value
dis.children.push(chis);
});
});
console.log(myData);//myData has the desired structure
root=myData;//assigning that json to root variable
I think you know what happens with the rest of the code.
Hope everything is clear, If not ask me for more.
:D
In order to create bubble chart we are using d3 pack layout, this layout we are generated at
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5); and this bubble calling nodes at this line
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))//here bubble.nodes takes the json, that json should be in leaf structure, nodes function will generate relational data, write console.log(bubble.nodes(classes(root))); Here we are using classes function, because flare.json has already relational format json but bubbles.nodes need leaf structure so in classes function we are converting that flare.json relational structure into leaf
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

Using ItemCollection on a BoxFolder type with Box API only returns 100 results and cannot retrieve the remaining ones

For a while now, I've been using the Box API to connect Acumatica ERP to Box and everything has been going fine until recently. Whenever I try to use a BoxCollection type with the property ItemCollection, I'll only get the first 100 results no matter the limit I set in the GetInformationAsync(). Here is the code snippet:
[PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
public BoxCollection<BoxItem> GetFolderItems(string folderId, int limit = 500, int offset = 0)
{
var response = new BoxCollection<BoxItem>();
var fieldsToGet = new List<string>() { BoxItem.FieldName, BoxItem.FieldDescription, BoxItem.FieldParent, BoxItem.FieldEtag, BoxFolder.FieldItemCollection };
response = Task.Run(() => Client.FoldersManager.GetFolderItemsAsync(folderId, limit, offset)).Result;
return response;
}
I then pass that information on to a BoxFolder type variable, and then try to use the ItemCollection.Entries property, but this only returns 100 results at a time, with no visible way to extract the remaining 61 (in my case, the Count = 161, but Entries = 100 always)
Another code snippet of the used variable, I am basically trying to get the folder ID based on the name of the folder inside Box:
private static void SyncProcess(BoxFolder rootFolder, string folderName)
{
var boxFolder = rootFolder.ItemCollection.Entries.SingleOrDefault(ic => ic.Type == "folder" && ic.Name == folderName);
}
I wasn't able to find anything related to that limit = 100 in the documentation and it only started to give me problems recently.
I had to create a work around by using the following:
var boxCollection = client.GetFolderItems(rootFolder.Id);
var boxFolder = boxCollection.Entries.SingleOrDefault(ic => ic.Type == "folder" && ic.Name == folderName);
I was just wondering if there was a better way to get the complete collection using the property ItemCollection.Entries like I used to, instead of having to fetch them again.
Thanks!
Box pages folder items to keep response times short. The default page size is 100 items. You must iterate through the pages to get all of the items. Here's a code snippet that'll get 100 items at a time until all items in the folder are fetched. You can request up to 1000 items at a time.
var items = new List<BoxItem>();
BoxCollection<BoxItem> result;
do
{
result = await Client.FoldersManager.GetFolderItemsAsync(folderId, 100, items.Count());
items.AddRange(result.Entries);
} while (items.Count() < result.TotalCount);
John's answer can lead to a duplicate values in your items collection if there will be external/shared folders in your list. Those are being hidden when you are calling "GetFolderItemsAsync" with "asUser" header set.
There is a comment about it in the Box API's codeset itself (https://github.com/box/box-windows-sdk-v2/blob/main/Box.V2/Managers/BoxFoldersManager.cs)
Note: If there are hidden items in your previous response, your next offset should be = offset + limit, not the # of records you received back.
The total_count returned may not match the number of entries when using enterprise scope, because external folders are hidden the list of entries.
Taking this into account, it's better to not rely on comparing the number of items retrieved and the TotalCount property.
var items = new List<BoxItem>();
BoxCollection<BoxItem> result;
int limit = 100;
int offset = 0;
do
{
result = await Client.FoldersManager.GetFolderItemsAsync(folderId, limit, offset);
offset += limit;
items.AddRange(result.Entries);
} while (offset < result.TotalCount);

Random Real US Address within a specific zip code, county or area

so for a project in school I am trying to simulate where students live in our town since official data is not available obviously due to privacy concerns. I started looking for a generator that works by zip code, radius or county but I haven't been able to find any (commercially or free) I would love it to be free, but I might be able to secure some funding for a license. If I find random generators, they cannot be limited to a ZIP code or city to produce real addresses randomly.
A good idea I found was here: https://stackoverflow.com/a/12289969/1778542
Based on that I would pick the city center's long\lat coordinates, find out the outskirts coordinates to create a plane, then randomly generate long\lat coordinates within the plane, feed them back in to have Google approximate the addresses for it. One concern that was raised (and I try to avoid) is that Google doesn't use verified addresses, rather approximations.
Does anyone have a hint where to find such a generator or a sleeker way to use GMaps?
Thanks a million!
GP
I use this code in one of my Laravel Seeder it gets a random street name in Romania provided that you give it the location area and Town,
It works by getting the latitude and longitude for that area and then randomly adds a radius of 2 Kilometers, after that it makes another request to google api, and from that it extracts a random street name.
I don't know if this will help you, adjusting this code can generate a real address provided that you give a first good location to look;
Here is the code:
protected function getRandomStreetNameFromCity($judet, $city){
$kmRange = 2;
$initalLocation = [];
$randomLocation= [];
$randomKmval = mt_rand(1, $kmRange) / mt_getrandmax();
// Poor Man Lat and Lng
//Latitude: 1 deg = 110.574 km
//Longitude: 1 deg = 111.320*cos(latitude) km
$guzzelCl = new Client();
$guzelReq = $guzzelCl->request('GET', 'http://maps.googleapis.com/maps/api/geocode/json?address=Romania,'.$judet.','.$city.'&sensor=false', [
'verify' => false,
]);
if($guzelReq->getStatusCode() == 200){
$arrJson = json_decode($guzelReq->getBody(), true);
while (count($arrJson['results']) <= 0){
$judet= $this->getNewJudet();
$city = $this->getNewOras();
$guzelReq = $guzzelCl->request('GET', 'http://maps.googleapis.com/maps/api/geocode/json?address=Romania,'.$judet.','.$city.'&sensor=false', [
'verify' => false,
]);
$arrJson = json_decode($guzelReq->getBody(), true);
}
$initalLocation = $arrJson['results'][0]['geometry']['location'];
}
$plusMinus = $this->generateRandomString(1);
$randomExp = [ 1 => $tempLat = eval("return (1 / (110.574 ".$plusMinus." ".$randomKmval." )+ ".$initalLocation['lat']." );"),
2 => eval('return ('.$initalLocation['lng'].' '.$plusMinus.' 1/111.320*cos($tempLat));'),
];
$guzelReq = $guzzelCl->request('GET', 'http://maps.googleapis.com/maps/api/geocode/json?latlng='.$randomExp[1].','.$randomExp[2], [
'verify' => false,
]);
return explode(',', json_decode($guzelReq->getBody(), true)['results'][0]['formatted_address'])[0];
}
protected function getNewJudet(){
//This is a administrative type of location named 'judet' Romania is divided in a number bellow 50 of this
return array_rand($this->judetOras, 1);
}
protected function getNewOras(){
//This is a Town String
return $this->judetOras[$iterateJud = array_rand($this->judetOras, 1)][array_rand($this->judetOras[$iterateJud], 1)];
}
protected function generateRandomString($length = 10) {
$characters = '-+';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}