How to implement tree nodes toggling in Vega JS? - javascript

I'm using Vega JS for building a tree chart. In general, my question is the following:
Vega documentation has a great example of tree layout. How can I extend it with an ability to collapse & expand its nodes?
To be more specific, let's consider an example of tree chart that I'm building here in Vega Editor.
If you click on the nodes, they will toggle (expand or collapse), allowing you to see particular branches of the tree. This feature works fine unless you try to collapse the top-level node (region) while keeping the second-level nodes (districts) expanded. In that case the tree will look like this:
It happens because of the way I handle this interaction:
When you click on a node, toggledNode signal is triggered, which in turn triggers toggle action in the expandedNodes data array. I.e. by clicking on a node, I add or remove that node to the expandedNodes array (more precisely, we add/remove a reduced object with only name property)
Thus expandedNodes data contains information about which nodes are explicitly expanded. But it doesn't know if those expanded nodes are inside of a collapsed parent node.
Then, to find out which nodes are actually visible, I use visibleNodes data. There I apply filter transform with the following expression: !datum.parent || indata('expandedNodes', 'name', datum.parent). I.e. I check only one level up: if the node's parent is present in the expandedNodes array , I consider the node as visible.
The problem is the following: I can't find any way of extending this functionality across multiple levels.
Probably I could write some hooks to check the same condition across 2 or 3 levels, e.g:
!datum.parent ||
indata('expandedNodes', 'name', datum.parent) &&
indata('expandedNodes', 'name', datum.myCustomFieldWithParentNode.parent) &&
indata('expandedNodes', 'name', datum.myCustomFieldWithParentNode.myCustomFieldWithParentNode.parent)
But it seems too complex for such a simple problem, and also it's not a final solution. In theory, a tree may contain dozens of nesting levels: what to do then?
I found one useful expression in Vega: treeAncestors. I could easily write a solution in JavaScript, where I have loops and array methods such as .some() and .every(). But apparently Vega doesn't support any expressions to iterate over an array. So even though I can get an array of tree node ancestors with treeAncestors function, I can't do anything with it to verify that all ancestors are expanded.
Either my approach is wrong, and somebody can find a better algorithm for doing the same, which doesn't require iterating over arrays (except for data and indata expressions) - or it's a current limitation of Vega.

You can use treeAncestors and then use a flatten transform to get a dataset that you can query. In your case it would look something like:
{
"transform": [
{
"as": "treeAncestors",
"type": "formula",
"expr": "treeAncestors('tree', datum.id, 'root')"
}
],
"name": "tree-ancestors",
"source": "tree"
},
{
"transform": [{"fields": ["treeAncestors"], "type": "flatten"}],
"name": "tree-ancestors-flatt",
"source": "tree-ancestors"
},
{
"transform": [
{
"type": "filter",
"expr": "indata('selected', 'value', datum.treeAncestors.id)"
}
],
"name": "filtered",
"source": "tree-ancestors-flatt"
},
{
"transform": [{"type": "aggregate", "groupby": ["id"]}],
"name": "filtered-aggregate",
"source": "filtered"
},
{
"transform": [
{
"type": "filter",
"expr": "indata('filtered-aggregate', 'id', datum.id) "
}
],
"name": "filtered-tree",
"source": "tree"
}

Vega doesn't seem to have a recursive way of solving the problem for your question" hey, if all my parents are expanded, then I am visible as a node ".
You can check indeed conditions for all levels you wish to define.
{
"type": "filter",
"expr": "!datum.parent || indata('expandedNodes','name',datum.parent)&&datum.depth==1||(indata('expandedNodes','name',datum.firstParent)&&indata('expandedNodes','name',datum.secondParent)&&datum.depth==2)||(indata('expandedNodes','name',datum.firstParent)&&indata('expandedNodes','name',datum.secondParent)&&indata('expandedNodes','name',datum.thirdParent)&&datum.depth==3)"
}
The code above says to VEGA : " hey check if all of my defined parents are expanded and filter me if any of my parents exist but are not expanded
To see the full solution with your case, please check :
spec

Related

Deleting an object from a nested array in DynamoDB - AWS JavaScript SDK

I'm building an app where I need to delete items stored in the database. Here's a (shortened) example of user data I have in my DynamoDB table called 'registeredUsers':
{
"userId": "f3a0f858-57b4-4420-81fa-1f0acdec979d"
"aboutMe": "My name is Mary, and I just love jigsaw puzzles! My favourite jigsaw category is Architecture, but I also like ones with plants in them.",
"age": 27,
"email": "mary_smith#gmail.com",
"favourites": {
"imageLibrary": [
{
"id": "71ff8060-fcf2-4523-98e5-f48127d7d88b",
"name": "bird.jpg",
"rating": 5,
"url": "https://s3.eu-west-2.amazonaws.com/jigsaw-image-library/image-library/images/bird.jpg"
},
{
"id": "fea4fd2a-851b-411f-8dc2-1ae0e144188a",
"name": "porsche.jpg",
"rating": 3,
"url": "https://s3.eu-west-2.amazonaws.com/jigsaw-image-library/image-library/images/porsche.jpg"
},
{
"id": "328b913f-b364-47df-929d-925676156e97",
"name": "rose.jpg",
"rating": 0,
"url": "https://s3.eu-west-2.amazonaws.com/jigsaw-image-library/image-library/images/rose.jpg"
}
]
}
}
I want to be able to delete the item 'rose.jpg' in the user.favourites.imageLibrary array. In order to select the correct user, I can provide the userId as the primary key. Then, in order to select the correct image in the array, I can pass the AWS.DocumentClient the 'id' of the item in order to delete it. However, I'm having trouble understanding the AWS API Reference docs. The examples given in the developer guide do not describe how to delete an item by looking at one of it's attributes. I know I have to provide an UpdateExpression and an ExpressionAttributeValues object. When I wanted to change a user setting, I found it pretty easy to do:
const params = {
TableName: REGISTERED_USERS_TABLE,
Key: { userId },
UpdateExpression: "set userPreferences.difficulty.showGridOverlay = :d",
ExpressionAttributeValues: {
":d": !showGridOverlay
},
ReturnValues: "UPDATED_NEW"
};
To conclude, I need a suitable Key, UpdateExpression and ExpressionAttributeValues object to access the rose.jpg item in the favourites array.
Unfortunately, the UpdateExpression syntax is not as powerful as you would have liked. It supports entire nested documents inside the item, but not sophisticated expressions to search in them or to modify them. The only ability it gives you inside a list is to access or modify its Nth element. For example:
REMOVE #favorites.#imagelibrary[3]
Will remove the 3rd element of imagelibrary (note that the "#imagelibrary" will need to be defined in ExpressionAttributeNames), and you can also have a condition on #favorites.#imagelibrary[3].#id, for example, in ConditionExpression. But unfortunately, there is no way to specify more complex combinations of conditions and updates, such as "find me the i where #favorites.#imagelibrary[i].#id is equal something, and then REMOVE this specific element".
Your remaining option is to read the full value of the item (or with ProjectionExpression just the #favorties.#imagelibrary array), and then in your own code find which of the elements you want to remove (e.g., discover that it is the 3rd element), and then in a separate update, remove the 3rd element.
Note that if there's a possibility that some other parallel operation also changes the item, you must use a conditional update (both UpdateExpression and ConditionExpression) for the element removal, to ensure the element that you are removing still has the id you expected. If the condition fails, you need to repeat the whole operation again - read the modified item again, find the element again, and try to remove it again. This is an example of the so-called "optimistic locking" technique which is often used with DynamoDB.

Get text nodes separately (in the correct order) with fast-xml-parser?

No matter what options I try, I seem to get all text nodes clustered together, without any information on where the inner XML nodes were inserted. It's easier to explain with a simple example:
<a>left <b>middle</b> right</a>
I expect this to give me something like this:
{
tag: 'a',
children: [
'left ',
{ tag: 'b', children: ['middle'] },
' right',
]
}
The exact format doesn't matter, but middle is between left and right. The order of children elements is important to me. With fast-xml-parser, what I get instead is the following:
{
"a": {
"#text": "left right",
"b": "middle",
}
}
I don't mind the different format, but I lost the information about the position of the <b>middle</b> node. From the JavaScript tree version of the XML file, there's no way to differentiate between these files, as they all parse into the same structure.
<a>left right<b>middle</b></a>
<a><b>middle</b>left right</a>
<a>left <b>middle</b> right</a>
<a>le<b>middle</b>ft right</a>
Is there an option which will allow me to preserve the order of text nodes?
Unfortunately, it seems like the answer is that this option is simply not available in fast-xml-parser, as per this issue I found: https://github.com/NaturalIntelligence/fast-xml-parser/issues/175. Text nodes will get merged as one, and the team member explains that this is "not a bug but expected behavior".

What are all the GoJS NodeData and LinkData property fields?

So I'm wanting to dynamically build a GraphLinksModel in GoJS using addNodeData(nodedata) and addLinkData(linkdata). My issue is that I don't know what the fields of nodedata and linkdata are, other than the fields that are in example code. I can build the Node object and Link object for each of my nodes and links, but addNodeData and addLinkData don't actually want those, they want "nodedata" and "linkdata". When I try to addNodeData(Node) then I get an error.
Here are the descriptions of addNodeData(nodedata) and addLinkData(linkdata). As you can see, it just defines the parameter as an Object, with no hints, other that in the examples about what's supposed to be in it. Any direction or explanation would be appreciated.
Node data is a JavaScript Object with minimum of the below formats. There can be additional property added to it based on type of object that you use.
{ "id": 1, "text": "TextToDisplay1" }
{ "id": 2, "text": "TextToDisplay2" }
The link data is again a JavaScript Object with the below format
{ "from": 1, "to": 2, "text": "Link above 1 and 2", "curviness": 20 }
'From' attribute of this object will have the From node's Id value and the same is teh case with 'to' attribute, it is 'to' nodes id.
There are no "all", the Node data can contain anything, arbitrarily, but must contain a key. If no key is defined, the GoJS model will automatically assign one. In other words, if you add { } as your node data and look at it, you'll find it is:
{ __gohashid: 1975, key: -5 }
__gohashid is internal to GoJS and should not be modified or used. They key is unique.
You can change the uniqueness function of the model, and you can even change the keyword for key.
You can read more in the Model overview in the API.
Other than there are a few built in data properties:
category, which specifies which Node or Link template to use, but the default is "" and its not necessary to specify.
In TreeModel there's also parent, which specifies the key of the parent Node.
Any other properties you find on the Model don't mean anything unless the Diagram's Node and Link templates have data-bindings to those names. For example this template has two data bindings:
// define a simple Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto", // the Shape will go around the TextBlock
$(go.Shape, "RoundedRectangle", { strokeWidth: 0},
// Shape.fill is bound to Node.data.color
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 }, // some room around the text
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key"))
);
If color exists in the Node data, it will be used as the Shape.fill in this Node template's Shape.
If key exists (it has to) in the Node data, it will be used as the TextBlock.text.
You can see that in action here: http://codepen.io/simonsarris/pen/ORwoLA?editors=1010
Let me know if you think there ought to be additional clarifications and I'll update the answer.

Structure of the JSON flare input for D3.js Treemap Visualization

I am currently working with the Treemap visualization of D3.js and was hoping to understand how the flare.json used in the example has been organized. Does the format of the json input file need to be in the exact same structure as used in the example. I have an input file from a web crawler with a list of URLs and their respective parent URLs. I tried using something like the following but it won't work and am not sure if it's just the structure that's different or something else.
listURLs.json:
{
"name": "flare",
"children": [
{"children":"http:\/\/a.wholelottanothing.org","name":"http:\/\/buzz.blogger.com"},
{"children":"http:\/\/www.bitworking.org","name":"http:\/\/buzz.blogger.com"},
{"children":"http:\/\/blog.computationalcomplexity.org","name":"http:\/\/buzz.blogger.com"},
{"children":"http:\/\/www.blogactionday.org","name":"http:\/\/buzz.blogger.com"},
{"children":"http:\/\/www.wikipaintings.org","name":"http:\/\/littlegreeniguana.blogspot.com"}
]
}
I know this looks very different from the flare.json used in the example but can this work? Also, the input that I am using doesn't include the 'size' parameter which is also probably why the output is blank. How do I use the size here? Can it be dynamically adjusted later in the code? Any help will be most appreciated, I am a D3 novie!
The hierarchical data format expected by tree, pack and other D3 hierarchical layouts is expecting "children" to be an array of objects, and traverses that hierarchical data in preparation for formatting your objects for display using layouts. So, you don't want to use "children" to store a single link, instead, you want it to store an array of objects formatted just like the parent object (even if there is only one thing in that array). It's a bit hard to grasp what you're trying to display in your dataset, but my guess is all those websites are under buzz.blogger.com, except the last one, in which case properly formatted hierarchical data would look like this (Note that everything is nested in a root node, which you can name whatever you want):
{
"name": "root node",
"children": [
{"name":"http:\/\/buzz.blogger.com", "children": [
{"name": "http:\/\/www.bitworking.org"},
{"name": "http:\/\/blog.computationalcomplexity.org"},
{"name": "http:\/\/www.blogactionday.org"}
]
},
{"name":"http:\/\/littlegreeniguana.blogspot.com", "children": [
{"name": "http:\/\/www.wikipaintings.org"}
]
}
]
}

Resort output for JSON (external file) imported data with jQuery

I am quite new to this languages and trying to make sense of what's going on. I have managed to get data from an external JSON file and create a list from it.
This is the contents from the JSON file:
{
"player": [
{
"name": "John",
"country": "USA",
"score": 102400
},
{
"name": "Mary",
"country": "Australia",
"score": 80001
},
{
"name": "Jane",
"country": "England",
"score": 103900
}
]
}
Now here is the fiddle with the HTML and js.
http://jsfiddle.net/tusika_/ut3NZ/
As you can see, every ul is wrapped in a div with class "player". What I would like to achieve is to be able to sort those divs of class "player", by sorting alphabetically the name (default) or country or descending score of the players.
After two days of research and finding answers to similar questions, I managed to put the data into an array, and when I use the sort method and the function in the js, i see in the console that the objects do get sorted differently, however they only sort alphabetically for the first three objects and then the last two get not sorted (in the original file I have many more players than three).
Also I do not undestand how to reprint of screen that new order. (it should replace the current output each time)
I would appreciate a response that indicates where the error of the logic is and doesn't only provide the code but helps me understand why the code is such.
Thank you very much!!!
The issue is (as you have noticed) the disconnection of the array order and the DOM order. You only use the array to create the DOM elements. They are not somehow linked so that what happens to one affect the other.
You will have to manually redraw the dom by either emptying the container and redrawint the element, or by re-arranging the existing DOM elements. For example you could have a function that will clear the #list element and then append the sorted nodes.
function displayData(array) {
var list = $("#list").empty();
$.each(array, function () {
list.append("<div class='player'><ul><li>" + this['name'] + "</li><li>" + this['country'] + "</li><li>" + this['score'] + "</li></ul></div>");
});
}
Also you do not need to sort the array while adding each element, just sort the whole of the array once.
So you can use the above code like this
var sorted = data.player.sort(byCountry);
displayData(sorted);
You can see a simple demo at http://jsfiddle.net/gaby/YhvTt/
When you're doing array.push(key, value);, you're pushing both the key and the value in the array (at position i and i+1).
OH, and BTW, you can simply do: data.player.sort(compare);

Categories