Code:
Ext.onReady(function() {
Ext.namespace('Ext', 'Ext.ux');
Ext.namespace('Draw', 'Draw.util');
Draw.util = {
pathToCoords: function(path){
var coords = Ext.isArray(path) ? path.toString(): path;
coords = '(' + coords.substr(1,coords.length-2)+ ')';
coords = coords.replace(/L+/g,'),(');
return coords;
},
coordsToPath: function(coords){
var path = coords;
path = 'M' + path.substr(2,path.length-4)+ 'Z';
path = path.replace(/\),\(/g,'L');
return path;
},
getMouseEvButton: function(e){
var evt = (e==null ? event:e),
clickType = 'LEFT';
if (evt.which) {
if (evt.which==3) clickType='RIGHT';
else if (evt.which==2) clickType='MIDDLE';
}
else if (evt.button) {
if (evt.button==2) clickType='RIGHT';
else if (evt.button==4) clickType='MIDDLE';
}
return clickType;
}
}
Ext.define('Ext.ux.DrawPoly', {
extend: 'Ext.draw.Component',
closeMarge: 15,
currentMode: 'addsprite',
style: {
//'opacity' : '0.7',
'position': 'absolute',
'z-index' : 0
},
initComponent: function(){
var me = this;
me.on({
click : me.onMouseClick,
mouseup: me.onDragStop,
mousedown: me.onDragStart,
afterrender: me.onAfterRender
});
},
onAfterRender : function(){
var me = this,
delegate = 'circle';
// Fix for IE8's VML
if(Ext.isIE8)
delegate = 'shape';
me.mon(me.getEl(), {
click : {
fn : me.onVertexMouseClick,
delegate: delegate
},
contextmenu : {
fn: me.onContextMenu,
preventDefault: true
},
scope: me
});
},
onMouseMove : function(e){
var me = this;
if(me.currentMode == 'edit'){
me.edit(e);
}else
if(me.currentMode == 'drag'){
me.drag(e);
}
},
onDragStop : function(e){
var me = this;
if(me.currentMode == 'drag'){
me.currentMode = 'selected';
// Apply translations to path coordinates
me.selected.setAttributes({
translate: {x:0,y:0},
path: me.applyPathTrans(me.selected)
},true);
// Apply translations to sprites from vertices group
var items = me.surface.getGroup('vertices').items;
for (var i = 0, length = items.length; i < length; i++) {
var item = items[i];
if(item.type == 'circle'){
item.setAttributes({
x: item.attr.x + item.attr.translation.x,
y : item.attr.y + item.attr.translation.y,
translate: {x:0,y:0}
},true);
}
};
me.fireEvent('positionchange',me.selected);
}
},
onDragStart : function(e){
var me = this,
xy = e.getXY(),
position = me.getMousePosition(e),
button = Draw.util.getMouseEvButton(e),
item = me.surface.items.get(e.target.id);
e.preventDefault();
if(button == 'LEFT'){
// If there's a poly selected and mousedown wasn't on draw surface
// then check for new vertex and prepare dragging start
if(me.currentMode == 'selected' && item && me.selected.id == item.id) {
me.isNewVertex(me.selected,new Array(position.x,position.y));
me.prev = me.surface.transformToViewBox(xy[0],xy[1]);
me.currentMode = 'drag';
}
if(!me.hasListener('mousemove'))
me.on('mousemove',me.onMouseMove);
}
},
onContextMenu: function(e){
var me = this,
item = me.surface.items.get(e.target.id);
if(me.currentMode == 'edit'){
me.cancelEdit();
}else
// if right click was on a poly then show context menu for it
if(item){
me.currentMode = 'select';
me.onMouseClick(e);
me.fireEvent('polymapctxmenu',e.getXY());
}
},
cancelEdit : function(){
var me = this;
if(me.currentMode == 'edit'){
me.selected.destroy();
me.selected = null;
me.currentMode = 'addsprite';
}
},
onMouseClick: function(e){
var me = this,
position = me.getMousePosition(e),
targetEl = Ext.get(e.target),
targetItem = me.surface.items.get(targetEl.id);
// If mousedown on path and is not editing mode then select that path
if(targetItem && targetItem.type == 'path' && me.currentMode != 'edit')
me.currentMode = 'select';
switch(me.currentMode){
case 'addsprite' : {
if(me.selected == null ) {
poly = Ext.create('Ext.draw.Sprite',{
type: 'path',
path: 'M1,1',
fill: '#7ff482',
"stroke-width": 3,
opacity: 1,
stroke: '#000000'
});
me.surface.add(poly).show();
}
poly.path = 'M'+position.x.toString() + ',' + position.y.toString();
poly.setAttributes({path: poly.path},true);
me.currentMode = 'edit';
me.selected = poly;
} break;
case 'edit' : {
var selected = me.selected,
segments = Raphael.parsePathString(selected.attr.path);
// If last point is first point then close the path
if(Math.abs(position.x - segments[0][1]) < me.closeMarge && Math.abs(position.y - segments[0][2]) < me.closeMarge){
// if it's at least a triangle, then close it.
if(segments.length > 3) {
selected.path += 'Z';
selected.setAttributes({path: selected.path},true);
me.currentMode = 'selected';
me.selectSprite(selected);
me.fireEvent('create',selected);
}
} else
selected.path = selected.attr.path;
} break;
case 'selected' : {
// Deselect if was selected
var source = targetEl.dom.nodeName;
if(source != 'circle' && source != 'shape') {
me.deselect();
}
} break;
case 'select' : {
// Select sprite if was clicked
if(me.selected && me.selected.id != targetEl.id){
me.deselect();
me.selectSprite(targetItem);
}else
if(me.selected == null){
me.selectSprite(targetItem);
}
me.currentMode = 'selected';
} break;
}
},
onVertexMouseClick: function(e){
var me = this,
el = Ext.get(e.target),
item = me.surface.items.get(el.id);
//Fix for IE8
if(Ext.isIE8 && item.type != 'circle'){
return;
}
if (me.currentMode == 'selected') {
me.currentMode = 'reshape';
me.reshapeTargetId = el.id;
me.on('mousemove',me.onVertexMouseMove,me);
}else
if(me.currentMode == 'reshape'){
me.currentMode = 'selected';
me.reshapeTargetId = null;
me.un('mousemove',me.onVertexMouseMove,me);
//check if is needed to remove vertex
me.isRemoveVertex(item);
me.fireEvent('positionchange',me.selected);
}
},
onVertexMouseMove: function(e){
var me = this,
vertex = me.surface.items.get(this.reshapeTargetId),
poly = me.selected,
position = me.getMousePosition(e);
var segments = Raphael.parsePathString(poly.attr.path);
segments[vertex.vertexIndex][1] = position.x;
segments[vertex.vertexIndex][2] = position.y;
poly.setAttributes({path: segments},true);
vertex.setAttributes({x: position.x, y : position.y},true);
},
drag: function(e){
var xy = e.getXY(),
me = this,
sprite = me.selected,
attr = sprite.attr, dx, dy,
vertices = me.surface.getGroup('vertices');
xy = me.surface.transformToViewBox(xy[0], xy[1]);
dx = xy[0] - me.prev[0];
dy = xy[1] - me.prev[1];
if(vertices){
vertices.setAttributes({
translate: {
x: attr.translation.x + dx,
y: attr.translation.y + dy
}
}, true);
}
me.prev = xy;
},
edit: function(e){
var me = this,
position = me.getMousePosition(e);
me.selected.setAttributes({path: me.selected.path + 'L' + position.x +',' + position.y },true);
},
getMousePosition : function(e){
var evt = e.browserEvent,
x = evt.offsetX || evt.layerX,
y = evt.offsetY || evt.layerY;
if(Ext.isIE8){
var box = Ext.get(e.target.parentElement).getBox(),
xy = e.getXY();
x= xy[0] - box.x;
y= xy[1] - box.y;
}
return {x:x, y:y};
},
selectSprite: function(poly){
var me = this,
segments = Raphael.parsePathString(poly.attr.path),
parentEl =poly.el.parent();
// Adjust z-index by replacing poly's element to be the last one in the parent
if(Ext.isIE8){
poly.setStyle('z-index',10);
}else
if(poly.id != parentEl.last().id){
me.surface.remove(poly,false);
me.surface.add(poly);
poly.show(true);
}
for (var i = 0, length = segments.length-1; i < length; i++) {
me.createVertex({
x: segments[i][1],
y: segments[i][2],
translation : poly.attr.translation,
vertexIndex: i
});
};
me.surface.getGroup('vertices').add(poly);
me.selected = poly;
me.fireEvent('select',me.selected);
},
deselect: function(){
var me = this;
if(Ext.isIE8){
me.selected.setStyle('z-index','0');
}
var group = me.surface.getGroup('vertices')
group.remove(me.selected);
group.destroy();
me.selected = null;
me.currentMode = 'addsprite';
me.fireEvent('deselect');
},
createVertex: function(cfg){
var configs = {
type: 'circle',
fill: '#0FF',
stroke: '#00F',
'stroke-width': 3,
radius:6,
group: 'vertices',
style: {
'z-index':11
}
};
Ext.apply(configs,cfg);
var vertex = Ext.create('Ext.draw.Sprite',configs);
this.surface.add(vertex).show(true);
return vertex;
},
applyPathTrans: function(pathSprite){
var segments = Raphael.parsePathString(pathSprite.attr.path),
tx = pathSprite.attr.translation.x,
ty = pathSprite.attr.translation.y;
for (var i = 0, length = segments.length-1; i < length; i++){
segments[i][1] += tx;
segments[i][2] += ty;
};
return segments;
},
isNewVertex: function(pathSprite, xy){
var me = this,
segments = Raphael.parsePathString(pathSprite.attr.path),
j,i = 0,
length = segments.length-1,
shortlength = length -1;
function distance(x1,y1,x2,y2){
return Math.sqrt(Math.pow(Math.abs(x1-x2),2) + Math.pow(Math.abs(y1-y2),2));
}
for (; i < length; i++) {
i == shortlength ? j = 0 : j = i+1;
var d = distance(segments[i][1], segments[i][2], segments[j][1], segments[j][2]),
d1 = distance(segments[i][1], segments[i][2], xy[0], xy[1]),
d2 = distance(xy[0], xy[1], segments[j][1], segments[j][2]);
if(Math.abs(d- (d1+d2)) < 0.2 && d1 > 20 && d2 > 20){
var insertIndex = i+1;
Ext.Array.insert(segments,insertIndex,[['L',xy[0],xy[1]]]);
me.shiftVertices(insertIndex);
me.createVertex({
x: xy[0],
y: xy[1],
vertexIndex: insertIndex
});
pathSprite.setAttributes({path: segments},true);
break;
}
};
},
isRemoveVertex: function(vertex){
var me = this,
segments = Raphael.parsePathString(me.selected.attr.path),
isOverlapped = -1,
x = vertex.attr.x,
y = vertex.attr.y;
//if is at least triangle
if(segments.length > 4) {
if(vertex.vertexIndex == 0) {
// Check with last
if(Math.abs(segments[segments.length-1][1] - x) < 4 &&
Math.abs(segments[segments.length-1][2] - y) < 4)
isOverlapped = segments.length-1;
// Check with first
if(Math.abs(segments[1][1] - x) < 4 &&
Math.abs(segments[1][2] - y) < 4)
isOverlapped = 1;
}
else
if(vertex.vertexIndex == segments.length-2){
// Check with -1
if(Math.abs(segments[vertex.vertexIndex-1][1] - x) < 4 &&
Math.abs(segments[vertex.vertexIndex-1][2] - y) < 4)
isOverlapped = vertex.vertexIndex-1;
//Check with first
if(Math.abs(segments[0][1] - x) < 4 &&
Math.abs(segments[0][2] - y) < 4)
isOverlapped = 0;
}else{
//Check with +1
if(Math.abs(segments[vertex.vertexIndex+1][1] - x) < 4 &&
Math.abs(segments[vertex.vertexIndex+1][2] - y) < 4)
isOverlapped = vertex.vertexIndex+1;
//Check with -1
if(Math.abs(segments[vertex.vertexIndex-1][1] - x) < 4 &&
Math.abs(segments[vertex.vertexIndex-1][2] - y) < 4)
isOverlapped = vertex.vertexIndex-1;
}
if(isOverlapped != -1){
if(vertex.vertexIndex == 0)
segments[1][0] = 'M';
Ext.Array.erase(segments,vertex.vertexIndex,1);
me.unshiftVertices(vertex.vertexIndex);
me.surface.items.remove(vertex);
vertex.destroy();
me.selected.setAttributes({path: segments},true);
}
}
},
shiftVertices: function(index){
var group = this.surface.getGroup('vertices');
for (var i = 0; i < group.length; i++) {
var item = group.items[i];
if(item.type == 'circle' && item.vertexIndex >= index){
item.vertexIndex++;
}
};
},
unshiftVertices: function(index){
var group = this.surface.getGroup('vertices');
for (var i = 0; i < group.length; i++) {
var item = group.items[i];
if(item.type == 'circle' && item.vertexIndex > index){
item.vertexIndex--;
}
};
},
drawPoly: function(cfg){
var me = this,
config = {
type: 'path',
fill: '#7ff482',
"stroke-width": 3,
opacity: 1,
stroke: '#000000'
};
Ext.apply(config,cfg)
var poly = Ext.create('Ext.draw.Sprite',config);
me.surface.add(poly).show(true);
return poly;
}
});
Ext.create('Ext.window.Window', {
width: 600,
height: 400,
hidden: false,
border: false,
title: 'Draw Components',
maximizable: true,
layout: 'fit',
items: [{
xtype: 'tabpanel',
defaults: {
padding: '0 0 0 0'
},
activeItem: 0,
items: [{
xtype: 'panel',
title: 'Draw test',
layout: 'fit',
items: [ Ext.create('Ext.ux.DrawPoly')]
}]
}],
resizable: {
dynamic: true
}
}).show();
});