Changing Port Location in custom element in JointJS - svg

I was able to create a custom element utilizing answers from this question:
Using predefined SVG file for creating a custom JointJS shape with ports
After reviewing the tutorials for JointJS I don't see how to move the ports to different locations on the element.
Thanks
Example code:
http://jsfiddle.net/jshubert/9a8brrun/
var el1 = new joint.shapes.devs.Model({
markup: '<g class="rotatable"><g class="scalable"><image class="body"/></g><text class="label"/><g class="inPorts"/><g class="outPorts"/></g>',
size: {
width: 100,
height: 100
},
position: {
x: 50,
y: 75
},
attrs: {
'.label': { text: 'SW_1', 'ref-x': .1, 'ref-y': .01},
'.body': {
width: 1024,
height: 768,
'xlink:href': 'data:image/svg+xml;utf8,' + encodeURIComponent(svgFile),
preserveAspectRatio: 'none'
}
},
inPorts: ['1'],
outPorts: ['2']
});

ref-x, ref-y applied on appropriate selector can do the trick eg.
'.inPorts .port0 .port-body' : {'ref-x': -20, 'ref-y': -20}
http://jsfiddle.net/9a8brrun/1/

Related

FabricJs- Cloning objects loses custom properties

I have set custom properties on SVG objects and on paths within the SVG objects.
Each object I add to the canvas also gets assigned an 'id' property, as well as some others properties that I use to interact with the objects in the app.
So far, when I clone these objects, I have been able to retain the properties that are on the SVG object by storing them in a session variable prior to cloning, then adding them back after cloning.
My problem is with the properties that are set on each of the object.paths. I have an 'id' property for each path, which I use for some functions that can manipulate the paths within the object.
So for example, before I group the object, the object.paths attribute looks like this...
paths: Array(4)
0: klass {id: "_25mm_x_400mm_ROUND", d: "M400.5,60.5A21.52,21.52", fill: "#ccc", dirty: false, stroke: "#000", …}
1: klass {id: "_25mm_x_400mm_ROUND", d: "M400.5,60.5v25c0", stroke: "#000", dirty: false, strokeMiterLimit: 10, …}
2: {id: "shapeTopColor", d: "M400.5,60.5A21.52,21.52", fill: "rgba(0, 0, 0, 0)", dirty: false, stroke: "#000", …}
3: {id: "shapeSideColor", d: "M400.5,60.5v25c0", stroke: "#000", dirty: false, strokeMiterLimit: 10, …}
Then, after ungrouping the object, the object.paths attribute looks like this...
paths: Array(4)
0: klass {type: "path", originX: "left", originY: "top", left: 0.4999999999999982, top: 0.5, …}
1: klass {type: "path", originX: "left", originY: "top", left: 0.5, top: 60.5, …}
2: klass {type: "path", originX: "left", originY: "top", left: 0.4999999999999982, top: 0.5, …}
3: klass {type: "path", originX: "left", originY: "top", left: 0.5, top: 60.5, …}
This breaks some functions that use the 'shapeTopColor and 'shapeSideColor' ids to change fill attributes for each path. So if a user groups an object, then ungroups it, they are no longer able to change the colour of the object.
Here is the code I am using to group objects...
export function groupSelectedItems() {
canvas = document.getElementById("c").fabric;
var activegroup = canvas.getActiveGroup();
var objectsInGroup = activegroup.getObjects();
var objectIds = [];
activegroup.clone(function(newgroup) {
canvas.discardActiveGroup();
objectsInGroup.forEach(function(object) {
objectIds.push({
'id':object.id,
'componentType':object.componentType,
'shape': object.shape,
// paths = object.paths //Tried this but causes errors.
});
canvas.remove(object);
});
newgroup.setControlsVisibility({'tl': false, 'tr': false, 'bl': false, 'br': false, 'ml': false, 'mr': false, 'mb': false, 'mt': false});
canvas.add(newgroup);
//Store the objects id's on to a session variable.
Session.set('objectIds', objectIds);
//put original objects id's back onto the new groups objects respectively.
var objectsInNewGroup = newgroup.getObjects();
objectsInNewGroup.forEach(function(object, key) {
Session.get('objectIds').forEach(function(o, i) {
if (key == i) {
object.id = o.id
object.componentType = o.componentType,
object.shape = o.shape
// object.paths = o.paths
}
});
});
});
}
So my question is, how can I clone an object or group and not lose any custom attributes I have set?
DEMO
var canvas = new fabric.Canvas('c');
var rect1 = new fabric.Rect({
id: 1,
width: 100,
height: 100,
fill: 'red',
componentType: 'a1',
shape: 'round1'
});
var rect2 = new fabric.Rect({
id: 2,
left:10,
top:20,
width: 100,
height: 100,
fill: 'magenta',
componentType: 'a2',
shape: 'round2'
});
var rect3 = new fabric.Rect({
id: 3,
left:30,
top:30,
width: 100,
height: 100,
fill: 'yellow',
componentType: 'a3',
shape: 'round3'
});
var group = new fabric.Group([rect1, rect2, rect3]);
canvas.add(group)
canvas.setActiveObject(group);
function cloneObj() {
group.clone(function(newgroup) {
canvas.add(newgroup.set({
left: newgroup.left + 10,
top: newgroup.top + 10
}));
console.log(newgroup);
}, ['id', 'componentType', 'shape']);
}
canvas {
border: 1px solid #999;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<button onclick='cloneObj()'>Clone</button>
<canvas id="c" width="700" height="400"></canvas>
clone accepts a callback and array for additional property to include in cloned object.
Use this code to save and get custom properties at initialization of component
fabric.Object.prototype.toObject = (function (toObject) {
return function (propertiesToInclude) {
propertiesToInclude = (propertiesToInclude || []).concat(
["data", "name", "lockRotation"] // custom attributes
);
return toObject.apply(this, [propertiesToInclude]);
};
})(fabric.Object.prototype.toObject);

Text overlapping in jointjs shape with multiple tspan tags

I have a custom jointjs Rect shape which displays text with 2 different font sizes:
twoTextRect= joint.shapes.basic.Generic.extend({
markup: '<g class="rotatable"><g class="scalable"><rect/></g><text><tspan class="word1"></tspan><tspan class="word2"></tspan></text></g>',
defaults: joint.util.deepSupplement({
type: 'twoTextRect',
attrs: {
rect: { fill: 'white', stroke: 'black', 'stroke-width': 1, 'follow-scale': true, width: 160, height: 160},
text: { ref: 'rect', 'font-size':20},
'.word1': { 'fill':'red'},
'.word2': { 'fill':'blue'}
},
size: { width: 160, height: 35 }
}, joint.shapes.basic.Generic.prototype.defaults)});
Then I create my shape instance and set the text for word1 and word2:
var rect3 = new twoTextRect();
rect3.attr('.word1/text' , 'aaa');
rect3.attr('.word2/text', 'bbbbbb');
I created it according to this answer: 2 different font sizes for text inside basic Rect, jointjs
The problem is that the text I set in word1 and word2 overlaps instead of being displayed in a raw, word1 and word2 both start at the begining of the rect instead of appearing one after the other... What am I doing wrong?
Thanks!
Try to position the texts using below or similar options so that they don't overlap
'.word1': { 'fill':'red', ref-x:0.5, ref-y:1},
'.word2': { 'fill':'blue', , ref-x:0.5, ref-y:1}

Change color of JointJS Rappid object (via modifying different instance in the left-nav menu and also modify via Rappid Inspector color picker)?

I basically want to change the color of a JointJS shape in their RAPPID environment I'm trialing.
Here is some background on what I have so far and what I want to achieve:
I have a JointJS shape called ChristmasTree that extends joint.dia.Element.
Basically the svg string that builds this ChristmasTree object is a path of the tree (svg path element), and multiple ornaments on it (4 svg circles/ellipses which have id's and classes that I would assume I can use to modify the colors).
I set some initial values for the ornaments via the style attr inside the svg string.
Once I place that in the left RAPPID menu, the user can drag that one Christmas Tree with red balls on it, yay.
Question ###1:
But I would like to place a 2nd ChristmasTree shape in the left menu without creating another main object that has Green balls... How would I achieve this?
In my below code, christmas_tree_style_2 should override the .ornament class with
'fill': "#00ff00", but doesn't (in the left menu, its still red)?
In fact, christmas_tree_style_1, i also tried to override with a Blue ball 'fill': "#0000ff", but its still red.
How can I achieve a left-nav override of the shape?
Question ###2:
Pretend you help me resolve previous issue. You can drag and drop 2 multiple color ChristmasTree's from the left-menu nav into the main RAPPID content area.
I'd like to now change the colors dynamically through the inspector.
I added a color inspector that shows up in the RAPPID right-nav menu for every element with:
'ornament_fill': { label: 'Color of christmas-tree-ornament's fill', type: 'color', group: 'generalinfo', index: 2 }
But not sure how to create an event to dynamically change the color of the ornaments. Any idea? Thanks!
Here is the important part's of the code below.
And also here is an actual working example (but the left-nav initial override and right-nav Inspector color override dont work, hence my 2 questions):
http://armyofda12mnkeys.kissr.com/rappid_christmastree/index.html
(sorry i couldnt find a CDN for rappid.js to add to JSFiddle so it was easier to upload the folder to a site). The applicable files to my question are app.js and rappid-custom-shapes.js.
#
//svg for Christmas Tree + its ornaments... will be added to ChristmasTree shape/obj below
var ChristmasTreeSVGstr = "<g class="rotatable"><g class="scalable">
...
<path id="christmastreestrokeid" class="christmastreestroke" ... />
<circle id="ornament1" class="ornament" r="24" cy="350" cx="120"
style="fill:#ff0000;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">...</circle>
<ellipse id="ornament2" class="ornament" rx="30" ry="25" cy="83" cx="231"
style="fill:#ff0000;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">...</circle>
<ellipse id="ornament3" class="ornament" rx="28" ry="38" cy="343" cx="331"
style="fill:#ff0000;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">...</circle>
<ellipse id="ornament4" class="ornament" rx="63" ry="54" cy="238" cx="230"
style="fill:#ff0000;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">...</circle>
</g></g>";
#
//default Christmas Tree
joint.shapes.basic.ChristmasTree = joint.dia.Element.extend({
markup: ChristmasTreeSVGstr,
defaults: joint.util.deepSupplement({
type: "basic",
name: 'Initial Christmas Tree label text',
size: {
width: 20,
height: 20
},
attrs: {
".christmastreestroke": {
stroke: 'green',
'stroke-width': '100px',
'fill-opacity': 1,
width: 10,
height: 15
},
".ornament": {
'fill': "#00ff00",
'fill-opacity': 1
},
"#ornament3": {
'fill': "#0000FF",
'stroke': "green",
'stroke-width': '5px',
'fill-opacity': .5
}
}
}, joint.dia.Element.prototype.defaults)
});
#
//RAPPID left menu christmas trees (2 variations of the initial ChristmasTree object, so I need to override some of the colors)
var christmas_tree_style_1 = new joint.shapes.basic.ChristmasTree({
position: { x: 0, y: 0 },
size: {
width: 40,
height: 50
},
attr: {
".christmastreestroke": {
stroke: 'green',
'stroke-width': '100px',
'fill-opacity': 1,
},
".ornament": {
'fill': "#0000ff",
'fill-opacity': 1
},
"#ornament3": {
'fill': "#0000FF",
'stroke': "green",
'stroke-width': '5px',
'fill-opacity': .5
}
}
});
var christmas_tree_style_2 = new joint.shapes.basic.ChristmasTree({
position: { x: 0, y: 0 },
size: {
width: 40,
height: 50
},
attr: {
".christmastreestroke": {
stroke: 'blue',
'stroke-width': '100px',
'fill-opacity': 1,
},
".ornament": {
'fill': "#00ff00",
'fill-opacity': 1
},
"#ornament3": {
'fill': "yellow",
'stroke': "yellow",
'stroke-width': '5px',
'fill-opacity': 1
}
}
});
//add to left menu
stencil.load([christmas_tree_style_1, christmas_tree_style_2], 'customchristmastrees');
#
//add it to the inspector
function createInspector(cellView) {
if (!inspector || inspector.options.cellView !== cellView) {
if (inspector) {
// Set unsaved changes to the model and clean up the old inspector if there was one.
inspector.updateCell();
inspector.remove();
}
//if(cellView.$el.hasClass('class')) // certain element should get certain things more in inspector?
//how to determine different shapes?
inspector = new joint.ui.Inspector({
inputs: {
'name': { label: 'Name of Christmas Tree', type: 'text', group: 'generalinfo', index: 1 },
'ornament_fill': { label: 'Color of christmas-tree-ornaments fill', type: 'color', group: 'generalinfo', index: 2 },
},
groups: {
generalinfo: { label: 'General Info', index: 1 },
},
cellView: cellView
});
$('.inspector-container').html(inspector.render().el);
}
}
Question #1: you need to remove style properties from the markup if you want to change them through the attr property on the element. I removed the fill, stroke, stroke-width from the markup:
var ChristmasTreeSVGstr = "<g class="rotatable"><g class="scalable">
...
<path id="christmastreestrokeid" class="christmastreestroke" ... />
<circle id="ornament1" class="ornament" r="24" cy="350" cx="120"
style="fill-rule:evenodd;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">...</circle>
<ellipse id="ornament2" class="ornament" rx="30" ry="25" cy="83" cx="231"
style="fill-rule:evenodd;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">...</circle>
<ellipse id="ornament3" class="ornament" rx="28" ry="38" cy="343" cx="331"
style="fill-rule:evenodd;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">...</circle>
<ellipse id="ornament4" class="ornament" rx="63" ry="54" cy="238" cx="230"
style="fill-rule:evenodd;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">...</circle>
</g></g>";
then you can customize the shape as follows:
var rb = new joint.shapes.basic.ChristmasTree({
position: { x: 50, y: 50 },
size: { width: 100, height: 150 },
attrs: {
".ornament": {
'fill-opacity': 1,
stroke: 'pink',
'stroke-width': 5,
fill: 'blue'
}
}
});
Question #2:
in case you sorted out the issue #1 you can simply sync the values directly with the attrs:
inspector = new joint.ui.Inspector({
inputs: {
'name': { label: 'Name of Christmas Tree', type: 'text', group: 'generalinfo', index: 1 },
attrs: {
'.ornament': {
fill: {
label: 'Color of christmas-tree-ornaments fill',
type: 'color',
group: 'generalinfo',
index: 2
}
}
},
},
groups: {
generalinfo: { label: 'General Info', index: 1 },
},
cellView: cellView
});

JointJS creating custom Shapes, Diamond, Hexagon

I m new to jointJS, I need to create custom shapes using JointJS, I have tried creating the diamond shape using the Rectangle, making its height and width same, and then rotate by 45 degrees as follows,
var diamond = new joint.shapes.basic.Rect({
position: { x: 100, y: 100 },
size: { width: 100, height: 100 },
attrs: { diamond: { width: 100, height: 30 } }
});
diamond.attr({
rect: { fill: '#cccccc', 'stroke-width': 2, stroke: 'black' },
text: {
text: 'Diamond', fill: '#3498DB',
'font-size': 18, 'font-weight': 'bold',
'font-variant': 'small-caps',
'text-transform': 'capitalize'
}
});
diamond.rotate(45);
However, the text present inside the rectangle also gets rotated, Any Ideas how can i proceed.... Also I need to create hexagon with a label... Any help will be much appreciated ....
Thanks In Advance,
Mayuri
There is no need to rotate the whole element. Try to add a transform attribute to joint.dia.basic.Rect model.
rect: { transform: 'rotate(45)' }
The other option would be to use joint.dia.basic.Path model.
var diamond = new joint.shapes.basic.Path({
size: { width: 100, height: 100 },
attrs: {
path: { d: 'M 30 0 L 60 30 30 60 0 30 z' },
text: {
text: 'Diamond',
'ref-y': .5 // basic.Path text is originally positioned under the element
}
}
});
In order to achieve a hexagon shape, use the joint.dia.basic.Path model again, but this time use the following path data.
path: { d: 'M 50 0 L 0 20 0 80 50 100 100 80 100 20 z'}
Last but least you can create a custom shape with SVG Polygon in its markup.
Thanks a lot Roman, I followed first solution for diamond and it worked liked a charm!!
here is this for any one looking to make diamond shape using joint.js, I have added the following in joint.js
joint.shapes.basic.Diamond = joint.shapes.basic.Generic.extend({
markup: '<g class="rotatable"><g class="scalable"><rect/></g><text/></g>',
defaults: joint.util.deepSupplement({
type: 'basic.Rect',
attrs: {
'rect': { fill: '#FFFFFF', stroke: 'black', width: 1, height: 1,transform: 'rotate(45)' },
'text': { 'font-size': 14, text: '', 'ref-x': .5, 'ref-y': .5, ref: 'rect', 'y-alignment': 'middle', 'x-alignment': 'middle', fill: 'black', 'font-family': 'Arial, helvetica, sans-serif' }
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
And for its implementation as follows,
var diamond = new joint.shapes.basic.Diamond({
position: { x: 100, y: 100 },
size: { width: 100, height: 100 },
attrs: { diamond: { width: 100, height: 30 } }
});
diamond.attr({
rect: { fill: '#cccccc', 'stroke-width': 2, stroke: 'black' },
text: {
text: 'Diamond', fill: '#3498DB',
'font-size': 18, 'font-weight': 'bold',
'font-variant': 'small-caps',
'text-transform': 'capitalize'
}
});

How to implement a flowlayout in ExtJS 4?

I want to use a layout where components are added horizontally like hbox but where upon adding if the component exceed the bounds of the container it is moved to the next line. This is similar to what FlowLayout is in swing and flex.
I couldn't find any layout in ExtJS 4.0 that would achieve this.
So I am wondering how I would go about doing this. I am fairly new to the framework so any pointers would be great.
Have you tried using ColumnLayout? If you don't specify the "columnWidth" properties, the child elements are css-floated from left to right:
Ext.create('Ext.Panel', {
width: 500,
height: 280,
title: "ColumnLayout Panel",
layout: 'column',
renderTo: document.body,
items: [{
xtype: 'panel',
title: 'First Inner Panel',
width: 250,
height: 90
},{
xtype: 'panel',
title: 'Second Inner Panel',
width: 200,
height: 90
}, {
xtype: 'panel',
title: 'Third Inner Panel',
width: 150,
height: 90
}]
});

Resources