Group bar chart data not working as expected - json

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/

Related

Parsing JSON error cannot read property 'url' of undefined

I have been trying to parse JSON, which have 3 different set of data where one element have various number of children and sometimes none. I am getting an error when there is no children present or only one present. I declared the JSON as var data.
JSON A
{
"floorplan": [
{
"title": "plan1",
"url": "https://media.plan1.pdf"
},
{
"title": "plan2",
"url": "https://media.plan2.pdf"
}
]
}
JSON B
{"floorplan": []}
JSON C
{
"floorplan": [
{
"title": "plan1",
"url": "https://media.plan1.pdf"
}
]
}
I parsed the JSON like this:
var items = JSON.parse(data);
return {
floorplan1: items.floorplan[0].url;
floorplan2: items.floorplan[1].url;
}
But, it only returned data for the JSON A, for other 2 it gave TypeError: Cannot read property 'url' of undefined.
I modified the code to check if floorplan have at least one child and then parse data.
var items = JSON.parse(data);
var plan = items.floorplan[0];
if(plan){
return {
floorplan1: items.floorplan[0].url;
floorplan2: items.floorplan[1].url;
}
}
The new code returned data for JSON A and B(as empty row), but gave error for C. C have one child still it got the error.
I also tried this code, still got the error for JSON C.
var items = JSON.parse(data);
var plan = items.floorplan[0];
var plan1;
var plan2;
if(plan){
plan1 = items.floorplan[0].url;
plan2 = items.floorplan[1].url;
}
return{
floorplan1 : plan1 ? plan1 : null;
floorplan2 : plan2 ? plan2 : null;
}
Is there any method I can try to get data returned for all 3 types of JSON?
let data = `
[{"floorplan": [{
"title": "plan1",
"url": "https://media.plan1.pdf"
}, {
"title": "plan2",
"url": "https://media.plan2.pdf"
}]},
{"floorplan": []},
{"floorplan": [{
"title": "plan1",
"url": "https://media.plan1.pdf"
}]}]`;
let json = JSON.parse(data);
//console.log(json);
json.forEach(items=>{
//console.log(items);
let o = {
floorplan1: items.floorplan.length > 0 ? items.floorplan[0].url : '',
floorplan2: items.floorplan.length > 1 ? items.floorplan[1].url : ''
};
console.log(o);
o = {
floorplan1: (items.floorplan[0] || {'url':''}).url,
floorplan2: (items.floorplan[1] || {'url':''}).url
};
console.log(o);
o = {
floorplan1: items.floorplan[0]?.url,
floorplan2: items.floorplan[1]?.url
};
console.log(o);
const {floorplan: [one = {url:''}, two = {url:''}]} = items;
o = {
floorplan1: one.url,
floorplan2: two.url
};
console.log(o);
});
Sure. A few ways, and more than I have here. I have put all the raw data into one string, parsed it into json and then iterated through that. In each loop my variable items will correspond to one of the json variables you created and referenced in your question as items.
In the first example, I check to make sure that items.floorplan has at least enough elements to contain the url I'm trying to reference, then use the ternary operator ? to output that URL if it exists or an empty string if it doesn't.
In the second example, I use the || (OR) operator to return the first object that evaluates to true. If items.floorplan[x] exists, then it will be that node, and if it doesn't I provide a default object with an empty url property on the right hand side, and then just use the url from the resulting object.
In the third, I use the optional chaining operator that was introduced in 2020. This method will return undefined if the url doesn't exist.
In the fourth example, I use destructuring to pull values out of the items variable, and make sure that there is a default value for url in case the items variable doesn't have a corresponding value.
But there are many more ways to go about it. These are just a few, and you can't necessarily say which approach is better. It's dependent on your intent and environment. With the exception of optional chaining (which shows undefined if the property doesn't exist), you can see these produce the same results.
DOCS for optional chaining: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
DOCS for destructuring: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
An article on destructuring: https://javascript.info/destructuring-assignment

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]);

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.

sorting json arrays in json array using angular js

I have json array in the following format,
$scope.data = [{
"values" : [["2 Day", 103.89], ["NextDay", 107.41], ["Ground", 428.75]],
"key" : "FedEx"
}, {
"values" : [["Ground", 117.8], ["NextDay", 10], ["2 Day", 15]],
"key" : "UPS"
}]
I need to sort it in to the following format :
$scope.data = [{
"values" : [["2 Day", 103.89], ["NextDay", 107.41], ["Ground", 428.75]],
"key" : "FedEx"
}, {
"values" : [["2 Day", 15], ["NextDay", 10], ["Ground", 117.8]],
"key" : "UPS"
}]
How can I do it using Angularjs?
A similar data set for which I want similar sorting to be applied, but here I have time (in long format) instead strings.
$scope.data1 = [{
"values" : [[1359072000000, 103.89], [1365116400000, 107.41], [1357516800000, 428.75]],
"key" : "FedEx"
}, {
"values" : [[1357516800000, 117.8], [1359072000000, 100], [1365116400000, 15]],
"key" : "UPS"
}];
To be formatted as
$scope.data1 = [{
"values" : [[1359072000000, 103.89], [1365116400000, 107.41], [1357516800000, 428.75]],
"key" : "FedEx"
}, {
"values" : [[1359072000000, 100],[1365116400000, 15], [1357516800000, 117.8], ],
"key" : "UPS"
}];
Natural sorting can be applied in js like this. Natural sorting is required since strings in your array contains numbers.
function strcmp(a, b) {
return a > b ? 1 : a < b ? -1 : 0;
}
function natcmp(a, b) {
var x = [], y = [];
a[0].replace(/(\d+)|(\D+)/g, function($0, $1, $2) { x.push([$1 || 0, $2]) })
b[0].replace(/(\d+)|(\D+)/g, function($0, $1, $2) { y.push([$1 || 0, $2]) })
while(x.length && y.length) {
var xx = x.shift();
var yy = y.shift();
var nn = (yy[0]-xx[0]) || strcmp(yy[1],xx[1]);
if(nn) return nn;
}
if(x.length) return -1;
if(y.length) return +1;
return 0;
}
Apply sorting in your array using javascript sort function as shown below.
$scope.data = $scope.data.map(function(d){ d.values = d.values.sort(natcmp); return d; });
Natural sorting is not needed for the second dataset. To sort the array in descending order by time, try this.
$scope.data1 = $scope.data1.map(function(d) {
d.values = d.values.sort(function(a, b) {
return new Date(b[0]) - new Date(a[0])
});
return d;
});
for displaying the array with some ng-repeat, you could use a filter
https://docs.angularjs.org/api/ng/filter/filter
There is orderBy filter but you can create your own filters.
for the data model to be sorted and not only for presentation, you could use the javascript sort function for arrays and give it a sorting implementation which compares 2 elements.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

Collapsible Tree Hierarchy d3

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.