1. #1
    Sencha Premium Member vadimv's Avatar
    Join Date
    Sep 2010
    Location
    Chisinau, Moldova
    Posts
    642
    Vote Rating
    25
    vadimv will become famous soon enough vadimv will become famous soon enough

      1  

    Default Ext.ux.DrawPoly

    Ext.ux.DrawPoly


    An extension of "Ext.draw.Component" which shows how to draw polygons. It's just an quick made example which probably needs some optimizations, but I thought that is ok for demonstrating ExtJS's draw capabilities and especially polygons as are the most wanted by many users. I used also Raphael(2.1.0) for some utils methods wich ExtJS doesn't have.
    Extension's features:
    - draw polygons
    - polys resize
    - polys selecting and deselecting
    - add new vertices, just by clicking on edges
    - remove vertices, just by overlapping neighboring vertices
    - cancel drawing by right clicking

    Code:
      <html><head>
        <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
        <title>Draw test</title>
        <link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />
    
    
        <script type="text/javascript" src="../../ext-all-debug.js"></script>
        <script type="text/javascript" src="test.js"></script>
        <script type="text/javascript" src="raphael-min.js"></script>
    
    
    </head>
    <body>
    </body>
    </html>
    test.js :
    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();
    });
    Attached Images

  2. #2
    Sencha - Support Team scottmartin's Avatar
    Join Date
    Jul 2010
    Location
    Houston, Tx
    Posts
    9,010
    Vote Rating
    460
    scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future

      0  

    Default


    Cool.. thanks for sharing.

    Scott.

  3. #3
    Sencha User
    Join Date
    Dec 2012
    Posts
    2
    Vote Rating
    0
    deepaganu123 is on a distinguished road

      0  

    Default can i get the entire code

    can i get the entire code


    Can I please get the entire code , Rapheal.js is not provided , I want to recreate this scenario on my own system

  4. #4
    Sencha Premium Member vadimv's Avatar
    Join Date
    Sep 2010
    Location
    Chisinau, Moldova
    Posts
    642
    Vote Rating
    25
    vadimv will become famous soon enough vadimv will become famous soon enough

      0  

    Default


    you have it, you can get raphael on related site.

  5. #5
    Sencha User
    Join Date
    Jan 2012
    Posts
    1
    Vote Rating
    0
    andre.foxsystems is on a distinguished road

      0  

    Default nice

    nice


    very nice work! thanks!