Binding some attributes of object to data - d3.js

I have data in the form of an object, and I'm trying to bind only some attributes of the object to my selection as data.
The object looks something like this:
var data = {'attr1': 100,
'attr2': "foo",
'attr3': 200,
...
'attr20': 34}
I have the attribute names I'm interested in stored in an array:
keys = ['attr1', 'attr3', 'attr20']
I want to bind the values (100, 200, 34). I'm doing the following:
var selection = d3.select('ul')
.selectAll('li')
.data(keys, function(key){ return data[key]})
.enter()
.append('li')
.text(function(d){return d})
And instead of getting "100" "200" and "34" as text outputs, I get "attr1", "attr2", and "attr3". I would have expected the key function to return the values of data, but it's only returning the keys.
Any suggestions on how I can bind only some attributes of the data object, when I know those keys?
I know my use is a little "backwards"... I made a jsfiddle here to show the output: http://jsfiddle.net/ChwLM/1/

You will need to filter change the data before setting it – http://jsfiddle.net/ChwLM/3/.
In d3, when you pass a function as the second argument, the following is how it gets called:
for (i = -1; ++i < m;) {
keyValue = key.call(groupData, nodeData = groupData[i], i);
if (node = nodeByKeyValue.get(keyValue)) {
updateNodes[i] = node;
node.__data__ = nodeData;
} else if (!dataByKeyValue.has(keyValue)) { // no duplicate data key
enterNodes[i] = d3_selection_dataNode(nodeData);
}
dataByKeyValue.set(keyValue, nodeData);
nodeByKeyValue.remove(keyValue);
}
groupData is ['attr1', 'attr3', 'attr20']. What you are changing with your function is the key of the data, not the value. With this – nodeData = groupData[i] – the value is getting set before you can change it. Here's the relevant piece from the fiddle above:
keys = ['attr1', 'attr3', 'attr20'];
filtered = [];
for (var i = 0, l = keys.length; i < l; ++i) {
filtered.push(data[keys[i]]);
}
And then you can pass filtered in to .data.

Related

Google Apps Script that loops through a filter and sends an e-mail with a PDF?

I have data from a questionnaire (20K rows) that I need to share with the store managers (report) of our shops (400 shops). I managed to write a script that sends a pdf of my sheet to a list of e-mail addresses. But I'm stuck on writing the loop for the filter, since I can't get the setVisibleValues(values) function to work for FilterCriteriaBuilder. The setHiddenValues(values) function works, but I can't figure out how to combine that with the loop.
Sample of my Google Sheet
See below for my current code:
/**
* Filtersheet by location
*/
function FilterSheet() {
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data')
spreadsheet.getRange('F1').activate();
var criteria = SpreadsheetApp.newFilterCriteria()
.setHiddenValues(['Amsterdam, Rotterdam'])
.build();
spreadsheet.getFilter().setColumnFilterCriteria(6, criteria);
};
/**
* Send pdf of currentspreadsheet
*/
function SendPdf() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Adres');
var blob = DriveApp.getFileById(ss.getId()).getAs("application/pdf");
blob.setName(ss.getName() + ".pdf");
var startRow = 2; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = spreadsheet.getRange(startRow, 1, numRows, 2);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i in data) {
var row = data[i];
var emailAddress = row[0]; // First column
var message = 'I hearby send you the overview of your data'
var subject = 'Overview of data';
MailApp.sendEmail(emailAddress, subject, message,{
attachments:[blob]});
}
}
getValues() returns the values of all range's cells no matter if they are shown or hidden.
Use a loop and isRowHiddenByFilter(rowPosition) to reap out all the filtered values. You could use Array.prototype.push to add the values to a new array or use Array.prototype.splice to modify the array holdin the values returned by getValues()
Related
How to use in Google Sheets setValue only for range of filtered rows (getRange for not hidden cells)?
I managemed to solve the problem.
This script takes a google spreadsheet with 2 sheets,one with Data and one with a combination EmailAdresses.
It sends a filtered list (filter column F) of sheet Data to the corresponding salon (location) in sheet Emailadresses (var mode email). Additionally, it has the option to "store" the pdf's in your google drive (var mode store)
*/
function construct() {
// settings:
//var mode = "store";
var mode = "email";
// get list of all salons and email
var salonList = SpreadsheetApp.getActive().getSheetByName('EmailAdressen');
// set endvar for loop
var endRow = salonList.getLastRow();
// loop trough the rows to get the Salon name and the corresponding email
for(i=1;i<=endRow;i++){
var salonName = salonList.getRange(i,2).getValue();
var email = salonList.getRange(i,1).getValue();
// create an array with all salons that should be hidden (we cant pick which one to show, so we have to go the other way around...)
var filterArray = [];
// create array with all salons to hide
for(c=1;c<=endRow;c++){
// get value from email list, check if it is not the current selected one and if so add it to the list to filter out
salonFilterName = salonList.getRange(c,2).getValue();
if(salonFilterName != salonName) {
filterArray.push(salonFilterName);
}
} // end for c
// filter the list with the array we just created
var spreadsheet = filterList(filterArray);
if(mode == "email"){
// export to PDF
var pdf = exportToPdf(spreadsheet);
// email to email address belonging to this salon
emailToAddress(email, pdf);
} // end if
if(mode == "store"){
StorePdf(spreadsheet, salonName);
}
} // end for i
return;
}
function filterList(salonNameArray) {
// select data sheet
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
// first remove all existing filters to make sure we are on a clean sheet
if(spreadsheet.getFilter()){
spreadsheet.getFilter().remove();
}
// create the filter
spreadsheet.getRange('F:F').createFilter();
// set criteria for filter with array passed from construct
var criteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(salonNameArray).build();
// apply filter
spreadsheet.getFilter().setColumnFilterCriteria(6, criteria);
return spreadsheet;
}
function exportToPdf(ss) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
var blob = DriveApp.getFileById(ss.getId()).getAs("application/pdf");
blob.setName(ss.getName() + ".pdf");
return blob;
}
function StorePdf(ss, salonName) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
var blob = DriveApp.getFileById(ss.getId()).getBlob();
blob.setName(salonName + "_" + Utilities.formatDate(new Date(), "GMT+1", "ddMMyyyy")+".pdf");
DriveApp.createFile(blob);
return;
}
function emailToAddress(email, pdf) {
MailApp.sendEmail(email, 'Type here the subject', 'Type here the body',{
attachments:[pdf]});
return;
}

Set the color in a choropleth based on the key

I'm trying to set the color of a choropleth based on some properties linked to the key.
However, all the methods to set the color only have the value of that specific place, but not its key.
var map = dc.geoChoroplethChart(dom)
.height(width*0.8)
.width(width)
.dimension(dim)
.projection(projection)
.colorAccessor(function(d,i){
// it isn't the usual d={key,value} but only d=value
})
.colorCalculator(function(d,i){
// it isn't the usual d={key,value} but only d=value
//and it's deprecated
})
.title(function (d) {return d.key + ': ' + d.value;})
// got the key,value as expected
how do I get the key from colorAccessor? it works fine from the other functions (eg title)
so it turns out it's "normal". As suggested by Gordon, the solution is to map the index of the geojson with the data you want. In my case, I have extra data about each countries on a country array:
var geojson=topojson.feature(europe,europe.objects.countries);
// map index in the map to country data this hack is to solve a problem of not having the key on the colorAccessor
var geo2data=[];
geojson.features.forEach(function(d,i1){
var i2=country.findIndex(function(c) {
return c.country.toUpperCase()==d.id});
geo2data[i1] = i2;
});
var map = dc.geoChoroplethChart(dom)
...
.colorAccessor(function(v,i){
if (geo2data[i] >= 0){
//geo2data[i] contains extra values, eg
return v / country[geo2data[i]].threshold;
}
return v;
})
The workaround I ended up using:
create a custom reduce function that duplicates the key in the value.
//the iso code of the country is the key
var group = dim.group().reduce(
function (p, v) {
p.total += +v.total;
if (p.iso == "") {
p.iso = v.country;
}
return p;
},
function (p, v) {
p.total -= +v.total;
return p;
},
function () { return {
total:0,iso:""
};
});
var map = dc.geoChoroplethChart(dom)
.colorAccessor(function(d){
if (!d || !d.value.iso)
return null; // area without data
// now d.value.iso contains the key, and d.value.total contains the value
return colorIndex(d.value.iso,d.value.total);// do magic
})
....

Why do I get "Illegal invocation" of this object?

Why do I get "Uncaught TypeError: Illegal invocation" when I post this functions returned object into an ajax post:
base.serialize = function()
{
var data
, depth = 0;
step = function(level, depth)
{
var array = [ ]
, items = level.children("li");
items.each(function()
{
var li = $(this)
, item = $.extend({}, li.data())
, sub = li.children("ol");
if (sub.length)
{
item.children = step(sub, depth + 1);
}
array.push(item);
});
return array;
}
data = step(base.$el, depth);
return data;
};
What I'm trying to do is to convert an HTML tree with data values to an array, to save sort order to database:
/*
* ------------------------------------------------------
* Liveflex Treeview
* ------------------------------------------------------
*/
var tree = $(".dd-list").liveflex_treeview({
handle : 'div.dd-handle'
, opencollapse : '.opencollapse'
, itemMoved : function(e)
{
var sort_array = e.serialize();
// Save order
$.post('/url_fetch/sort_posts', { 'sort_array' : sort_array }, function(data)
{
console.log('Data:' + data);
});
}
});
You're trying to post an object containing DOM elements. But DOM elements have cyclic properties (they all points to the window, for example) and sometimes contains properties you can't fetch. They can't be serialized as JSON (or by any naive function just recursively serializing the properties).
Your solutions might be :
to replace the DOM elements with some kind of representation related to your application
to use a special function to serialize the DOM elements (see this related question)

Dynamically choose which properties to get using Linq

I have an MVC application with a dynamic table on one of the pages, which the users defines how many columns the table has, the columns order and where to get the data from for each field.
I have written some very bad code in order to keep it dynamic and now I would like it to be more efficient.
My problem is that I don't know how to define the columns I should get back into my IEnumerable on runtime. My main issue is that I don't know how many columns I might have.
I have a reference to a class which gets the field's text. I also have a dictionary of each field's order with the exact property It should get the data from.
My code should look something like that:
var docsRes3 = from d in docs
select new[]
{
for (int i=0; i<numOfCols; i++)
{
gen.getFieldText(d, res.FieldSourceDic[i]);
}
};
where:
docs = List from which I would like to get only specific fields
res.FieldSourceDic = Dictionary in which the key is the order of the column and the value is the property
gen.getFieldText = The function which gets the entity and the property and returns the value
Obviously, it doesn't work.
I also tried
StringBuilder fieldsSB = new StringBuilder();
for (int i = 0; i < numOfCols; i++)
{
string field = "d." + res.FieldSourceDic[i] + ".ToString()";
if (!string.IsNullOrEmpty(fieldsSB.ToString()))
{
fieldsSB.Append(",");
}
fieldsSB.Append(field);
}
var docsRes2 = from d in docs
select new[] { fieldsSB.ToString() };
It also didn't work.
The only thing that worked for me so far was:
List<string[]> docsRes = new List<string[]>();
foreach (NewOriginDocumentManagment d in docs)
{
string[] row = new string[numOfCols];
for (int i = 0; i < numOfCols; i++)
{
row[i] = gen.getFieldText(d, res.FieldSourceDic[i]);
}
docsRes.Add(row);
}
Any idea how can I pass the linq the list of fields and it'll cut the needed data out of it efficiently?
Thanks, Hoe I was clear about what I need....
Try following:
var docsRes3 = from d in docs
select (
from k in res.FieldSourceDic.Keys.Take(numOfCols)
select gen.getFieldText(d, res.FieldSourceDic[k]));
I got my answer with some help from the following link:
http://www.codeproject.com/Questions/141367/Dynamic-Columns-from-List-using-LINQ
First I created a string array of all properties:
//Creats a string of all properties as defined in the XML
//Columns order must be started at 0. No skips are allowed
StringBuilder fieldsSB = new StringBuilder();
for (int i = 0; i < numOfCols; i++)
{
string field = res.FieldSourceDic[i];
if (!string.IsNullOrEmpty(fieldsSB.ToString()))
{
fieldsSB.Append(",");
}
fieldsSB.Append(field);
}
var cols = fieldsSB.ToString().Split(',');
//Gets the data for each row dynamically
var docsRes = docs.Select(d => GetProps(d, cols));
than I created the GetProps function, which is using my own function as described in the question:
private static dynamic GetProps(object d, IEnumerable<string> props)
{
if (d == null)
{
return null;
}
DynamicGridGenerator gen = new DynamicGridGenerator();
List<string> res = new List<string>();
foreach (var p in props)
{
res.Add(gen.getFieldText(d, p));
}
return res;
}

What is the faster way to access a DataTable/DataRowsCollection?

I have a datatable with 100,000+ DataRow. Which method is faster to access the collection?
Is there any faster way to process the rows collection ?
Method 1:
var rows= dsDataSet.Tables["dtTableName"].Rows;
int rowCount = dsDataSet.Tables["dtTableName"].Rows.Count;
for (int c = 0; c < rowCount; c++)
{
var theRow = rows[c];
//process the dataRow
}
Method 2:
for (int c = 0; c < dsDataSet.Tables["dtTableName"].Rows.Count; c++)
{
var theRow = dsDataSet.Tables["dtTableName"].Rows[c];
//process the dataRow
}
It is worth noting the most direct way to access cells is via the DataColumn indexer; the data is actually stored in the columns, not the rows (no: really).
So something like:
var table = dataSet.Tables["dtTableName"];
// HERE: fetch the DataColumn of those you need, for example:
var idCol = table.Columns["Id"];
var nameCol = table.Columns["Name"];
// now loop
foreach(DataRow row in table.Rows)
{
var id = (int)row[idCol];
var name = (string)row[nameCol];
// ...
}
However, frankly if you want the best performance, I would start by saying "don't use DataSet / DataTable". That is actually a very complicated model designed to be all kinds of flexible, with change tracking, rule enforcement, etc. If you want fast, I'd use a POCO and something like "dapper", for example:
public class Foo {
public int Id {get;set;}
public string Name {get;set;}
}
...
string region = "North";
foreach(var row in conn.Query<Foo>("select * from [Foo] where Region = #region",
new { region })) // <=== simple but correct parameterisation
{
// TODO: do something with row.Id and row.Name, which are direct
// properties of the Foo row returned
var id = row.Id;
var name = row.Name;
// ...
}
or even skip the type via dynamic:
string region = "North";
foreach(var row in conn.Query("select * from [Foo] where Region = #region",
new { region })) // ^^^ note no <Foo> here
{
// here "row" is dynamic, but still works; not quite as direct as a
// POCO object, though
int id = row.Id; // <=== note we can't use `var` here or the
string name = row.Name; // variables would themselves be "dynamic"
// ...
}

Resources