Selecting edges based on both edge's data and target's data - cytoscape.js

I would like a selector for edges matching
edge[type="blocker"]
that have a target matching
node[status="complete"]
In other words, is there a valid way of expressing the following:
cytoscape({
...
style: [
...
{
selector: '( node -> node[status="complete"] )[type="blocker"]',
style: {
display: 'none',
},
},
...
],
...
})
I don't see a way of doing this in the documentation.
Obviously, I could copy the target's data into the node's data and use the following:
edge[type="blocker"][target_status="complete"]
But duplicating the data goes against my every instinct as a software developer.

You can provide function to filter method:
cy.edges().filter(function(ele) {
return ele.data('type') == 'blocker' &&
ele.target().data('status') == 'complete';
})

How about this
EDIT:
var selectedEdges = cy.nodes("[status= 'complete']").connectedEdges("[type = 'blocker']);
var selectedEdges.addClass('specialSnowflake')
And in your stylesheet, just define:
{
"selector": "edge.specialSnowflake",
"style": {
"display": "none"
}
}

If a selector does not suffice, then you could (1) suggest a new feature to enhance the edge -> selector and perhaps even make a PR for it or (2) use a function on the style property instead.
E.g. for (2):
{ selector: 'edge', style: { display: edgeIsDisplayed } }
where the function can be anything, like edgeIsDisplayed(edge) => edge.data('foo') === 'bar' && edge.target().hasClass('baz')
See http://js.cytoscape.org/#style/mappers

There's no selector that will help.
However, it is possible to avoid having to manually update two elements when data changes.
Style sheet value functions are called every time a matching element's data changes. In one those function, one could therefore update the data of incoming edges every time the node's data is updated, keeping the data of the two in sync automatically.
var push_status = function(node) {
node.incomers('edge').forEach( edge => edge.data('target_status', node.data('status')) );
node.outgoers('edge').forEach( edge => edge.data('source_status', node.data('status')) );
};
cytoscape({
...
style: [
...
{
selector: 'node',
style: {
label: node => { push_status(node); return node.data('id'); },
},
},
{
selector: 'edge[type="blocker"][target_status="complete"]',
style: {
display: 'none',
},
},
...
],
...
})
This would qualify as a hack, but it works perfectly. Updating the node's data updates the edge's data, which causes the style to be applied or unapplied as appropriate.
Be wary of creating an infinite loop! In particular, modifying the data of a node's parent will trigger a calculation of the node's style. This problem can be avoided by replacing
ele.data('key', val)
with
// Making changes to a element's data triggers a style recalculation.
// This avoids needlessly triggering the style recalculation.
var set_data = function(node, key, new_val) {
let old_val = node.data(key);
if (new_val != old_val)
node.data(key, new_val);
};
set_data(ele, 'key', val)

Related

How to not trigger watch when data is modified on specific cases

I'm having a case where I do wish to trigger the watch event on a vue project I'm having, basically I pull all the data that I need then assign it to a variable called content
content: []
its a array that can have multiple records (each record indentifies a row in the db)
Example:
content: [
{ id: 0, name: "First", data: "{jsondata}" },
{ id: 1, name: "Second", data: "{jsondata}" },
{ id: 2, name: "Third", data: "{jsondata}" },
]
then I have a variable that I set to "select" any of these records:
selectedId
and I have a computed property that gives me the current object:
selectedItem: function () {
var component = this;
if(this.content != null && this.content.length > 0 && this.selectedId!= null){
let item = this.content.find(x => x.id === this.selectedPlotBoardId);
return item;
}
}
using this returned object I'm able to render what I want on the DOM depending on the id I select,then I watch this "content":
watch: {
content: {
handler(n, o) {
if(o.length != 0){
savetodbselectedobject();
}
},
deep: true
}
}
this work excellent when I modify the really deep JSON these records have individually, the problem I have is that I have a different upload methord to for example, update the name of any root record
Example: changing "First" to "1"
this sadly triggers a change on the watcher and I'm generating a extra request that isnt updating anything, is there a way to stop that?
This Page can help you.
you need to a method for disables the watchers within its callback.

How does an aframe entity defaults to the schema ones if no data is passed on

I'm having some difficulties in understanding how in aFrame an entity works with schema and data together. Forgive me, I'm a bit new in the library and maybe I'm missing something. Thanks.
Consider the following:
// register the aframe component (Button.js):
import { registerComponent, THREE } from 'aframe'
const button = registerComponent('button', {
schema: {
width: { type: 'number', default: 0.6 },
height: { type: 'number', default: 0.40 },
color: { type: 'string', default: 'orange' },
},
init: function() {
const schema = this.schema // Schema property values.
const data = this.data // Component property values.
const el = this.el // Reference to the component's entity.
// Create geometry.
this.geometry = new THREE.BoxBufferGeometry(data.width || schema.width.default, data.height || schema.height.default, data.depth || schema.depth.default)
// Create material.
this.material = new THREE.MeshStandardMaterial({ color: data.color || schema.color.default })
// Create mesh.
this.mesh = new THREE.Mesh(this.geometry, this.material)
// Set mesh on entity.
el.setObject3D('mesh', this.mesh)
}
})
export default button
// add the entity in the app (App.js)
import button from './Button'
<a-entity button="width: 0.4; height: 0.4: color: 'green'" position="0 0 0"></a-entity>
Now with the example above I would expect that my component will use data instead of schema defaults. And if I would instead just do:
<a-entity button position="0 0 0"></a-entity>
It would use the schema default ones.
Is this how it should be done?
I've been seeing a lot of examples where people just use data.
Thanks
The schema isn't intended to be used directly. From the docs:
The schema defines the properties of its component. (...)
The component’s property type values are available through this.data
The idea goes like this:
You define the properties (with defaults) in the schema
schema {
prop: {default: "default value"}
},
You access the values with this.data[prop_name]:
init: function() {
console.log("prop value at init:", this.data.prop)
},
You can monitor the property updates (like via setAttribute("propname", "newValue") within the update handler:
update: function(oldData) {
console.log("prop changed from", oldData.prop, "to", this.data.prop)
},
In a setup like this:
<!-- this.data.prop will contain the default values -->
<a-entity foo></a-entity>
<!-- this.data.prop will contain the new value -->
<a-entity foo="prop: value></a-entity>

Why it is hard to use vue-i18n in vue data() (why it is not reactive)

I am using vue-i18n in a vue project. And I found it really confusing when using some data in vue data with i18n. Then if I change locale, that data is not reactive. I tried to return that data from another computed data but anyways it is not reactive because i18n is written in data. *My situation - * I want to show table with dropdown(list of columns with checkbox) above it. When user checks a column it will be showed in table if unchecks it won't. It is working fine until I change locale. After changing locale table columns is not translated but dropdown items is reactively translated and my code won't work anymore. Here is some code to explain better: In my myTable.vue component I use bootstrap-vue table -
template in myTable.vue
<vs-dropdown vs-custom-content vs-trigger-click>
<b-link href.prevent class="card-header-action btn-setting" style="font-size: 1.4em">
<i class="fa fa-th"></i>
</b-link>
<vs-dropdown-menu class="columns-dropdown">
<visible-columns :default-fields="columns" #result="columnListener"></visible-columns>
</vs-dropdown-menu>
</vs-dropdown>
<b-table class="generalTableClass table-responsive" :fields="computedFieldsForTable">custom content goes here</b-table>
script in myTable.vue
data(){
return {
fieldsForTable: [];
}
},
computed: {
computedFieldsForTable () {
return this.fieldsForTable;
},
columns() {
return [
{
key: 'id',
label: this.$t('id'),,
visible: true,
changeable: true
},
{
key: 'fullName',
label: this.$t('full-name'),,
visible: true,
changeable: true
},
{
key: 'email',
label: this.$t('email'),,
visible: true,
changeable: true
}
]
}
},
mounted () {
this.fieldsForTable = this.filterColumns(this.columns);
},
methods: {
filterColumns(columns = []) {
return columns.filter(column => {
if (column.visible) {
return column
}
})
},
columnListener ($event) {
this.fieldsForTable = this.filterColumns($event)
}
}
Can someone give me some advice for this situation ?
*EDIT AFTER SOME DEBUGGING: I think when filtering columns(in computed) and returning it for fieldsForTable inside filterColumns(columns) method, it actually returning array(of objects) with label='Label Name' not label=this.$t('labelName'). So after filtering the new array has nothing to do with vue-i18n. My last chance is reloading the page when locale changes.
Trying modify computedFieldsForTable as follows. You need to reference this.columns in computedFieldsForTable, so that Vue can detect the change of labels in this.columns.
computedFieldsForTable () {
return this.filterColumns(this.columns);
},
EDITED: put your this.columns in data. Then
columnListener ($event) {
this.columns = $event;
}
I hope i didn't misunderstand what you mean.
EDITED (again):
Maybe this is the last chance that I think it can work. Put columns in computed() still and remove computedFieldsForTable. Finally, just leave fieldsForTable and bind it on fields of <b-table>.
watch: {
columns(val) {
this.fieldsForTable = this.filterColumns(val)
}
},
method: {
columnListener ($event) {
this.fieldsForTable = this.filterColumns($event)
}
}
However, I think it is better and easier to reload page whenever local change. Especially when your columns have a more complex data structure.

Nested grid in ExtJS 4.1 using Row Expander

On the front-end I have a Calls grid. Each Call may have one or more Notes associated with it, so I want to add the ability to drill down into each Calls grid row and display related Notes.
On the back-end I am using Ruby on Rails, and the Calls controller returns a Calls json recordset, with nested Notes in each row. This is done using to_json(:include => blah), in case you're wondering.
So the question is: how do I add a sub-grid (or just a div) that gets displayed when a user double-clicks or expands a row in the parent grid? How do I bind nested Notes data to it?
I found some answers out there that got me part of the way where I needed to go. Thanks to those who helped me take it from there.
I'll jump straight into posting code, without much explanation. Just keep in mind that my json recordset has nested Notes records. On the client it means that each Calls record has a nested notesStore, which contains the related Notes. Also, I'm only displaying one Notes column - content - for simplicity.
Ext.define('MyApp.view.calls.Grid', {
alias: 'widget.callsgrid',
extend: 'Ext.grid.Panel',
...
initComponent: function(){
var me = this;
...
var config = {
...
listeners: {
afterrender: function (grid) {
me.getView().on('expandbody',
function (rowNode, record, expandbody) {
var targetId = 'CallsGridRow-' + record.get('id');
if (Ext.getCmp(targetId + "_grid") == null) {
var notesGrid = Ext.create('Ext.grid.Panel', {
forceFit: true,
renderTo: targetId,
id: targetId + "_grid",
store: record.notesStore,
columns: [
{ text: 'Note', dataIndex: 'content', flex: 0 }
]
});
rowNode.grid = notesGrid;
notesGrid.getEl().swallowEvent(['mouseover', 'mousedown', 'click', 'dblclick', 'onRowFocus']);
notesGrid.fireEvent("bind", notesGrid, { id: record.get('id') });
}
});
}
},
...
};
Ext.apply(me, Ext.apply(me.initialConfig, config));
me.callParent(arguments);
},
plugins: [{
ptype: 'rowexpander',
pluginId: 'abc',
rowBodyTpl: [
'<div id="CallsGridRow-{id}" ></div>'
]
}]
});

Dojo Tree : bridge from *unformatted* json to expected format

I am very new to Dojo (1.7), and I am very excited by the AMD loader and the global philosophy, then thought I have red some dozen of documentation and googled a lot and my brains starts to grill, I am still unable to understand and perform some things : I would like to display a dijit.Tree of any sort of JSON, yes like a JSON editor, because I use also persistent JSON files for storing few datas (not only for GET/.../ transmission) . Here are my expects :
sample JSON : {"infos":{"address":"my address","phone":"my
phone"},"insurance":{"forks":[14,53,123],"prices":[5,8,"3%"]}}
display the differents variables of any JSON : the root child is the
root json variable, children L1 are the root variables, etc...and upon the json variable type (String, Number, Object, Array) I will also display a corresponding icon
not to have to parse the whole json and format it in one big time, would like for exemple to display first the root node, then the well formated children trought a getChildren method for example, so it is done progressively on expando (like a lazy load). I have already made my own Trees classes with javascript, the more flexible way was I gave a dataRoot, a renderItem(dataItem, domItem) and a getChildren(dataItem) to the constructor so I could perform and return all I want, the Tree only performed the rendering only when needed, the Tree had no knowing about datas structure neither modify it, but I am not sure to understand well why the dijit.Tree needs a so restrictive way of build...
Here is my last try, it might totally not the right way, (maybe I have to subclass) but as far as I understand, I need to play with 3 classes (dojo store, tree model and tree widget), but firstly it seems the model can't get the root node, please check my different code comments. So please is there any patient person that can give me a simple example with some clear explanations (yeah I am a bit demanding), at least the list of the right necessary variables for constructor's options I need for start displaying a nice tree view of my json file, there's so much I'm totally lost, many thanks !
...
// before there is the AMD part that load the needed things
Xhr.get({ url:'data/file.json', handleAs:'json',
load: function(data){
console.log('xhr.loaded : ', data);// got my javascript object from the json string
var store = new ItemFileReadStore({// is it the right store I need ??
// or the Memory store ?
// assuming later I'll need to save the data changes
rootId : 'root',//
rootLabel : 'Archive',// useless ? isn't it the model responsability ?
data : {id:'root', items:[data]}// trying to give a root node well formatted
});
var model = new TreeStoreModel({
store : store,
getChildren : function(obj){
// firstly here it seems the root is not found
// I got a 'error loading root' error
// what is missing in my instanciations ??
// what is exactyly the type of the 1st arg : a store ?
console.log('getChildren : ', this.get(obj.id));
},
mayHaveChildren : function(){
console.log('mayHaveChildren ', arguments);
return true;
}
});
var tree = new Tree({
model: model
}, domId);
tree.startup();
}
});
My solution is based on dojo/store/Memory inspired by Connecting a Store to a Tree:
You can find live demo at http://egoworx.com/ or download complete source from dropbox.
Now code. First dojo/store/Memory:
var data = {"infos":{"address":"my address","phone":"my phone", "gift": false, "now": new Date()},"insurance":{"forks":[14,53,123],"prices":[5,8,"3%"]}};
var store = new Memory({
data: data,
mayHaveChildren: function(object) {
var type = this.getType(object);
return (type == "Object" || type == "Array");
},
getChildren: function(object, onComplete, onError) {
var item = this.getData(object);
var type = this.getType(object);
var children = [];
switch(type) {
case "Array":
children = item;
break;
case "Object":
for (i in item) {
children.push({label: i, data: item[i]});
}
break;
}
onComplete(children);
},
getRoot: function(onItem, onError) {
onItem(this.data);
},
getLabel: function(object) {
var label = object.label || object + "";
var type = this.getType(object);
switch(type) {
case "Number":
case "String":
case "Boolean":
case "Date":
var data = this.getData(object);
if (data != label) {
label += ": " + this.getData(object);
}
}
return label;
},
getData: function(object) {
if (object && (object.data || object.data === false) && object.label) {
return object.data;
}
return object;
},
getType: function(object) {
var item = this.getData(object);
if (lang.isObject(item)) {
if (lang.isArray(item)) return "Array";
if (lang.isFunction(item)) return "Function";
if (item instanceof Date) return "Date";
return "Object";
}
if (lang.isString(item)) return "String";
if (item === true || item === false) return "Boolean";
return "Number";
},
getIconClass: function(object, opened) {
return this.getType(object);
}
});
Please note I added a boolean and Date type to your data.
dijit/Tree based on this store:
var tree = new Tree({
model: store,
persist: false,
showRoot: false,
getIconClass: function(object, opened) {
if (lang.isFunction(this.model.getIconClass)) {
return this.model.getIconClass(object, opened);
}
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf";
}
}, "placeholder");
tree.startup();
And finally a stylesheet to display data type icons:
.dijitTreeIcon {
width: 16px;
height: 16px;
}
.Object {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/object.png);
}
.Array {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/array.png);
}
.Date {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/date.png);
}
.Boolean {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/boolean.png);
}
.String {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/string.png);
}
.Number {
background-image: url(http://dojotoolkit.org/api/css/icons/16x16/number.png);
}
I cannot access jsFiddle since I'm currently in China, but I'll put the code above there upon my return to Europe and post a link here.
Try somethign like that instead :
store = new dojo.data.ItemFileWriteStore({
url : "",
data: {
identifier: "id",
label : "label",
items : [{
id : "root",
label : "root",
type : "root",
children: [data]
}]
}
});
Also in general avoid overriding the tree functions, you might extend them, but becareful.
If you want to console.log, then rather connect to them...
ItemFileReadStore is a read-only store, so not the one you want for "saving modifications".
You can try the ItemFileWriteStore, or JsonRest, etc.