I'd like to visualise data in a "multiBarHorizontalChart" in NVD3 and read the data from a .csv file.
I'm struggling to get the data in the right format. According to NVD3.org the format for multiBarHorizontalChart has to be:
[
{
"key": "Series 1",
"color": "#d67777",
"values": [
{
"label" : "Group A" ,
"value" : -1.8746444827653
} ,
{
"label" : "Group B" ,
"value" : -8.0961543492239
}
]
},
The code that I'm using is below. Could someone tell me what I'm doing wrong?
d3.csv("File.csv", function (error, csv) {
if (error) return console.log("there was an error loading the csv: " + error);
console.log("there are " + csv.length + " elements in my csv set");
var mmm = ["pre_ineq","post_ineq"];
for (var i = 0; i < mmm.length; i++) {
myall[i].values.label = csv.map(function(d) { return [ d["label"] ]; });
myall[i].values.value = csv.map(function(d) { return [ +d[mmm[i]] ]; });
//or? myall[i].values = csv.map(function(d) { return [ label=d["label"], +d[mmm[i]] ]; });
};
var chart;
nv.addGraph(function() {
var chart = nv.models.multiBarHorizontalChart()
.x(function(d) { return d.label })
.y(function(d) { return d.value })
.margin({top: 30, right: 20, bottom: 50, left: 175})
.showValues(true)
.tooltips(false)
.showControls(false);
chart.yAxis
.tickFormat(d3.format(',.2f'));
d3.select('#chart1')
.datum(myall)
.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
});
Thanks a lot!
Guess you should do something like
for (var i = 0; i < mmm.length; i++) {
myall[i].values = csv.map(function(d) {
return { "label": d["label"], "value": +d[mmm[i]] };
});
};
But it's not tested.
Related
I'd like to have tooling to perform certain validations on JSON. Explanation with examples:
Given JSON fragment:
{
"optionsMinValue": 0
"optionsMaxValue": 56
"options": [
{
"name": "name1",
"value": 0
},
{
"name": "name2",
"value": 1
},
{
"name": "name3",
"value": 56
}
]
}
Validation examples:
Given the fragment above, the validation of optionsMaxValue should
pass.
Given the fragment above, if optionsMaxValue is changed to 55, then
the validation should fail.
Added bonus validation:
Check whether an item is included in the options array for every integer between optionsMinValue and optionsMaxValue. In other words, in the given fragment the array should contain 57 items with an item for each value from 0 to 56.
Existing tooling:
Does tooling exist that can be used relatively easily to perform these sorts of checks?
First thought is that something like json-schema validation could be done. It has been a few years since I looked at that as an option, so my hope is that tooling has emerged that is a homerun on this.
Ajv JSON schema validator - github link
const schema = {
type: "object",
properties: {
name: {type: "string"},
value: {type: "number", minimum: 0, maximum: 55},
},
required: ["name", "value"],
additionalProperties: false,
}
const option = {
"name": "name1",
"value": 0
},
const validate = ajv.compile(schema)
const valid = validate(data)
if (!valid) console.log(validate.errors)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/4.4.0/ajv.min.js"></script>
Joi package is best for these kind of validations
following Joi schema can be used to solve your requirement
Joi.object({
optionsMinValue: Joi.number().min(0).max(30).required(),
optionsMaxValue: Joi.number().min(56).max(100).required(),
options: Joi.array().items(
Joi.object({
name: Joi.string().required(),
value: Joi.number().min(0).max(56).required(),
})
),
});
Following is a sample code that works for your scenario
const inputData = {
optionsMinValue: 0,
optionsMaxValue: 56,
options: [
{
name: "name1",
value: 0,
},
{
name: "name2",
value: 1,
},
{
name: "name3",
value: 56,
},
],
};
const Joi = joi; // for node.js use - const Joi = require("joi");
// Schema for validation
const schema = Joi.object({
optionsMinValue: Joi.number().min(0).max(30).required(),
optionsMaxValue: Joi.number().min(56).max(100).required(),
options: Joi.array().items(
Joi.object({
name: Joi.string().required(),
value: Joi.number().min(0).max(56).required(),
})
),
});
const runValidation = (schema, inputData) => {
const validationResult = Joi.compile(schema)
.prefs({ errors: { label: "key" }, abortEarly: false })
.validate(inputData);
if (validationResult.error) {
// Validation failed
console.log("Error, validation failed");
// Set error message to string
const errorMessage = validationResult.error.details
.map((details) => details.message)
.join(", ");
console.log("failure reason - ", errorMessage);
return;
}
console.log("validation passed");
};
runValidation(schema, inputData);
<script src="https://cdn.jsdelivr.net/npm/joi#17.6.0/dist/joi-browser.min.js"></script>
Even if you use an existing tool, you should write validation rules for that tool. Since you are not an expert in any of these tools, it may be easier to write a few lines of code in your preferred language. For example, in JavaScript it might look like this:
function validateJson(jsonToValidate, maxValue = 56) {
if (jsonToValidate.optionsMaxValue !== maxValue) {
console.log("Failure on optionsMaxValue.");
return false;
}
if (jsonToValidate.options.length !== maxValue+1) {
console.log("Incorrect number of items.");
return false;
}
let values = jsonToValidate.options.map(a => a.value).sort();
if (values[0] !== 0 || values[maxValue] !== maxValue) {
console.log("Values out of desired sequence.");
return false;
}
let sum = values.reduce((a, b) => a + b, 0);
if (sum !== maxValue * (maxValue + 1) / 2) {
console.log("Values out of desired sequence.");
return false;
}
console.log("Validation PASSED.");
return true;
}
Let's try with truncated json object:
let jsonSample = {
"optionsMinValue": 0,
"optionsMaxValue": 2,
"options": [{
"name": "name1",
"value": 0
},
{
"name": "name2",
"value": 1
},
{
"name": "name3",
"value": 2
}
]
};
function validateJson(jsonToValidate, maxValue = 56) {
if (jsonToValidate.optionsMaxValue !== maxValue) {
console.log("Failure on optionsMaxValue.");
return false;
}
if (jsonToValidate.options.length !== maxValue+1) {
console.log("Incorrect number of items.");
return false;
}
let values = jsonToValidate.options.map(a => a.value).sort();
if (values[0] !== 0 || values[maxValue] !== maxValue) {
console.log("Values out of desired sequence.");
return false;
}
let sum = values.reduce((a, b) => a + b, 0);
if (sum !== maxValue * (maxValue + 1) / 2) {
console.log("Values out of desired sequence.");
return false;
}
console.log("Validation PASSED.");
return true;
}
validateJson(jsonSample, 2);
I would like to display the number of tasks elements in JSON, but I do not know how to go about it.
I want to make something like this:
Tasks to do 2/12 (where 2 - tasks with flag 1, 12 - all tasks)
I tried using the lenght function, but I got the information function lenght is not defined, similarly with the slice function.
[
{
"id":1,
"clients_id":1,
"products_id":1,
"tasks_id":1,
"project_name":"Some project",
"created_at":null,
"updated_at":null,
"clients":{
"id":1,
"client_name":"Some client",
"contact_name":"Some client",
"client_phone":"123123123",
"client_mail":"clientmail#mailclient.com",
"client_nip":"1112223333",
"client_logo":"logo.jpg",
"updated_at":"2019-04-11 09:45:11",
"created_at":"-0001-11-30 00:00:00"
},
"products":{
"id":1,
"product_name":"Some product",
"product_description":"Really nice product bro",
"product_price":"999$",
"updated_at":"2019-04-08 14:35:13",
"created_at":null
},
"tasks":[
{
"id":1,
"project_id":1,
"task_name":"First task",
"task_description":"its very hard task",
"task_due":"2099-01-12 00:00:00",
"status":0,
"created_at":null,
"updated_at":"2019-04-11 14:09:08"
},
{
"id":2,
"project_id":1,
"task_name":"fix task 1",
"task_description":"or something else",
"task_due":"2201-01-12 00:00:00",
"status":1,
"created_at":null,
"updated_at":"2019-04-11 14:10:11"
}
]
}]
<script>
export default {
mounted() {
let app = this;
let id = app.$route.params.id;
app.id = id;
axios.get('/api/v1/projects/' + id)
.then(function (resp) {
app.project = resp.data;
})
.catch(function () {
alert("Could not load your projects")
});
},
data: function () {
return {
//client_id: null,
project: {
id: '',
clients_id: '',
products_id: '',
tasks_id: '',
project_name: '',
updated_at: '',
created_at: '',
clients: ''
},
task: {
status: ''
}
//client: []
}
},
methods: {
saveForm() {
var app = this;
var newproject = app.project;
axios.patch('/api/v1/projects/' + app.id, newproject)
.then(function (resp) {
app.$router.replace('/c/');
})
.catch(function (resp) {
console.log(resp);
alert("Could not create your company");
});
},
taskDone(taskid, projectid){
var app = this;
{{app}};
var newtask = app.task;
var flag = 1;
axios.patch('/api/v1/tasks/' + taskid + '?status='+flag)
.then(function (resp) {
app.$router.push('/pr/view/' + projectid);
location.reload();
})
.catch(function (resp) {
console.log(resp);
alert("Could not create your company");
});
},
taskUnDone(taskid, projectid){
var app = this;
{{app}};
var newtask = app.task;
var flag = 0;
axios.patch('/api/v1/tasks/' + taskid + '?status='+flag)
.then(function (resp) {
app.$router.push('/pr/view/' + projectid);
location.reload();
})
.catch(function (resp) {
console.log(resp);
alert("Could not create your company");
});
}
}
}
</script>
You could create a computed function that returns the length of tasks filtered by status of 1.
computed() {
status() {
const tasks = this.project.tasks;
const complete = tasks.filter(task => task.status === 1);
return `${complete.length}/${tasks.length}`;
}
}
Then use status as a "variable" in your markup.
<p>Tasks done: {{ status }}</p>
I want to build force-directed graph using d3. Each nodes of the graph is a character from Marvel comics (e.g. Spider-man). If the characters appear in one comic there should be a link between the nodes which represent them.
I have a characters_by_comics.json file which contains array of object the following type:
{
"comic_id": 2,
"characters": [
1009220,
1010776,
1009378,
1010363,
1009215,
1009471,
1009718
]
},
I also have characters.json which contains all info about each character including their character_id. In addition, comics.json contains the title of every comic and its comic_id.
The graph is going to be quiet huge. That will affect user experience in a negative way. So, to prevent this I added some information about each character, such as race, gender, alive, etc. Using this information then I am planning to:
Create different filters that can be applied to the graph. For example, if I apply only female filter, only the nodes representing female characters should be visible
Create different types of links: not only appear in one comic, but also belongs to one team, for example.
My question is how can I transform the data I have so it is easy to create nodes and links from it using d3?
You don't include a snippet of characters.json, but that is your node data. Sounds like it might already formatted correctly. It would need to be something like this:
var characters = [
{
id: 1009220,
name: 'Ms Marvel'
}, {
id: 1010776,
name: 'Spiderman'
}
....
];
Now the real question becomes, how do we get your link data from your characters_by_comics.json? Assuming a structure like this:
var charactersByComic = [{
"comic_id": 2,
"characters": [
1009221,
1010776,
...
]
}, {
"comic_id": 3,
"characters": [
1009221,
1009220,
1009379,
...
]
}];
You could do it like:
var linkData = {}; // object to hold data
charactersByComic.forEach(d0 => { // loop the master data
d0.characters.forEach(d1 => { // outer loop of characters
d0.characters.forEach(d2 => { // inner loop of characters
var key = d1 + "|" + d2; // create unique key to prevent duplicate relationshipts
if (d1 !== d2 && !linkData[key]) { // if its not the same character, and they don't already have a relationship
linkData[key] = { // add it
source: d1,
target: d2
};
}
});
});
});
linkData = Object.values(linkData); // take just the array of relationships
This would produce our desired link structure:
[
{"source":1009221,"target":1010776},
{"source":1009221,"target":1009378},
...
]
Now we can jam this whole thing into a force directed graph:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="200" height="200"></svg>
<script>
var characters = [{
id: 1009220,
name: 'Ms Marvel'
}, {
id: 1010776,
name: 'Spiderman'
}, {
id: 1009378
}, {
id: 1010363
}, {
id: 1009215
}, {
id: 1009471
}, {
id: 1009718
}, {
id: 1009221
}, {
id: 1010777
}, {
id: 1009379
}, {
id: 1010361
}, {
id: 1009212
}, {
id: 1009474
}, {
id: 1009715
}];
var charactersByComic = [{
"comic_id": 2,
"characters": [
1009221,
1010776,
1009378,
1010363,
1009215,
1009471,
1009718,
1010777
]
}, {
"comic_id": 3,
"characters": [
1009221,
1009220,
1009379,
1010361,
1009212,
1009474,
1009715,
1010777
]
}];
var linkData = {};
charactersByComic.forEach(d0 => {
d0.characters.forEach(d1 => {
d0.characters.forEach(d2 => {
var key = d1 + "|" + d2;
if (d1 !== d2 && !linkData[key]) {
linkData[key] = {
source: d1,
target: d2
};
}
});
});
});
linkData = Object.values(linkData);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
console.log(d)
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(linkData)
.enter().append("line")
.attr("stroke-width", 1);
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(characters)
.enter()
.append("circle")
.attr("r", 5)
.attr("fill", "steelblue");
node.append("title")
.text(function(d) { return d.id; });
simulation
.nodes(characters)
.on("tick", ticked);
simulation.force("link")
.links(linkData);
console.log(simulation)
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
</script>
I am trying to sort through some data regarding seat vacancies using nodejs. The response from the API is like this
{
"data":[
{
"date":"2016-10-01",
"timeslots":[
{
"from_time":"10:00",
"to_time":"18:00",
"vacancies":2400
},
{
"from_time":"18:00",
"to_time":"21:00",
"vacancies":900
}
]
},
{
"date":"2016-10-02",
"timeslots":[
{
"from_time":"10:00",
"to_time":"18:00",
"vacancies":2400
},
{
"from_time":"18:00",
"to_time":"21:00",
"vacancies":900
}
]
},
{
"date":"2016-10-03",
"timeslots":[
{
"from_time":"10:00",
"to_time":"18:00",
"vacancies":2400
},
{
"from_time":"18:00",
"to_time":"21:00",
"vacancies":900
}
]
},
{
"date":"2016-10-04",
"timeslots":[
{
"from_time":"10:00",
"to_time":"18:00",
"vacancies":2400
},
{
"from_time":"18:00",
"to_time":"21:00",
"vacancies":900
}
]
},
{
"date":"2016-10-05",
"timeslots":[
{
"from_time":"10:00",
"to_time":"18:00",
"vacancies":2400
},
{
"from_time":"18:00",
"to_time":"21:00",
"vacancies":900
}
]
}
]
}
And I am trying to process this using the code below
app.get('/fetchdata/:ticketid/:fromdate/:todate', function (req, res){
var id = req.params.ticketid;
var fromdate = req.params.fromdate;
var todate = req.params.fromdate;
var key = id+fromdate+todate;
var username = 'foo';
var password = 'foobar';
var slot_url = "https://"+username+":"+password+"#boo.boobar.com/1/timeslots?productId="+id+"&fromDate="+fromdate+"&toDate="+todate;
var result = {};
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
request({
url: slot_url,
json: true,
headers: headers
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
var data = [];
try {
body.forEach(function (entry) {
var temp = {};
temp['date'] = entry["date"];
data = data.concat(temp);
});
result['data'] = data;
result['response'] = 1;
result['message'] = 'Capacity list fetched successfully!';
//client.set(key, JSON.stringify(data));
res.json(result);
}
catch (err) {
result['response'] = 0;
result['message'] = 'No data found!';
result['exception'] = err.message;
res.json(result);
}
}
else
{
console.log("Something went wrong!! "+error.message);
}
})
});
Now I keep getting the response {"response":0,"message":"No data found!","exception":"Object #<Object> has no method 'forEach'"}
I understand that this is because the timeslots key inside the data key is an object but I am unable to parse this. What changes should be done to my code to be able to parse this content.
My ultimate objective is to sort out this data according to the dates and create a json like this
{
"2016-10-01":[
{
"from_time":"10:00",
"to_time":"10:30",
"capacity":-147
},
{
"from_time":"10:30",
"to_time":"11:00",
"capacity":1
}
]
}
body is the root object, so it looks like you're wanting to iterate over body.data instead (e.g. body.data.forEach(...)), which is an array.
I have this json:
{
"info": [
{
"id": 999,
"products": [
{
"id": 1,
},
{
"id": 2,
}
]
}
]
}
Info
-- products
-----id
And my factory:
AppAngular.factory('model', ['$http', function ($http) {
return {
load: function (scope) {
$http.get('mydomain/api').success(function (data) {
var myObject = {};
angular.extend(myObject,data);
for (var i = 0; i < myObject.info.length; i++) {
for (var j = 0; j < myObject.info[i].products.length; j++) {
myObject.info[i].products[j].selected = true;
myObject.info[i].products[j].quantity = 1;
.....
}
}
}
});
}
};
} ]);
Theres is a way do add some property after angular.extend, without using a lot of for? We need do add some properties or behavior to the object after make json get.
For exemple:
Info
-- products
-----id
-----selected
-----quantity
You could use angular.forEach:
AppAngular.factory('model', ['$http', function ($http) {
return {
load: function (scope) {
$http.get('mydomain/api').success(function (data) {
var myObject = {};
angular.extend(myObject,data);
angular.forEach(myObject.info, function (info) {
angular.forEach(info.products, function (product) {
product.selected = true;
product.quantity = 1;
.....
});
});
});
}
};
}]);