Collapsible Tree Hierarchy d3 - csv

It was stated that d3 does not take any specific data fromat whether it be json or csv. But I have noticed some odd behavoirs.
In this example http://bl.ocks.org/mbostock/4339083, the input file is json so
d3.json("/d/4063550/flare.json", function(error, flare) {
root = flare;
root.x0 = height / 2;
root.y0 = 0;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse); //?
update(root);
});
In this example, it is a csv file that is loaded
d3.csv("FederalBudget_2013_a.csv", function(csv) {
var data=[];
//Remove all zero values nodes
csv.forEach(function (d) {
var t=0;
for (var i=0; i < sumFields.length; i++) {
t+= Number(d[sumFields[i]]);
}
if (t > 0) {
data.push(d);
}
})
var nest = d3.nest()
.key(function(d) { return d.Level1; })
.key(function(d) { return d.Level2; })
.key(function(d) { return d.Level3; })
.entries(data);
root={};
root.values=nest;
root.x0 = h / 2;
root.y0 = 0;
var nodes = tree.nodes(root).reverse(); //?
tree.children(function (d){ return d.children;}); //?
update(root);
});
Please clarify as to why there are different approaches where I placed a quesiton mark. I tried to see in the second example the children but that returned nothing.
Thank you.

It's true that d3 can use a wide range of data formats, but the examples you provide are attempting to do something different because of the way that each of the data types provided are configured.
The json file in the first example is configured in a hierarchy that represents the type of data that a tree diagram can use straight away, but the csv data is 'flat' in the sense that it hasn't been formed into an arrangement of parent / child relationships in a tree form.
For instance, the following is a 'flat' data structure where a range of named nodes each has a parent.
name,parent
"Level 2: A","Top Level"
"Top Level", "null"
"Son of A","Level 2: A"
"Daughter of A","Level 2: A"
"Level 2: B","Top Level"
The following is the same data encoded with the relationships expressed in a hierarchy (as the json equivalent).
[
{
"name": "Top Level",
"parent": "null",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A"
},
{
"name": "Daughter of A",
"parent": "Level 2: A"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level"
}
]
}
]
The graphical equivalent is as follows;
It may be useful to have a look over this blog post that explains both type of input and a few other implementations

To clarify in the BrightPoint example, using the d3.nest() function takes the flat CSV data and creates a hierarchy based on the .key values (in this case column names in the flat CSV data.) This is where the children are created.
Ideally your back end service can create JSON formatted data, but often times you have to manipulate the data client side to get it into a digestible format.

Related

Restructuring a large amount of values in a JSON file

I have a JSON file with a large amount of the following values:
"values": [
"Foo": 1,
"Bar": 2,
"Baz": 3,
...
],
How do I efficiently convert this into:
"values": [
{
"name": "Foo",
"value": 1
},
{
"name": "Bar",
"value": 2
},
{
"name": "Baz",
"value": 3
},
...
],
Any help would be appreciated!
Okay, so there are two problems with your input. The first is the fact that the given JSON is invalid, so can't directly be parsed. The square brackets after "values" should be curly brackets, to allow for a hash instead of an array:
let raw_old_data =
// Read the old file
fs.readFileSync('./input_data.json').toString()
// Remove all newlines which could interfere with the regex
.replace(/[\r\n]/g, '')
// Replace the square brackets after `"values"` with curly braces
.replace(/"values": \[(.+?)\]/g, '"values": { $1 }');
To convert this (now valid) string to a JSON object, you use JSON.parse:
let old_data = JSON.parse(raw_old_data);
The second problem is that the format in which the values are stored doesn't match your needs. You want to convert from { key: "value" } to [ name: "key", value: "value" ]. The following function can do that, assuming your version of Node supports ES6 (If not, look at Murillo's answer):
function fix_format(obj) {
// This is where we keep the new items in the correct format
let res = [];
// Loop over all values
Object.keys(obj.values).forEach(name => {
let value = obj.values[name];
// Change the format and add to resulting array
res.push({
// If the variable is the same as the key of the hash, it doesn't have to be specified
name,
value,
});
});
return res;
}
All that's then left to do is loop all data from the old object through that function with the Array.map function:
let new_data = old_data.map(fix_format);
And optionally write it back to a file to use with a different program:
fs.writeFileSync('./formatted_data.json', JSON.stringify(data, null, 2));
Note: The 2 in the JSON.stringify function indicates that the resulting JSON should be padded with 2 spaces, to keep it readable.
With ES6:
Object.keys(values).map(name => ({
name,
value: values[name]
}))
Without ES6:
var keys = Object.keys(values);
var newValues = [];
for(var i = 0; i < keys.length; i++){
newValues.push({
name: keys[i],
value: values[keys[i]]
})
}
If your intention is to use the received data i.e obtain data from DB (e.g MSSql, MySql...) using the connection.query(your_custom_sql_query, (err, rows, fields)
for more info:Node.js MySQL Select From Table
I'll recommend you to use:
const myJson = JSON.stringify(rows[0]);

Grabbing a number from .JSON

I have a long list of data, in the following format:
[
{
"ID": "1234",
"date/time": "2016-07-18 18:21:44",
"source_address": "8011",
"lat": "40.585260",
"lng": "-105.084420",
}
]
And I am creating a script to extract the values out of each line. For example, if a line contains "ID": I want to be able to store the value "1234" into a variable, so I can store it in a different format.
Here is my code to detect "ID":
'use strict';
let lineReader = require('line-reader');
//.JSON variables from input file
let id;
//begin creating new object
console.log('var source = {');
//output the file
lineReader.eachLine('dataOut.json', function (line, last) {
//detect ID, and print it out in the new format
if (id =~ /^id:$/) {
console.log('id: "') + console.log('",');
}
//done
if (last) {
console.log('}');
return false; // stop reading
}
});
Once I detect the ID, I'm not sure how I can obtain the value that follows the "ID" on that line.
How can I store the values on a line, after I detect which line they are on?
Unless your json file is stupidly big, you can just require it and then it's an in memory JS object.
var obj = require('./dataOut.json');
// first element
console.log(obj[0]);

How to remove extra column value from jqgrid json data

Free Jqgrid has actions column. colmodel:
{"hidden":false,"label":"","name":"_actions","width":72
,"align":"left","template":"actions","fixed":false,"resizable":true,
"formatoptions":{"editbutton":true,"delbutton":true,"delOptions":{"url":"Delete" }}},
{"label":"Nimetus","name":"Nimi","index":"Nimi","editoptions":{"maxlength":80,"size":80 }
It is populated from remote json data like
{"total":1,
"page":1,
"rows":[{"id":"2ARVELDUSARV", "cell":[null,"2ARVELDUSARV"]},
{"id":"ACME","cell":[null,"ACME"]},
{"id":"KAKSKOERA","cell":[null,"KAKSKOERA"]}
]
}
In cell array first column is not used.
If this column is removed, jqgrid does not render data correctly since this column presence is required as placeholder for actions column.
How to fix this so that jqgrid will accept data without first column:
{"total":1,
"page":1,
"rows":[{"id":"2ARVELDUSARV", "cell":[null,"2ARVELDUSARV"]},
{"id":"ACME","cell":["ACME"]},
{"id":"KAKSKOERA","cell":["KAKSKOERA"]}
]
}
Update
I looked for data format change as recommended in answer.
jqgrid data is created from sql select statement in ASP.NET MVC4 using code below. Web API serializes this to format for json for jqgrid automatically.
How to create result which can serialized to propertyname: value format recommended in answer ?
object GetDataForJqGrid() {
IDbConnection conn;
using (var dataReader = DataAccessBase.ExecuteReader(sql.ToString(), out conn,
CommandBehavior.CloseConnection | CommandBehavior.SingleResult,
sql.GetParameters.ToArray()))
{
var rowList = new List<GridRow>();
var pkeys = DatabasePrimaryKey();
while (dataReader.Read())
{
var pkv = new List<object>();
int offset = 1; // required for actions column
var row = new GridRow
{
id = IdHelper.EncodeId(pkv),
cell = new object[dataReader.FieldCount + offset + imageCount]
};
for (int j = 0; j < dataReader.FieldCount; j++)
row.cell[offset + j] = dataReader.GetValue(j);
rowList.Add(row);
}
return new
{
total = rowList.Count() < rows ? page : page + 1, page,
rows = rowList
};
}
public class GridRow
{
public string id;
public object[] cell;
}
The most easy way would be to chanege the format of data returned from the server to use repeatitems: false style of the data. I mean the usage of
{
"total": 1,
"page": 1,
"rows": [
{ "id": "2ARVELDUSARV", "Nimi": "2ARVELDUSARV" },
{ "id": "ACME", "Nimi": "ACME" },
{ "id": "KAKSKOERA", "Nimi": "KAKSKOERA"}
]
}
or, after adding key: true to the definition of the column Nimi
{
"total": 1,
"page": 1,
"rows": [
{ "Nimi": "2ARVELDUSARV" },
{ "Nimi": "ACME" },
{ "Nimi": "KAKSKOERA"}
]
}
instead of
{
"total": 1,
"page": 1,
"rows": [{
"id": "2ARVELDUSARV",
"cell": ["2ARVELDUSARV"]
}, {
"id": "ACME",
"cell": ["ACME"]
}, {
"id": "KAKSKOERA",
"cell": ["KAKSKOERA"]
}]
}
Alternatively one can use jsonReader: { repeatitems: false } event with your current format of data and add jsonmap: "cell.0" property to, which means getting the first element (index 0) from the array cell:
$("#list").jqGrid({
datatype: "json",
url: "andrus.json",
colModel: [
{ label: "", name: "_actions", template: "actions" },
{ label: "Nimetus", name: "Nimi", jsonmap: "cell.0" }
],
iconSet: "fontAwesome",
jsonReader: { repeatitems: false }
});
see the demo.
I personally would recommend you don't use your original format (cell with array of values) and use just the named property with additional id property (if id value is not included in the item already). If you would do use the solution with jsonmap you should be carefully with changing the order of the columns (using remapColumns) and later reloading of data. You could required to update jsonmap values after the changing the column order. Thus I repeat that I recommend you to change format of data returned from the server.
UPDATED: The Updated part of your question formulate absolutely new question which have no relation with jqGrid. It's pure C# problem. Nevertheless I try to answer, because I use C# too.
What you can do with minimal changes of your code is the following: You should add using System.Dynamic; and using System.Linq; first of all. Then you should replace the code inside of using (...) {...} to about the following
var rowList = new List<dynamic>();
while (dataReader.Read()) {
var row = new ExpandoObject() as IDictionary<string, object>;
for (int j = 0; j < dataReader.FieldCount; j++) {
if (!dataReader.IsDBNull(j)) {
row.Add(dataReader.GetName(j), dataReader.GetValue(j));
}
}
rowList.Add(row);
}
Serializing of rowList will produce the names properties. If you know the primary key of the data, then you can add id property with the corresponding value in the same way (using row.Add("id", IdHelper.EncodeId(pkv))). I don't included the part because the code which you posted is not full and pkv is currently always new List<object>(), which is wrong. If the data have composed key (multiple value set is unique) then you can make string concatenation of the keys using '_' (underscore) as the separator.

Group bar chart data not working as expected

Have an array set of the type json object.
Var data has an array object like:
0: Object
letter : A
frequency : .08167
1: Object
letter : B
frequency : .01492
2: Object
letter : C
frequency : .02780
3: Object
letter : A
frequency : .06167
4: Object
letter : D
frequency : .02492
5: Object
letter : C
frequency : .03780
The field letter has more than one set of data(for ex: letter A is having two sets of frequency or more). I need to create a group chart for the same.
var frequency= d3.keys(data[0]).filter(function (key) {
return (key !== "letter");
});
data.forEach(function (d) {
d.val = frequency.map(function (name) { return { name: name, value: +d[name] }; });
});
This doesn't seem to work. Is there anything wrong in the structure of Json data?
From the question and comment, it seems like you have your data in a format like this:
data = [
{ "letter": "A", "frequency": .08167, "year": 2015},
{ "letter": "B", "frequency": .01492, "year": 2015},
{ "letter": "C", "frequency": .02780, "year": 2015}...
In which case, the best way to group by letter for your grouped bar chart would be to use d3.nest:
var nested_data = d3.nest()
.key(function (d) {
return d.letter;
})
.entries(data);
which will structure your data with multiple values under each letter key.
Here's a working fiddle with your data: http://jsfiddle.net/henbox/jc0nohhb/1/, that borrows a lot from this example: http://bl.ocks.org/mbostock/3887051
Note - This is a good resource to read more about nest in d3: http://bl.ocks.org/phoebebright/raw/3176159/

Cannot bind JSON to d3

I've search far and wide, but cannot bind JSON data to a simple scatterplot for the life of me. I've looked at posts and examples, but I can only manage to bind arrays and not JSON. Below, I've tried to simply display JSON data as text and still can't make it work. Please let me know if you have any idea why!
d3_attempt.js
var data;
d3.json("json_data.json",function(error, dataset) {
if (error) return console.warn(error);
data = dataset;
var myscatter = d3.select("#somediv").append("svg")
.attr("width", 700)
.attr("height", 400);
myscatter.selectAll("text")
.data(data.data)
.enter()
.append("text")
.text(function(d){return d)})
});
json_data.json
{
"data":
{
"john": {"name": "john", "age": "13"},
"matt": {"name": "matt", "age":"14"}
}
}
Yup, you can only bind arrays, so you probably want to convert your data to an array:
myscatter.selectAll("text")
.data(d3.values(data.data));
This will give each of your text nodes the { name, age } object as data. If you need the keys too (looks like it's not required in this case), you could use d3.entries, which gives you an array of objects like { key: "john", value: { name: "John", age: "13" }}.