I am working on an application that fetches data from a database and I would like to show them as graph.
I managed the "tap" event on a node by showing their neighbors (nodes and connection links).
The problem is that, every time I want to show the neighbors, all the graph is re-rendered and if some nodes were moved before, they lose their previous position.
Is there a way to add only the neighbors without affecting the position of the node already present in the layout?
Important: the constraint is that all the nodes should be "movable": the number of nodes in the graph can, easily, increase and I would like to have the availability to move/organize them without losing the result when I add new ones (by clicking on a node)
I am using cola-layout in my project.
Here the way I managed to add neighbors:
function addNeighbour(node, link) {
cy.startBatch();
addNode(link.otherNode.type, link.otherNode.name, link.otherNode.properties);
cy.add([
{
group: 'edges',
data:
{
id: node + ":" + link.type + ":" + link.otherNode.type + ":" + link.otherNode.name,
source: source,
target: target,
type: link.type,
properties: linkproperties
}
}
]);
refreshLayout()
cy.endBatch();
}
}
var layoutOpts = {
name: 'cola',
refresh: 2,
edgeLength: 200,
fit: false
}
function refreshLayout() {
layout.stop();
layout = cy.elements().makeLayout(layoutOpts);
layout.run();
}
Thanks in advance
(1) You can lock a node to make its position immutable, via nodes.lock().
(2) You can run a layout on a subset of the graph to exclude certain elements, via eles.layout().
Either of these strategies can be used in general, or they can be used in tandem.
For your case, it sounds like you should use (1).
Lock the existing nodes.
Add the new nodes.
Run Cola on the entire graph.
When Cola is done, free the locked nodes.
Note, however, that this won't always give a good result. You could over-constrain the system. If you want a good layout result, it's probably best to just run the layout on everything without locking, as Stephan T. suggested.
Related
I am trying to understand more in depth the difference between filter and item access control.
Basically I understand that Item access control is, sort of, higher order check and will run before the GraphQL filter.
My question is, if I am doing a filter on a specific field while updating, for instance a groupID or something like this, do I need to do the same check in Item Access Control?
This will cause an extra database query that will be part of the filter.
Any thoughts on that?
The TL;DR answer...
if I am doing a filter on a specific field [..] do I need to do the same check in Item Access Control?
No, you only need to apply the restriction in one place or the other.
Generally speaking, if you can describe the restriction using filter access control (ie. as a graphQL-style filter, with the args provided) then that's the best place to do it. But, if your access control needs to behave differently based on values in the current item or the specific changes being made, item access control may be required.
Background
Access control in Keystone can be a little hard to get your head around but it's actually very powerful and the design has good reasons behind it. Let me attempt to clarify:
Filter access control is applied by adding conditions to the queries run against the database.
Imagine a content system with lists for users and posts. Users can author a post but some posts are also editable by everyone. The Post list config might have something like this:
// ..
access: {
filter: {
update: () => ({ isEditable: { equals: true } }),
}
},
// ..
What that's effectively doing is adding a condition to all update queries run for this list. So if you update a post like this:
mutation {
updatePost(where: { id: "123"}, data: { title: "Best Pizza" }) {
id name
}
}
The SQL that runs might look like this:
update "Post"
set title = 'Best Pizza'
where id = 234 and "isEditable" = true;
Note the isEditable condition that's automatically added by the update filter. This is pretty powerful in some ways but also has its limits – filter access control functions can only return GraphQL-style filters which prevents them from operating on things like virtual fields, which can't be filtered on (as they don't exist in the database). They also can't apply different filters depending on the item's current values or the specific updates being performed.
Filter access control functions can access the current session, so can do things like this:
filter: {
// If the current user is an admin don't apply the usual filter for editability
update: (session) => {
return session.isAdmin ? {} : { isEditable: { equals: true } };
},
}
But you couldn't do something like this, referencing the current item data:
filter: {
// ⚠️ this is broken; filter access control functions don't receive the current item ⚠️
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId ? {} : { isEditable: { equals: true } };
},
}
The benefit of filter access control is it doesn't force Keystone to read an item before an operation occurs; the filter is effectively added to the operation itself. This can makes them more efficient for the DB but does limit them somewhat. Note that things like hooks may also cause an item to be read before an operation is performed so this performance difference isn't always evident.
Item access control is applied in the application layer, by evaluating the JS function supplied against the existing item and/or the new data supplied.
This makes them a lot more powerful in some respects. You can, for example, implement the previous use case, where authors are allowed to update their own posts, like this:
item: {
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId || item.isEditable;
},
}
Or add further restrictions based on the specific updates being made, by referencing the inputData argument.
So item access control is arguably more powerful but they can have significant performance implications – not so much for mutations which are likely to be performed in small quantities, but definitely for read operations. In fact, Keystone won't let you define item access control for read operations. If you stop and think about this, you might see why – doing so would require reading all items in the list out of the DB and running the access control function against each one, every time a list was read. As such, the items accessible can only be restricted using filter access control.
Tip: If you think you need item access control for reads, consider putting the relevant business logic in a resolveInput hook that flattens stores the relevant values as fields, then referencing those fields using filter access control.
Hope that helps
In relay, when using REQUIRED_CHILDREN like so:
return [{
type: 'REQUIRED_CHILDREN',
children: [
Relay.QL`
fragment on Payload {
myConnection (first: 50) {
edges {
node {
${fragment}
}
}
}
}
`
]
}]
and reading off the response through the onSuccess callback:
Relay.Store.commitUpdate(
new AboveMutation({ }), { onFailure, onSuccess }
)
the response turns the property myConnection into a hashed name (i.e. __myConnection652K), which presumably is used to prevent connection/list conflicts inside the relay store.
However, since this is a REQUIRED_CHILDREN and I'm manually reading myConnection, it just prevents access to it.
Is there an way to get the actual property names when using the onSuccess callback?
Just as Ahmad wrote: using REQUIRED_CHILDREN means you're not going to store the results. The consequence of it is that data supplied to the callback is in raw shape (nearly as it came from server) and data masking does not applies.
Despite not storing the data, it seems to be no reason (though core team member's opinion would be certainly more appropriate here) not to convert it to client style shape. This is the newest type of mutation, so there is a chance such feature was accidentally omitted. This is normal that queries are transformed to the server style shape, the opposite transformation could take place as well. However until now is has not been needed - while saving the data to the store and updating components props, transformation was made meanwhile. Currently most of Relay team is highly focused on rewriting much of the implementation, so I would not expect this issue to be improved very soon.
So again, solution proposed by Ahmed to convert type to GraphQLList seems to be the easiest and most reliable. If for any reason you want to stand by connection, there is an option to take GraphQL fragment supplied as children (actually its parsed form stored in __cachedFragment__ attribute of that original fragment) and traverse it to obtain the serializationKey for desired field (eg __myConnection652K).
I'm able to do affinity computer-data collocation with apache ignite. In the following two examples, it works as expected.
// Works on all nodes
IgniteUtil.getIgnite().compute().broadcast(() -> {
System.out.println("Should happen on all nodes");
cache.get(key).forEach(x -> {
System.out.println(x);
});
});
// Works on just the one node
IgniteUtil.getIgnite().compute().affinityRun(IgniteUtil.CACHE_NAME, key , () -> {
System.out.println("Should only happen on one node");
cache.get(key).forEach(x -> System.out.println(x));
});
However, I want to run a lambda against all the nodes data. So for example, say I had cached For every person, all of their orders from Amazon. I want to know what the total dollar amount is for everyone's orders.
I'm probably just missing an example, but according to the docs I don't see how to do this. In the examples I've seen I have to specify the keys I want to compute with. In this example, I just want to be able to do some lambda on all of the nodes, with each node only operating on its own share of the data.
I've tried doing this
IgniteUtil.getIgnite().compute().affinityRun(IgniteUtil.CACHE_NAME, key , () -> {
System.out.println("Should only happen once per node");
List<Integer> count = new ArrayList<Integer>();
System.out.println("Size: " + Sets.newHashSet(cache.iterator()).size());
cache.iterator().forEachRemaining(x -> {count.add(count.size());});
System.out.println("Calculated Size: " + count.size());
System.out.println("Values: " );
cache.get(key).forEach(x -> System.out.print(x));
System.out.println();
});
and it does only execute on the node that has the key, however, the cache size is the full cache size, not just the values that are local.
Any suggestions?
You can broadcast a closure as in your first example and use IgniteCache.localEntries() method to iterate through the local data.
Is there some simple way how to replace existing node with another node in JCR 2.0?
Due to the ordering of nodes, currently I am doing these steps:
step 1: Find sibling node which is right after existing node i want to replace:
if (preserveOrdering) {
NodeIterator iter = parent.getNodes();
boolean found = false;
while (iter.hasNext()) {
if (tempNode.equals(iter.nextNode())) {
found = true;
if (iter.hasNext()) {
tempNodeSibling = iter.nextNode();
break;
}
}
}
assert found;
}
step 2: delete existing node:
tempNode.remove();
step 3:
Create new node (I am doing clone, but probably node.addNode() method can be used,
new node is appended to the end of the child node list):
workspace.clone(workspace.getName(), existingNodePath, tempNodePath, false);
step 4:
Move new node before his old tempNode sibling (remebered in the first step)
parent.orderBefore(tempNodeName, tempNodeSibling.getName());
All these steps looks to me quite cumbersome. But I cannot find in JCR API better way. Mainly because there is only one method orderBefore() working with ordering.
Do you think there is some different/more simple approach for solving this problem?
Unfortunately, with JCR 2.0 new nodes are always added at the end and using javax.jcr.Node.reorder(...) is the only way to change the position of a child node within the parent's list of children. It is inconvenient to say the least, but I suspect adding such methods would have added too much complexity to an already complicated API.
I am a beginner to sencha Touch, basically i am a blackberry developer. Currently we are migrating our application to support Sencha Touch 1.1. Now i have some business solutions like i want to store the selected values in the local database. I mean i have multiple screens where, Once the user selects a value in each of the screen the data should save in the below following format.
[{'key1': "value1", 'key2': "value2", 'key3': "value3" ,'key4': "value4", 'key5': "value5"}]
1. First, the values need to be saved in key value pairs
2. The keys should play the role of primary key, key shouldn't be duplicated.
3. Should be available till the application life cycle or application session, don't need to save the data permanently.
I have come across the concepts like LocalStorageProxy, JsonStore and some others. I don't understand which one i can use for my specific requirements.
May be my question is bit more confusing. I have achieved the same using vector, in Blackberry Java so any data structure similar to this could help me. Need the basic operations like
Create
Add
Remove
Remove all
Fetch elements based on key
Please suggest me some samples or some code snapshots, which may help me to achieve this.
Edit: 1
I have done the changes as per #Ilya139 's answer. Now I am able to add the data with key,
// this is my Object declared in App.js
NSDictionary: {},
// adding the data to object with key
MyApp.NSDictionary['PROD'] = 'SONY JUKE BOX';
//trying to retrieve the elements from vector
var prod = MyApp.NSDictionary['PROD'];
Nut not able to retrieve the elements using the above syntax.
If you don't need to save the data permanently then you can just have a global object with the properties you need. First define the object like this:
new Ext.Application({
name: 'MyApp',
vectorYouNeed: {},
launch: function () { ...
Then add the key-value pairs to the object like this
MyApp.vectorYouNeed[key] = value;
And fetch them like this
value = MyApp.vectorYouNeed[key];
Note that key is a string object i.e. var key='key1'; and value can be any type of object.
To remove one value MyApp.vectorYouNeed[key] = null; And to remove all of them MyApp.vectorYouNeed = {};