I know that it's pretty easy to trigger an action in JointJS if you click a markup selector. We can simply add a custom event attr of that markup element, but as far as I know only pointer events are allowed. Is there any way to archive the same for hovering?
For example I have a custom cell with 4 buttons, that are just a bunch of svg tag added via the markup. I want to change the opacity of all other elements on the canvas, depending on the button that was hovered. I have an idea on how to filter everything and change the opacity, but I have no idea on how to trigger that event and know what selector I'm hovering.
You could take advantage of the 'element:mouseenter' event on the paper, then event.target can give you access to sub-elements on the shape.
In the following example, when you hover a color on the button shape, the standard.Rectangle type shapes all change to that color.
This solution takes advantage of adding classes via markup, and checking if the target has the particular class.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.6.5/joint.css" />
<style>
.joint-paper {
display: inline-block;
border: 1px solid gray;
}
</style>
</head>
<body>
<!-- content -->
<div id="myholder"></div>
<!-- dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.1/backbone.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.6.5/joint.js"></script>
<!-- code -->
<script type="text/javascript">
var namespace = joint.shapes;
var graph = new joint.dia.Graph({}, { cellNamespace: namespace });
var paper = new joint.dia.Paper({
el: document.getElementById('myholder'),
model: graph,
width: 600,
height: 300,
gridSize: 1,
cellViewNamespace: namespace
});
class ButtonShape extends joint.dia.Element {
defaults() {
return {
...super.defaults,
type: 'MyShape',
size: {
width: 120,
height: 155
},
attrs: {
body: {
width: 'calc(w)',
height: 'calc(h)',
fill: '#ffffff',
stroke: '#333333',
strokeWidth: 2,
rx: 5,
ry: 5,
pointerEvents: 'none'
},
rectUpper: {
width: 'calc(0.8*w)',
height: 'calc(h/2.5)',
fill: 'cornflowerblue',
stroke: '#333333',
strokeWidth: 2,
x: 'calc(0.1*w)',
y: 10
},
rectLower: {
width: 'calc(0.8*w)',
height: 'calc(h/2.5)',
fill: 'tomato',
stroke: '#333333',
strokeWidth: 2,
x: 'calc(0.1*w)',
y: 'calc(h - calc(h/2.5 + 10))'
},
}
};
}
preinitialize() {
this.markup = [{
tagName: 'rect',
selector: 'body'
}, {
tagName: 'rect',
selector: 'rectUpper',
className: 'upper'
}, {
tagName: 'rect',
selector: 'rectLower',
className: 'lower'
}];
}
}
const buttonShape = new ButtonShape();
buttonShape.position(10, 10);
buttonShape.addTo(graph);
for(let i = 0; i < 5; i++) {
const rect = new joint.shapes.standard.Rectangle();
rect.position(i * 60 + 10, 200);
rect.resize(50, 50);
rect.addTo(graph);
}
paper.on('element:mouseenter', function (cellView, evt) {
const rectangles = graph.getElements().filter((el) => el.get('type') === 'standard.Rectangle');
if (evt.target.classList.contains('upper')) {
rectangles.forEach((rect) => {
rect.attr('body/fill', 'cornflowerblue')
});
}
if (evt.target.classList.contains('lower')) {
rectangles.forEach((rect) => {
rect.attr('body/fill', 'tomato')
});
}
});
</script>
</body>
</html>
Related
I learned cytoscape.js and related extension some days and tried its many amazing features.
I find that when the number of children is greater than 5 and expand a node, the whole graph fly out of screen.
I constructed more complex data which own 3 parent nodes and 5 child nodes per parent node. There is a connection between any two child nodes.
So there are 3 parent nodes, 15 children nodes and 14+13+12..1 links.
To sum up, when there are more links, the layout behavior looks abnormal.
See my demo below.
You can modify parameters of my function getInitData() to see the effect.
document.addEventListener('DOMContentLoaded', function(){
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
ready: function(){
var api = this.expandCollapse({
layoutBy: {
name: "cose-bilkent",
animate: 'end',
randomize: false,
fit: false,
idealEdgeLength : 150
},
fisheye: false,
animate: true,
undoable: false
});
api.collapseAll();
},
style: [
{
selector: 'node',
style: {
'background-color': '#ad1a66'
}
},
{
selector: ':parent',
style: {
'background-opacity': 0.333
}
},
{
selector: "node.cy-expand-collapse-collapsed-node",
style: {
"background-color": "darkblue",
"shape": "rectangle"
}
},
{
selector: 'edge',
style: {
'width': 3,
'line-color': '#ad1a66'
}
},
{
selector: 'edge.meta',
style: {
'width': 2,
'line-color': 'red'
}
},
{
selector: ':selected',
style: {
"border-width": 3,
"border-color": '#DAA520'
}
}
],
elements : getInitData(3, 5)
});
var api = cy.expandCollapse('get');
var elements = null;
});
function getInitData(parentNum, childrenNum){
var data = [], children = [], i, j, n;
for(i = 0; i < parentNum; i++){
n = "parent"+i;
data.push({"group":'nodes',data:{"id":n}});
for(j = 0; j < childrenNum; j++){
children.push({"group":'nodes',data:{"id":n+"_child_"+j, parent:n}});
}
}
var s,t;
for(i = 0; i < children.length - 1; i++){
s = children[i].data.id;
for(j = i+1; j < children.length; j++){
t = children[j].data.id;
data.push({"group":'edges',data:{"id":s+"_"+t, source:s, target:t}});
}
}
return data.concat(children);
}
body {
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
font-size: 14px;
}
#cy {
z-index: 999;
width: 100%;
height: 100%;
}
h1 {
opacity: 0.5;
font-size: 1em;
font-weight: bold;
}
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="https://unpkg.com/cytoscape#3.1.0/dist/cytoscape.min.js"></script>
<!-- for testing with local version of cytoscape.js -->
<!--<script src="../cytoscape.js/build/cytoscape.js"></script>-->
<script src="https://unpkg.com/cytoscape-cose-bilkent#4.0.0/cytoscape-cose-bilkent.js"></script>
<script src="https://unpkg.com/cytoscape-expand-collapse#3.1.1/cytoscape-expand-collapse.js"></script>
<div id="cy"></div>
Solution one:
Your graph doesn't have any fitting logic. You can implement that yourself with the two methods cy.center(), which centers the graph to the current viewport and cy.fit(), which zooms the graph to the right position. You would have to call these mehtods everytime you change your graph, e.g. when you add a node, remove a node or, like in your case, expand and collapse. You can do that by binding these events and calling the said methods there.
Binding, as you know from the last question works like this:
cy.unbind('event');
cy.bind('event', 'target', function (event) {...});
Solution two:
You can alternatively, if possible (not all layouts can do this), set the method to fit: true,, which fits the graph with cy.fit(); and cy.center(); internally.
Additional problem and solution for that:
You said, that your graph looks bad when you only have one node in it, so to circumvent that, you can set the padding property of 'cose-bilkent' to a higher number. You can do that at the initialization in the options.
document.addEventListener('DOMContentLoaded', function() {
var padding = 10;
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
layout: {
name: 'cose-bilkent',
animate: false,
randomize: true
},
style: [{
selector: 'node',
style: {
'background-color': '#ad1a66'
}
},
{
selector: 'edge',
style: {
'width': 3,
'line-color': '#ad1a66'
}
}
],
elements: [{
"data": {
"id": "glyph9"
}
}]
});
document.getElementById("add").addEventListener("click", function() {
padding += 10;
var layout = cy.layout({
name: 'cose-bilkent',
animate: false,
padding: padding
});
layout.run();
});
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 90%;
width: 100%;
position: absolute;
float: left;
}
button {
margin-right: 10px;
}
<!DOCTYPE>
<html>
<head>
<title>cytoscape-cose-bilkent.js demo</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="https://unpkg.com/cytoscape#3.1.0/dist/cytoscape.min.js"></script>
<!-- for testing with local version of cytoscape.js -->
<!--<script src="../cytoscape.js/build/cytoscape.js"></script>-->
<script src="https://unpkg.com/cytoscape-cose-bilkent#4.0.0/cytoscape-cose-bilkent.js"></script>
<script src="https://unpkg.com/cytoscape-expand-collapse#3.1.1/cytoscape-expand-collapse.js"></script>
</head>
<body>
<button id="add" type="button">Add padding</button>
<div id="cy"></div>
</body>
</html>
In stencils we have png, on drop to paper we replace cell[png] with svg.
cell - represents png
vectorString - markup of png
var currentSvg = new joint.shapes.basic.pwmeter({
position: { x: bbox.x, y: bbox.y },
size: { height: cell.attributes.minHeight, width: cell.attributes.minWidth },
minWidth: cell.attributes.minWidth,
minHeight: cell.attributes.minHeight,
markup: vectorString,
type: cell.attributes.type,
fileName: cell.attributes.fileName,
isInteractive: true,
elementClassName: cell.attributes.elementClassName,
isAnimatedDevice: cell.attributes.isAnimatedDevice,
attrs: {
'.myClass': {
fill: '#ffffff',
stroke: '#000000'
}
}
});
currentSvg.addTo(Rappid.graph);
we are making pwmeter by extending Image
joint.shapes.basic.pwmeter = joint.shapes.basic.Image.extend({
type: 'basic.pwmeter',
initialize: function () {
return joint.shapes.basic.Image.prototype.initialize.apply(this, arguments);
}
});
We drop png from stencils to paper, and get svg markup for corresponding png,
we call currentSvg.addTo(Rappid.graph), during execution of this, all text in meter [svg] are replaced by blank.
our text - 0
auto generated code replaces my text, we are not able to figure out why this is happening
-
above is the generated text
We are new to jointjs, we think we are doing something wrong during extending.
I can see there is a markup in you settings. Make sure there is a text 'placeholder' in the markup.
in this example attr text is tied with the <text/> element
new joint.shapes.basic.pwmeter({
markup: '<g><image/><text/></g>',
attrs: {
text: { text: '48x48' },
image: { 'xlink:href': 'http://placehold.it/48x48', width: 48, height: 48 }
},
size: { width : 50, height: 50 },
position: { x:200, y: 50},
}).addTo(graph)
but in here, attr text has no matching element in the markup, therefore it won't be rendered.
new joint.shapes.basic.pwmeter({
markup: '<g><image/></g>',
attrs: {
text: { text: '90x90' },
image: { 'xlink:href': 'http://placehold.it/90x90', width: 90, height: 90 }
},
size: { width : 90, height: 90 },
position: { x:50, y: 50},
}).addTo(graph)
more information about attr property could be found here http://resources.jointjs.com/docs/jointjs/v1.0/joint.html#joint.dia.Element.presentation
I'm new with jointjs and I try to constraint a rectangle with ports to a line.
I tried to reproduce tutorial, that works with a basic.Circle, with a basic.Rect but not with devs.Model
Could someone explian me why and how to solve this problem?
Many thanks in advance!
Here is my code :
var width=400, height=1000;
var ConstraintElementView = joint.dia.ElementView.extend({
pointermove: function(evt, x, y) {
joint.dia.ElementView.prototype.pointermove.apply(this, [evt, 100, y]);
}
});
var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({ el: $('#myholder'), width: width, height: height, gridSize: 1, model: graph, elementView: ConstraintElementView});
var m1 = new joint.shapes.devs.Model({
position: { x: 20, y: 20 },
size: { width: 90, height: 90 },
inPorts: [''],
outPorts: [''],
attrs: {
'.label': { text: 'Model', 'ref-x': .4, 'ref-y': .2 },
rect: { fill: '#2ECC71' },
'.inPorts circle': { fill: '#16A085' },
'.outPorts circle': { fill: '#E74C3C' }
}
});
var m2=m1.clone();
m2.translate(0,300);
var earth = new joint.shapes.basic.Circle({
position: { x: 100, y: 20 },
size: { width: 20, height: 20 },
attrs: { text: { text: 'earth' }, circle: { fill: '#2ECC71' } },
name: 'earth'
});
graph.addCell([m1, m2, earth]);
Why does it not work?
devs.Model is not rendered via ContraintElementView to the paper.
devs.Model uses devs.ModelView for rendering, basic.Circle and basic.Rect use ContraintElementView.
JointJS dia.Paper searches for a view defined in the same namespace as the model first. If found, it uses it. It uses one from the paper elementView option otherwise. i.e. joint.shapes.devs.ModelView found for devs.Model but no view found for basic.Circle (no joint.shapes.basic.RectView is defined)
How to make it work?
define elementView paper option as a function. In that case paper don't search the namespace and uses the result of the function first.
Note that in order to render ports devs.ModelView is still required.
i.e.
var paper = new joint.dia.Paper({
elementView: function(model) {
if (model instanceof joint.shapes.devs.Model) {
// extend the ModelView with the constraining method.
return joint.shapes.devs.ModelView.extend({
pointermove: function(evt, x, y) {
joint.dia.ElementView.prototype.pointermove.apply(this, [evt, 100, y]);
}
});
}
return ConstraintElementView;
}
});
http://jsfiddle.net/kumilingus/0bjqg4ow/
What is the recommended way to do that?
JointJS v0.9.7+
not to use custom views that restrict elements movement
use restrictTranslate paper option instead.
i.e.
var paper = new joint.dia.Paper({
restrictTranslate: function(elementView) {
// returns an area the elementView can move around.
return { x: 100, y: 0, width: 0, height: 1000 }
};
});
http://jsfiddle.net/kumilingus/atbcujxr/
I think this could help you :
http://jointjs.com/demos/devs
I am going thorough the jointjs tutorial, in particular HelloWorld (http://www.jointjs.com/tutorial#hello-world). The basic example works fine except for the fact that the space in text: 'my box', results in the strange character. However, adding another small paper as suggested in the tutorial does not work, I do not get 2 working papers as indicated in the tutorial. The suggested addition is
var paperSmall = new joint.dia.Paper({
el: $('#myholder-small'),
width: 600,
height: 100,
model: graph,
gridSize: 1
});
paperSmall.scale(.5);
paperSmall.$el.css('pointer-events', 'none');
How to combine the addition with the main code? The main code is below:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="joint.css" />
<script src="jquery.js"></script>
<script src="lodash.js"></script>
<script src="backbone.js"></script>
<script src="joint.js"></script>
</head>
<body>
<div id="myholder"></div>
<script type="text/javascript">
var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({
el: $('#myholder'),
width: 600,
height: 200,
model: graph,
gridSize: 1
});
var rect = new joint.shapes.basic.Rect({
position: { x: 100, y: 30 },
size: { width: 100, height: 30 },
attrs: { rect: { fill: 'blue' }, text: { text: 'my box', fill: 'white' } }
});
var rect2 = rect.clone();
rect2.translate(300);
var link = new joint.dia.Link({
source: { id: rect.id },
target: { id: rect2.id }
});
graph.addCells([rect, rect2, link]);
</script>
</body>
</html>
Somehow the tutorial does not specify that the second Paper should be associated with another div in HTML:
<div id="myholder-small"></div>
I have used rickshaw library for plotting graphs. In that the legend part is there. I have also used button on the legend to delete graph. That means when I click on close button the respected graph should get deleted. But I am unable to do this. Is there any JavaScript or jQuery function to do this? Please help me finding the solution for this. Onclick event.. JavaScript function for deletion of graph
This is my code for plotting graph using rickshaw library...
<html>
<head>
<title>Sample-Rickshaw example</title>
<link type="text/css" rel="stylesheet" href="rickshaw.min.css">
<script src="vendor/d3.min.js"></script>
<script src="vendor/d3.layout.min.js"></script>
<script src="rickshaw.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js">
</script>
// CSS part
<style>
#chart_container {
position: relative;
display: inline-block;
font-family: Arial, Helvetica, sans-serif;
}
#chart {
display: inline-block;
margin-left: 40px;
}
#y_axis {
position: absolute;
top: 0;
bottom: 0;
width: 40px;
}
#legend {
display: oneline-block;
vertical-align: top;
margin: 0 0 0 10px;
}
#close_box {
btn_size:large;
font-family: Verdana, Geneva, sans-serif;
font-size:small ; font-weight: bold;
float: right; color: #666;
display: block; text-decoration: none;
border: 2px solid #666;
padding: 0px 3px 0px 3px;
}
</style>
// actual code
<body>
<div id="chart_container">
<div id="y_axis"></div>
<div id="chart"></div>
</div>
</head>
<div id="chart">
</div>
<div id="legend">
<FORM>
<INPUT type="button" value="x" onclick="remove graph">Remove Graph
</FORM>
</div>
// script for plotting data//
<script>
var palette = new Rickshaw.Color.Palette();
var graph = new Rickshaw.Graph( {
element: document.querySelector("#chart"),
width: 550,
height: 250,
series: [
{
name: "Northeast",
data: [ { x: -1893456000, y: 25868573 }, { x: -1577923200, y: 29662053 }, { x: -1262304000, y: 34427091 }, { x: -946771200, y: 35976777 }, { x: -631152000, y: 39477986 }, { x: -315619200, y: 44677819 }, { x: 0, y: 49040703 }, { x: 315532800, y: 49135283 }, { x: 631152000, y: 50809229 }, { x: 946684800, y: 53594378 }, { x: 1262304000, y: 55317240 } ],
color: palette.color(),
}
]
} );
var x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph } );
var y_axis = new Rickshaw.Graph.Axis.Y( {
graph: graph,
orientation: 'left',
tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
element: document.getElementById('y_axis'),
} );
var legend = new Rickshaw.Graph.Legend( {
element: document.querySelector('#legend'),
graph: graph
} );
graph.render();
// jquery function
$('#legend').on('click','.button',function(e){
$(this).remove(graph);
e.preventdefault
});
/*
$('#legend').on('click', function(e){
$('#legend').remove(); // or do something else
e.preventDefault();
}
*/
</script>
//Html tags
</body>
</html>
This is my whole code for plotting graph and respected legend.. commented part in the code is for deletion part.Is there any JavaScript or jQuery function to do this? Please help me finding the solution for this. Onclick event.. JavaScript function for deletion of graph`
The JavaScript error console complains about multiple things in your code. Perhaps take some time to find your errors yourself before you implement my changes. Always have the error console open when you are coding something in JavaScript!
To get your code working:
In the onclick-attribute something in JavaScript is expected. Instead of:
<!-- this is invalid, not a method call, not an initialization, in onclick ... -->
<INPUT type="button" value="x" onclick="remove graph">Remove Graph
put
<input type="button" value="x" onclick="removegraph()" />Remove Graph
Notice that I also ended the tag with />. Pay attention that all the tags are closed correctly!
When I analyzed the elements on the website, I noticed that the graph isn't printed in the graph div, but uses the chart_container. To change this behavior I edited the parameters of the graph:
var graph = new Rickshaw.Graph( {
element: $("#chart")[0], // I changed this line because this was obviously not working
width: 550,
height: 250,
series: [{
name: "Northeast",
data: [ { x: -1893456000, y: 25868573 }, { x: -1577923200, y: 29662053 }, { x: -1262304000, y: 34427091 }, { x: -946771200, y: 35976777 }, { x: -631152000, y: 39477986 }, { x: -315619200, y: 44677819 }, { x: 0, y: 49040703 }, { x: 315532800, y: 49135283 }, { x: 631152000, y: 50809229 }, { x: 946684800, y: 53594378 }, { x: 1262304000, y: 55317240 } ],
color: palette.color()
}]
});
Replace your method
// jquery function
$('#legend').on('click','.button',function(e){
$(this).remove(graph);
e.preventdefault // this line throws an error, because there is no ; and it is not a method-call nor an initialization or anything
});
with
// we've defined before, that this method is called when you click on the button X
function removegraph()
{
$("#chart").remove() ;
// uncomment the following lines, if you want to remove also the chart_container and the legend
// $("#chart_container").remove() ;
// $("#legend").remove() ;
}
Also pay attention that you open and close the tags in the correct order, for example:
<html>
<head>
</head>
<body>
</body>
</html>
This isn't correct in your code.