cytoscape js box selection and highlight - javascript

There are 3 nodes in the example,I use modifier key + mousedown then drag,I can select 2 nodes and drag them,the problem is after I release the mouse,I can not see which nodes I have selected.They are not highlight or with shadow or reversed color or with some sign to mark they are selected.
<style>
#cy{
width:600px;
height:800px
}
</style>
<script src="cytoscape.js"></script>
<div id="cy"></div>
<script>
var cy = cytoscape({
container: document.getElementById('cy'), // container to render in
elements: [ // list of graph elements to start with
{ // node a
data: { id: 'a' }
},
{ // node b
data: { id: 'b' }
},
{ // node c
data: { id: 'c' }
},
{ // edge ab
data: { id: 'ab', source: 'a', target: 'b' }
}
],
style: [ // the stylesheet for the graph
{
selector: 'node',
style: {
'background-color': '#666',
'label': 'data(id)'
}
},
{
selector: 'edge',
style: {
'width': 3,
'line-color': '#ccc',
'target-arrow-color': '#ccc',
'target-arrow-shape': 'triangle'
}
}
],
layout: {
name: 'grid',
rows: 1
},
boxSelectionEnabled:true,
panningEnabled: true
});
</script>

You can store the selected node(s) in a variable or array and then add a mouseUp event, in which you highlight the nodes in said variable or array.
http://js.cytoscape.org/#events/user-input-device-events
Here you find the bind for cytoscape (always unbind events before binding them again):
http://js.cytoscape.org/#cy.on
cy.unbind('mouseup');
cy.bind('mouseup',/* 'node', */ function () {});

Related

Hide edge labels in vis.js-network

I would like to simply show/hide the labels of the edges of my vis.js-network - is this possible?
I have tried to update the edges in the vis.js-data structure:
Delete the label property - doesn't work
Set the label to undefined - doesn't work
Set the label to '' - doesn't work
Set the label to ' ' - works
I would prefer a network-wise toggle of some kind, but I haven't found one.
Is there a better way of doing this?
An alternative to updating the label property on each edge is to change the font color to be transparent for all edges. The setOptions() method can be used to update the options and will apply all edges in the network. The options edges.font.color and edges.font.strokeColor should both be updated, then returned to their original values to display the edges.
Example below and also at https://jsfiddle.net/rk9s87ud/.
var nodes = new vis.DataSet([
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" },
]);
var edges = new vis.DataSet([
{ from: 1, to: 2, label: 'Edge 1' },
{ from: 2, to: 3, label: 'Edge 2' },
{ from: 3, to: 4, label: 'Edge 3' },
{ from: 4, to: 5, label: 'Edge 4' },
]);
var container = document.getElementById("mynetwork");
var data = {
nodes: nodes,
edges: edges,
};
var options = {
nodes: {
// Set any other options, for example node color to gold
color: 'gold'
},
edges: {
font: {
// Set to the default colors as per the documentation
color: '#343434',
strokeColor: '#ffffff'
}
}
}
var hiddenEdgeTextOptions = {
edges: {
font: {
// Set the colors to transparent
color: 'transparent',
strokeColor: 'transparent'
}
}
};
var network = new vis.Network(container, data, options);
var displayLabels = true;
document.getElementById('toggleLabels').onclick = function() {
if(displayLabels){
// Apply options for hidden edge text
// This will override the existing options for text color
// This does not clear other options (e.g. node.color)
network.setOptions(hiddenEdgeTextOptions);
displayLabels = false;
} else {
// Apply standard options
network.setOptions(options);
displayLabels = true;
}
}
#mynetwork {
width: 600px;
height: 160px;
border: 1px solid lightgray;
}
<script src="https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js"></script>
<button id="toggleLabels">Toggle labels</button>
<div id="mynetwork"></div>

JointJS Adding a Port to a custom element

I created a custom element using the jointjs tutorial like this:
CustomRect = joint.dia.Element.define('example.CustomRect', {
attrs: {
rect: {
refX: 0,
refY: 0,
refWidth: '116',
refHeight: '70',
strokeWidth: 1,
stroke: '#000000',
fill: '#FFFFFF'
},
label: {
textAnchor: 'left',
refX: 10,
fill: 'black',
fontSize: 18,
text: 'text'
},
upperRect: {
strokeWidth: 1,
stroke: '#000000',
fill: 'rgba(255,0,0,0.3)',
cursor: 'pointer'
}
}
}, {
markup: [{
tagName: 'rect',
selector: 'rect'
},
{
tagName: 'text',
selector: 'label'
},
{
tagName: 'rect',
selector: 'upperRect'
}]
})
...
let customRect = new this.CustomRect()
customRect.attr({
upperRect: {
refWidth: '116',
refHeight: '10',
refX: 0,
refY: -11,
event: 'element:upperRect:pointerdown'
}
})
Furthermore, I tried adding Ports with the Port API for joint.dia.Element like this:
customRect.addPorts([
{
args: {
y: 30
},
z: 0
}
]);
This does add Ports to my element, but they don't have the functionality they should have, so they're just some circles that do nothing.
The method shown in the Port Tutorial does not work, since I'm not using joint.shapes.devs.Model for my custom element, because then I can't customize it like the joint.dia.Element element (or at least I think I can't).
So, how can I add Ports to a custom element defined with joint.dia.Element that have the functionality they should have (as shown in the Port Tutorial)?
Unfortunately, the documentation does not explain this clear enough. If you want to make the ports interactive you need to set the magnet attribute on them.
const CustomRect = joint.dia.Element.define('example.CustomRect', {
/* ... */
attrs: {
root: {
// Don't allow the root of the element to be target of a connection
magnet: false
}
},
ports: {
items: [{
id: 'port1',
attrs: {
portBody: {
// The port can be a target of a connection
// The user can create a link from the port by dragging the port
magnet: true
}
}
}, {
id: 'port2',
attrs: {
portBody: {
// The port can only become a target of a connection
// The logic can be modified via paper.options.validateMagnet()
magnet: 'passive'
}
}
}]
}
}, {
/* ... */
portMarkup: [{
tagName: 'circle',
selector: 'portBody',
attributes: {
'r': 10,
'fill': '#FFFFFF',
'stroke': '#000000'
// If all the magnets are `true` / `passive` feel free to define
// the magnet here in the default port markup or per a port group
// 'magnet': true
}
}]
});

using mapData() after adding several edges by cy.add() in cytoscape.js

I am using cytoscape.js for converting a raw txt data to cytoscape.js graph.
for that, first I make the graph with :
var cy = cytoscape({...})
without any elements.Then after reading nodes and edges from txt file, I add them with this for nodes :
cy.add({
data: {id: keys[i]}
}
);
and this for edges:
cy.add({
data: {
id: 'edge' + i,
source: edges[i].source,
target: edges[i].target,
label: edges[i].weight
}
});
Then i want to apply 'width': 'mapData(weight, 0, 100, 1, 6)'. how can I do that?
I tried these:
1) using mapData() in edge selector when making cy variable.(it doesnt works for added edges and nodes)
2) using mapData() for each edge after making each of them:
cy.$('#edge' + i).style('width','mapData(weight, 0, 100, 1, 6)');
3) using mapData() after making all nodes and edges :
cy.elements('edge').style('line-color','mapData(weight,1,6,blue,green)');
none of them works!
Maybe like this:
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
style: [
{
selector: 'node',
style: {
'label': 'data(name)',
'width': 'mapData(weight, 0, 100, 1, 6)',
'height': 'mapData(weight, 0, 100, 1, 6)',
}
}
]
});
cy.add([
{ group: "nodes", data: { id: "n0", name: "node0", weight: "x"} },
{ group: "nodes", data: { id: "n1", name: "node1", weight: "y"} },
{ group: "edges", data: { id: "e0", source: "n0", target: "n1" } }
]);
cy.ready(function () { // Wait for nodes to be added
cy.layout( // Call layout
name: 'yourDecision'
).run();
});
I define the cytoscape instance on initialization and there I define the style of the nodes/edges/... .

cytoscape.js & meteor simple example doesnt work

i added to meteor cytoscape : infinitedg:cytoscape
i have very basic meteor app:
hello.js http://pastebin.com/2frsHc9g
hello.html http://pastebin.com/10EYyJ74
but i am not able to make it work
here is error i can see in console of web browser:
on rendered zavolana hello.js:9 ss [object Object] debug.js:41
Exception from Tracker afterFlush function: debug.js:41 TypeError:
Cannot read property 'addEventListener' of undefined
at CanvasRenderer.registerBinding (infinitedg_cytoscape.js:17127)
at CanvasRenderer.load (infinitedg_cytoscape.js:17283)
at new CanvasRenderer (infinitedg_cytoscape.js:13419)
at $$.fn.core.initRenderer (infinitedg_cytoscape.js:7527)
at new $$.Core (infinitedg_cytoscape.js:6592)
at Function.$$.init (infinitedg_cytoscape.js:75)
at cytoscape (infinitedg_cytoscape.js:58)
at HTMLDivElement. (infinitedg_cytoscape.js:2808)
at Function.jQuery.extend.each (jquery.js:384)
at jQuery.fn.jQuery.each (jquery.js:136)
do you have please some "hello world" of combination of cytoscape and meteor ?
problem was with wrong library installed via meteor
after i installed correct cytoscape library, it is working
correct is cytoscape:cytoscape
here is minimal and working example:
JS
sit = "" //hlavni objekt
if (Meteor.isClient) {
Template.graf.rendered = function() {
// Meteor.defer(function() {
//setTimeout(function(){
console.log("on rendered called");
//var divcy = $('#cy');
// console.log("ss " + divcy);
sit = cytoscape({
container: document.getElementById('cy'),
ready: function() {
console.log("network ready");
updateNetworkData(sit); // load data when cy is ready
},
style: cytoscape.stylesheet()
.selector('node')
.style({
'content': function(e) {
return e.data("name")
},
'font-size': 12,
'text-valign': 'center',
'color': 'white',
'text-outline-width': 2,
'text-outline-color': function(e) {
return e.locked() ? "red" : "#888"
},
'min-zoomed-font-size': 8
// 'width': 'mapData(score, 0, 1, 20, 50)',
// 'height': 'mapData(score, 0, 1, 20, 50)'
})
.selector('edge')
.style({
'content': function(e) {
return e.data("name") ? e.data("name") : "";
},
'target-arrow-shape': 'triangle',
})
});
//})
}
}
if (Meteor.isServer) {
Meteor.startup(function() {
// code to run on server at startup
});
}
function updateNetworkData(net) {
// init Data
var nodes = [{ // node a
group: 'nodes',
data: {
id: 'a',
name:'a'
}
}, { // node b
group: 'nodes',
data: {
id: 'b',
name:'b'
}
}
]
var edges = [{ // edge ab
group: 'edges',
data: {
id: 'ab',
name:'ab',
source: 'a',
target: 'b'
}
}
]
net.elements().remove(); // make sure evything is clean
net.add(nodes);
net.add(edges);
net.reset() // render layout
}
CSS
#cy {
width : 70vw;
height: 50vw;
position: absolute;
}
HTML
<head>
<title>hello</title>
</head>
<body>
<h1>Welcome to Meteor!b</h1>
{{>graf}}
</body>
<template name="graf">
<div id="cy"></div>
</template>

Optimize JavaScript DrillDown code

I have a drilldown map on my page which I would like to optimise.
Right now I am loading every "drilldown" map even if it is not clicked.
Here is an example that shows how the data is load if the state is clicked.I would like to achieve that.
But this is my code and as you can see, I am loading all drilldown jsons even if the map is not clicked. In my example I have only 2 drilldown option, but in my real life problem I have it like 15 so it really slows down a little bit everything.
So this is my code:
// get main map
$.getJSON('json/generate_json_main_map.php', function(data) {
// get region 1 map
$.getJSON('json/generate_json_region_1.php', function(first_region) {
// get region 2 map
$.getJSON('json/generate_json_region_2.php', function(second_region) {
// Initiate the chart
$('#interactive').highcharts('Map', {
title: {
text: ''
},
colorAxis: {
min: 1,
max: 10,
minColor: '#8cbdee',
maxColor: '#1162B3',
type: 'logarithmic'
},
series: [{
data: data,
"type": 'map',
name: st_ponudb,
animation: {
duration: 1000
},
states: {
//highlight barva
hover: {
color: '#dd4814'
}
}
}],
drilldown: {
drillUpButton: {
relativeTo: 'plotBox',
position: {
x: 0,
y: 0
},
theme: {
fill: 'white',
'stroke-width': 0,
stroke: 'white',
r: 0,
states: {
hover: {
fill: 'white'
},
select: {
stroke: 'white',
fill: 'white'
}
}
}
},
series: [{
id: 'a',
name: 'First',
joinBy: ['hc-key', 'code'],
type: 'map',
data: first_region,
point: {
events: {
click: function() {
var key = this.key;
location.href = key;
}
}
}
}, {
id: 'b',
name: 'Second',
joinBy: ['hc-key', 'code'],
type: 'map',
data: second_region,
point: {
events: {
click: function() {
var key = this.key;
location.href = key;
}
}
}
}]
}
});
});
});
});
JSON from generate_json_main_map.php:
[{"drilldown":"a","name":"region 1","value":"1","path":""},{"drilldown":"b","name":"region 2","value":"2","path":""}]
JSON from generate_json_region_1.php:
[{"name":"Place 1","key":"place.php?id=1","value":"1","path":""},{"name":"Place 2","key":"place.php?id=2","value":"2","path":""}]
This is my attempt to make ajax calls load in parallel, but the map is not loading, I get just the coloraxis.
$(function() {
$.when($.getJSON('json/generate_json_main_map.php'), $.getJSON('json/generate_json_region_1.php'), $.getJSON('json/generate_json_region_2.php')).done(function(data,first_region,second_region){
$('#interactive').highcharts('Map', {
title: {
text: ''
},
colorAxis: {
min: 1,
max: 10,
minColor: '#8cbdee',
maxColor: '#1162B3',
type: 'logarithmic'
},
series: [{
data: data,
"type": 'map',
name: st_ponudb,
animation: {
duration: 1000
},
states: {
hover: {
color: '#dd4814'
}
}
}],
drilldown: {
drillUpButton: {
relativeTo: 'plotBox',
position: {
x: 0,
y: 0
},
theme: {
fill: 'white',
'stroke-width': 0,
stroke: 'white',
r: 0,
states: {
hover: {
fill: 'white'
},
select: {
stroke: 'white',
fill: 'white'
}
}
}
},
series: [{
id: 'a',
name: 'First',
joinBy: ['hc-key', 'code'],
type: 'map',
data: first_region,
point: {
events: {
click: function() {
var key = this.key;
location.href = key;
}
}
}
}, {
id: 'b',
name: 'Second',
joinBy: ['hc-key', 'code'],
type: 'map',
data: second_region,
point: {
events: {
click: function() {
var key = this.key;
location.href = key;
}
}
}
}]
}
});
});
});
I can see that the jsons are loaded and there is no JS error shown by firebug.
If you want to load on click, you need to call the state data on click_event (and not at startup).
Just like your JSFiddle example:
chart : {
events: {
drilldown: function (e) {
// Load you data
// show it with chart.addSeriesAsDrilldown(e.point, {...});
}
}
}
Or as #Whymarrh suggests, you can load them all in parallel (instead of one after the other) and once they are all retrieved, compute your map.
See https://lostechies.com/joshuaflanagan/2011/10/20/coordinating-multiple-ajax-requests-with-jquery-when/ for example on how to execute a code after all ajax calls have completed.
When you load your map data as you did, in the following manner:
$.when(
$.getJSON('json/generate_json_main_map.php'),
$.getJSON('json/generate_json_region_1.php'),
$.getJSON('json/generate_json_region_2.php')
).done(...);
The effect is this - when any of the three requests fail, all promises will be rejected and ultimately, your map never gets to be initialised.
A better approach could be to request all data independently, and the outcomes would be handled as follows:
If the request for the main data fails, abort the other requests unconditionally (there would be no need for a drill down if the primary data is non-existent).
If request for main data succeeds, you may go on and initialise the map as data becomes available. The request for drill down data may or may not succeed though (but half bread is better than none?). Assuming everything goes well, then in the event that user initiates a drill down action, you show a loading message and ultimately add the drill down series when it becomes available.
Here's an implementation of the method I offered:
$(function () {
// immediately trigger requests for data
var loadMainData = $.getJSON("json/generate_json_main_map.php");
var loadRegionData = {
"region-1-name": $.getJSON("json/generate_json_region_1.php"),
"region-2-name": $.getJSON("json/generate_json_region_2.php")
};
// region drilldown options
var regionalSeriesOptions = {
"region-1-name": {
id: 'a',
name: 'First',
joinBy: ['hc-key', 'code'],
type: 'map',
point: {
events: {
click: function () {
var key = this.key;
location.href = key;
}
}
}
},
"region-2-name": {
id: 'b',
name: 'Second',
joinBy: ['hc-key', 'code'],
type: 'map',
point: {
events: {
click: function () {
var key = this.key;
location.href = key;
}
}
}
},
// ...
"region-(n-1)-name": {
// series options for region 'n-1'
},
"region-n-name": {
// series options for region 'n'
},
"region-(n+1)-name": {
// series options for region 'n+1'
}
};
// main options
var options = {
title: {
text: ""
},
series: [{
type: "map",
name: st_ponudb,
animation: {
duration: 1000
},
states: {
hover: {
color: "#dd4814"
}
}
}],
events: {
drilldown: function (e) {
var regionName, request, series, chart;
if (e.seriesOptions) {
// drilldown data is already loaded for the currently
// selected region, so simply return
return;
}
regionName = e.point.name;
request = loadRegionData[regionName];
series = regionalSeriesOptions[regionName];
chart = this;
chart.showLoading("Loading data, please wait...");
request.done(function (data) {
// series data has been loaded successfully
series.data = data;
chart.addSeriesAsDrilldown(e.point, series);
});
request.fail(function () {
if (loadMainData.readyState !== 4) {
// do you really want to cancel main request
// due to lack of drilldown data?
// Maybe half bread is better than none??
loadMainData.abort();
}
});
// whether success or fail, hide the loading UX notification
request.always(chart.hideLoading);
}
},
colorAxis: {
min: 1,
max: 10,
minColor: '#8cbdee',
maxColor: '#1162B3',
type: 'logarithmic'
},
drilldown: {
drillUpButton: {
relativeTo: 'plotBox',
position: {
x: 0,
y: 0
},
theme: {
fill: 'white',
'stroke-width': 0,
stroke: 'white',
r: 0,
states: {
hover: {
fill: 'white'
},
select: {
stroke: 'white',
fill: 'white'
}
}
}
},
series: []
}
};
loadMainData.done(function (data) {
options.series[0].data = data;
$("#interactive").highcharts("Map", options);
}).fail(function () {
Object.keys(loadRegionData).forEach(function (name) {
// if primary data can't be fetched,
// then there's no need for auxilliary data
loadRegionData[name].abort();
});
});
});
Since I don't know every detail of your code, it's left for you to find a way to fit it into your solution.

Categories