Here is a JSFiddle showing the issue: https://jsfiddle.net/Bronzdragon/xpvt214o/399003/
graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({
el: document.getElementById('myholder'),
model: graph,
width: 800,
height: 600,
});
var rect = new joint.shapes.basic.Rect({
attrs: {rect: {fill: 'lightblue'}},
size: { width: 200, height: 100 },
ports: {
groups: {
'inputs': {
position: { name: 'left' },
attrs: {circle: {fill: 'lightgreen'}},
magnet: 'passive'
},
'outputs': {
position: { name: 'right' },
attrs: {circle: {fill: 'pink'}},
magnet: true,
}
},
items:[ { group: 'inputs' }, { group: 'outputs' } ]
}
});
rect.position(100, 50);
rect.addTo(graph);
var rect2 = rect.clone();
rect2.position(400, 100);
rect2.addTo(graph);
I'm defining an JointJS element (called rect), with two ports on it, and adding them to the graph. The elements themselves are functional, but the ports aren't. When dragging off of an active magnet (the pink circle), it should create a link. Instead it moves the element.
I've followed the JointJS guide in creating a shape, and adding ports. These ports I've added seem to be completely inactive though. I do not know what I'm doing wrong.
I have emailed the JointJS team, and they came back to me with the following (Thank you very much, Roman!):
Thank you for reaching out to us! You were nearly there. The magnet attribute meant to be set on a specific shape DOM element. Similar to fill, refX, xlinkHref etc.
var rect = new joint.shapes.basic.Rect({
attrs: {rect: {fill: 'lightblue' }, root: { magnet: false }},
size: { width: 200, height: 100 },
ports: {
groups: {
'inputs': {
position: { name: 'left' },
attrs: {circle: {fill: 'green', magnet: 'passive' }},
},
'outputs': {
position: { name: 'right' },
attrs: {circle: {fill: 'red', magnet: true }},
}
},
items:[ { group: 'inputs' }, { group: 'outputs' } ]
}
});
Also, If you are new to JointJS I recommend you to read our online tutorials (https://resources.jointjs.com/tutorial). I suggest using standard shapes over basic, as the standard shapes are newer and reflecting the best JointJS techniques.
I am working with JointJS ports. By default ports are displayed as circles:
https://resources.jointjs.com/tutorial/ports
Is there any way to format the style from circle to rectangle? Please suggest any class names where I can override this.
the tutorial there is using the devs.Model shapes and it's a bit outdated approach...
There is a markup option in the port configuration, see more at https://resources.jointjs.com/docs/jointjs/v2.1/joint.html#dia.Element.ports
I'd recommend a port demo (https://github.com/clientIO/joint/tree/master/demo/ports). There is even a shape you're might be looking for:
var g6 = new joint.shapes.basic.Circle({
position: { x: 50, y: 50 },
size: { width: 500, height: 300 },
attrs: {
text: { text: 'compensateRotation: true', fill: '#6a6c8a' },
circle: { stroke: '#31d0c6', 'stroke-width': 2 }
},
ports: {
groups: {
'a': {
position: {
name: 'ellipseSpread',
args: { startAngle: 0, dr: 0, compensateRotation: true }
},
label: {
position: 'radial'
},
attrs: {
rect: {
stroke: '#31d0c6',
'stroke-width': 2,
width: 20,
height: 20,
x: -10,
y: -10
},
'.dot': {
fill: '#fe854f',
r: 2
},
text: {
fill: '#6a6c8a'
}
},
markup: '<g><rect/><circle class="dot"/></g>'
}
}
}
});
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 () {});
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.
I want to create a custom element with new attributes, I created my custom element like that but I need a new attribute to store information about the element.
joint.shapes.basic.newRect = joint.shapes.basic.Generic.extend({
markup: '<g class="rotatable"><g class="scalable"><rect/></g><text/></g>',
defaults: joint.util.deepSupplement({
type: 'basic.newRect',
attrs: {
'rect': { fill: 'white', stroke: 'black', 'follow-scale': true, width: 80, height: 40 },
'text': { 'font-size': 14, 'ref-x': .5, 'ref-y': .5, ref: 'rect', 'y-alignment': 'middle', 'x-alignment': 'middle' }
}
}, joint.shapes.basic.Generic.prototype.defaults)
Thanks!
You can add new properties next to the type and attrs. These will be your default properties for your element, like so:
joint.shapes.basic.newRect = joint.shapes.basic.Generic.extend({
markup: '<g class="rotatable"><g class="scalable"><rect/></g><text/></g>',
defaults: joint.util.deepSupplement({
type: 'basic.newRect',
attrs: {
'rect': { fill: 'white', stroke: 'black', 'follow-scale': true, width: 80, height: 40 },
'text': { 'font-size': 14, 'ref-x': .5, 'ref-y': .5, ref: 'rect', 'y-alignment': 'middle', 'x-alignment': 'middle' }
},
mycustom: 'foo'
}, joint.shapes.basic.Generic.prototype.defaults)
Later when you instantiate your element, you can also add properties only to that specific element:
var myNewRect = new joint.shapes.basic.newRect({ position: { x: 1, y: 1 }});
myNewRect.set('mycustom2', 'bar')
myNewRect.get('mycustom') // 'foo'
myNewRect.get('mycustom2') // 'bar'
All these properties will be taken into account when serializing the graph as well.
You can also use the provided Element#prop. See http://jointjs.com/api#joint.dia.Element:prop