I'm rendering a force directed graph using d3js. The design is such that nodes are added to the graph at run time by querying a database. This works fine.
While the graph is being rendered, node-by-node, if I happen to click anywhere on the screen, at sometimes, the incoming node gets stuck at the top left of the div and the following error is displayed on debugging:
Error: Invalid value for <line> attribute x2="NaN"
Error: Invalid value for <g> attribute transform="translate(NaN,NaN)"
Sometimes, even an already formed link breaks and an old node gets stuck on the top left until the next refresh.
If I don't interfere, the rendering completes smoothly.
I really do not understand this behavior. Does anyone have any idea what must be going wrong here?
Find my code to compute the graph below:
var graph = { "nodes": [], "links": [] };
var nodeMap = [];
var timestamp;
var json_obj;
var newLogs = false;
$(document).ready(function () {
var count = 0;
doPoll();
function doPoll() {
json_obj = "some object";
var uri = 'api/values?id=' + json_obj;
newLogs = false;
$.getJSON(uri)
.done(function doPoll(data) {
$.each(data, function (key, item) {
newLogs = true;
timestamp = item.timestamp;
populateJson();
function populateJson() {
if (nameExistsInNodes(item.entityName) && item.state == "1") {
searchAndRemoveFromGraph(item.entityName);
}
if (item.entityId != null && !idExistsInNodes(item.entityId)) {
graph.nodes.push({
"eId": item.entityId,
"name": item.entityName,
"state": item.state
});
} else if (item.state == "0" && item.entityId == null) {
graph.nodes.push({
"eId": item.entityName,
"name": item.entityName,
"state": item.state
});
} else if (idExistsInNodes(item.entityId)) {
updateEntityInfo(item);
}
if (!existsInMap(item.entityId) && item.entityId != null) {
nodeMap.push({ "id": item.entityId, "index": count++ });
}
if (item.mapsTo != null && !existsInLinks(findIndexForId(item.entityId), findIndexForId(item.mapsTo))
&& findIndexForId(item.entityId) != null && findIndexForId(item.mapsTo) != null) {
graph.links.push({
"source": findIndexForId(item.entityId),
"target": findIndexForId(item.mapsTo)
});
}
}
});
if (newLogs) {
renderUsingD3(graph);
}
}) // end done
.always(function () { setTimeout(doPoll, 3000) })
} // end doPoll
});
Each item that comes in has an id, name and state and my logic plays around with these 3 to get the graph up.
Related
Is there a way to remove literally all null or empty string values from an object? We have an aggregation which creates an object with empty fields and empty objects should the value be null.
What we wish to do is remove all null properties and empty objects and recreate the object, in order to keep the data as small as possible.
e.g. in the following object, only 'test' and 'more-nested-data' should be taken into account, the rest can be removed
{
"test": "some",
"test2": {
},
"test3": {
"some-key": {
},
"some-other-key": {
"more-nested-data": true,
"more-nested-emtpy": null
}
}
}
which should become:
{
"test": "some",
"test3": {
"some-other-key": {
"more-nested-data": true
}
}
}
I tried a lot, but I think by using objectToArray that something could be done, but I have not found the solution yet. The required aggregation should need to recursively (or by defined levels) remove null properties and empty objects.
Use the $function operator available in 4.4 (Aug 2021) to do this recursively as you note. Given this input which is a slightly expanded version of that supplied in the question:
var dd = {
"test": "some",
"test2": { },
"test3": {
"some-key": { },
"some-other-key": {
"more-nested-data": true,
"more-nested-emtpy": null,
"emptyArr": [],
"notEmptyArr": [
"XXX",
null,
{"corn":"dog"},
{"bad":null},
{"other": {zip:null, empty:[], zap:"notNull"}}
]
}
}
}
db.foo.insert(dd);
then this pipeline:
db.foo.aggregate([
{$replaceRoot: {newRoot: {$function: {
body: function(obj) {
var process = function(holder, spot, value) {
var remove_it = false;
// test FIRST since [] instanceof Object is true!
if(Array.isArray(value)) {
// walk BACKWARDS due to potential splice() later
// that will change the length...
for(var jj = value.length - 1; jj >= 0; jj--) {
process(value, jj, value[jj]);
}
if(0 == value.length) {
remove_it = true;
}
} else if(value instanceof Object) {
walkObj(value);
if(0 == Object.keys(value).length) {
remove_it = true;
}
} else {
if(null == value) {
remove_it = true;
}
}
if(remove_it) {
if(Array.isArray(holder)) {
holder.splice(spot,1); // snip out the val
} else if(holder instanceof Object) {
delete holder[spot];
}
}
};
var walkObj = function(obj) {
Object.keys(obj).forEach(function(k) {
process(obj, k, obj[k]);
});
}
walkObj(obj); // entry point!
return obj;
},
args: [ "$$CURRENT" ],
lang: "js"
}}
}}
]);
produces this result:
{
"_id" : 0,
"test" : "some",
"test3" : {
"some-other-key" : {
"more-nested-data" : true,
"notEmptyArr" : [
"XXX",
{
"corn" : "dog"
},
{
"other" : {
"zap" : "notNull"
}
}
]
}
}
}
A convenient way to debug such complex functions is by declaring them as variables outside of the pipeline and running data through them to simulate the documents (objects) coming out the database, e.g.:
ff = function(obj) {
var process = function(holder, spot, value) {
var remove_it = false;
// test FIRST since [] instanceof Object is true!
if(Array.isArray(value)) {
...
printjson(ff(dd)); // use the same doc as above
You can put print and other debugging aids into the code and then when you are done, you can remove them and call the pipeline to process the real data as follows:
db.foo.aggregate([
{$replaceRoot: {newRoot: {$function: {
body: ff, // substitute here!
args: [ "$$CURRENT" ],
lang: "js"
}}
}}
]);
Sounds like the unwind operator would help. Checkout the unwind operator at https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/
I am trying to use mCustomScrollbar on jqgrid( 4.9.2). The design of the scrollbar is getting changed but when scrolling horizontally the top headers does not move as they do normally.
The example I am trying to work on is of collapsable grid.
and for the mCustomScroll
$(".ui-jqgrid-bdiv").mCustomScrollbar({
axis:"yx",
});
Is it not possible at all to use any custom scroll bar on jqgrid ?
I had made some customized changes to jqgr so migrating to the other version will be a tough task, so Instead, I made the changes in the mcustomscrollbar and posting the answer so if any one else come across the same problem it will be beneficial.
so there is a methods _tweetTo, which is called for the container on which the scrollbar is assigned by the initializing as
$(".ui-jqgrid-bdiv").mCustomScrollbar({
axis:"yx",
});
now just after the method call _tweenTo(line# 2131 for V:3.1.5) insert the following code
if ($(".ui-jqgrid-hdiv").length > 0) {
$(".ui-jqgrid-view").css("overflow", "hidden");
$(".ui-jqgrid-hdiv").css("width", $("#grid1").width() + "px"); // grid1 is the id of your gridcontainer/table
_tweenTo($(".ui-jqgrid-hdiv")[0], property, Math.round(scrollTo[0]), dur[0], options.scrollEasing, options.overwrite, {
onStart: function () {
if (options.callbacks && options.onStart && !d.tweenRunning) {
/* callbacks: onScrollStart */
if (_cb("onScrollStart")) { _mcs(); o.callbacks.onScrollStart.call(el[0]); }
d.tweenRunning = true;
_onDragClasses(mCSB_dragger);
d.cbOffsets = _cbOffsets();
}
}, onUpdate: function () {
if (options.callbacks && options.onUpdate) {
/* callbacks: whileScrolling */
if (_cb("whileScrolling")) { _mcs(); o.callbacks.whileScrolling.call(el[0]); }
}
}, onComplete: function () {
if (options.callbacks && options.onComplete) {
if (o.axis === "yx") { clearTimeout(mCSB_container[0].onCompleteTimeout); }
var t = mCSB_container[0].idleTimer || 0;
mCSB_container[0].onCompleteTimeout = setTimeout(function () {
/* callbacks: onScroll, onTotalScroll, onTotalScrollBack */
if (_cb("onScroll")) { _mcs(); o.callbacks.onScroll.call(el[0]); }
if (_cb("onTotalScroll") && scrollTo[1] >= limit[1] - totalScrollOffset && d.cbOffsets[0]) { _mcs(); o.callbacks.onTotalScroll.call(el[0]); }
if (_cb("onTotalScrollBack") && scrollTo[1] <= totalScrollBackOffset && d.cbOffsets[1]) { _mcs(); o.callbacks.onTotalScrollBack.call(el[0]); }
d.tweenRunning = false;
mCSB_container[0].idleTimer = 0;
_onDragClasses(mCSB_dragger, "hide");
}, t);
}
}
});
}
and in the method definition of _tweenTo there is another method _tween
update that method as
function _tween() {
// added condition so the top headers remains fixed
if (el.classList.contains("ui-jqgrid-hdiv") && prop == "top") {
return;
}
//ends here
if (duration > 0) {
tobj.currVal = _ease(tobj.time, from, diff, duration, easing);
elStyle[prop] = Math.round(tobj.currVal) + "px";
} else {
elStyle[prop] = to + "px";
}
onUpdate.call();
}
and the scrollbar is up and running.. !!
I have a piechart I'm displaying using the following dimension -
var types = facts.dimension(function (d) {
if (d.types === 2)
return "Type 2";
else if (d.types === 3)
return "Type 3";
else
return "Other";
});
I would like to not return, ignore, all other types, so the pie chart would just display Type 2 and Type 3. I cannot seem to get this working though I'm sure it's simple. Can I do something within that dimension or do I need to filter before?
Thanks for any help.
Have you tried creating a new type and then creating a dimension on top of that?
facts.forEach(function(d) {
if (d.types == 2) {
d._type = "Type 2";
} else if (d.types == 3) {
d._type = "Type 3";
} else {
d._type = "Other";
}
});
var types = facts.dimension(dc.pluck('_type'))
You need to (1) Filter your data then (2) remove it from the bin. Run your data through ensure_group_bins(myGroup) you'll get the chart you're after
function remove_empty_bins(source_group) {
return {
all:function () {
return source_group.all().filter(function(d) {
return d.key != "unwanted" ;
});
}
};}
function ensure_group_bins(source_group, bins) {
return {
all:function () {
var result = source_group.all().slice(0), // copy original results (we mustn't modify them)
found = {};
result.forEach(function(d) {
found[d.key] = true;
});
bins.forEach(function(d) {
if(!found[d])
result.push({key: d, value: 0});
});
return result;
}
};};
dc.js Github
I am having problem in my custom sorting method. I have a json structure wherein integer values are being passed as String. While sorting I need to sort the list based on the integer value, but it is being sorted as String values.
I have created a JsFiddle for the same:
http://jsfiddle.net/ayan_2587/vjF2D/14/
The angular code is as follows:-
var app = angular.module('myModule', []);
app.controller('ContactListCtrl', function ($scope, $timeout, $filter) {
var sortingOrder = 'price';
$scope.sortingOrder = sortingOrder;
$scope.sortorder = '-sort';
$scope.contacts = [{
"name": "Richard",
"surname": "Stallman",
"price": "7200"
}, {
"name": "Donald",
"surname": "Knuth",
"price": "34565"
}, {
"name": "Linus",
"surname": "Torvalds",
"price": "23454"
}];
$scope.setLoading = function (loading) {
$scope.isLoading = loading;
}
$scope.layoutDone = function (value) {
console.log(value);
$scope.setLoading(true);
$timeout(function() {
// take care of the sorting order
if ($scope.sortingOrder !== '') {
if(value == 'sort'){
$scope.contacts = $filter('orderBy')($scope.contacts, $scope.sortingOrder, false);
}
else if(value == '-sort'){
$scope.contacts = $filter('orderBy')($scope.contacts, $scope.sortingOrder, true);
}
}
$scope.setLoading(false);
}, 1000);
}
$scope.loadFeed = function(url) {
$scope.setLoading(true);
}
$scope.loadFeed();
});
app.directive('repeatDone', function() {
return function(scope, element, attrs) {
if (scope.$last) { // all are rendered
scope.$eval(attrs.repeatDone);
}
}
})
Please help me out !!!
SInce you sort in JS, you can allways use Array.sort() - at least on modern browsers-, passing it a sort function like:
function sortAsc(a,b) {
a = parseInt(a.price);
b = parseInt(b.price);
return a > b ? 1 : (a === b ? 0 : -1);
}
And then execute the sorting like:
if(value == 'sort'){
$scope.contacts.sort(sortAsc);
}
See forked fiddle: http://jsfiddle.net/sRruf/ (the initial state is not sorted)
The sort property (here price) can be parameterized with a little extra work.
And here is a version using the orderBy filter and a custom predicate: http://jsfiddle.net/sRruf/1/
Current example of groups usage says that errorPlacement should be defined, where names of fields are hard coded:
$("#myform").validate({
groups: {
username: "fname lname"
},
errorPlacement: function(error, element) {
if (element.attr("name") == "fname"
|| element.attr("name") == "lname" )
error.insertAfter("#lastname");
else
error.insertAfter(element);
},
debug:true
})
Is there any way to get rid of this errorPlacement with field names? The error message should be always places after last element in the group.
I think you're going to have to use errorPlacement, but you can pull out a few tricks to get the error in a consistent place among groups every time:
$("#test-form").validate({
groups: {
username: "fname lname",
range: "min max"
},
errorPlacement: function ($error, $element) {
var elementName = $element.attr("name"),
lastInGroup = $.map(this.groups, function(fields, name) {
var fieldsArr;
if (fields.indexOf(elementName) >= 0) {
fieldsArr = fields.split(" ");
return fieldsArr[fieldsArr.length - 1];
} else {
return null;
}
})[0];
if (lastInGroup) {
$error.insertAfter($("input[name='" + lastInGroup + "']"));
} else {
$error.insertAfter($element);
}
}
});
Basically, access the groups object that you've defined and find the last field in every group. Attach the error after that field.
Example: http://jsbin.com/acenic/