首页 > 代码库 > mxGraph实现鱼骨图(因果图)

mxGraph实现鱼骨图(因果图)

        鱼骨图由日本管理大师石川馨先生所发明,故又名石川图。鱼骨图是一种发现问题“根本原因”的方法,它也可以称之为“Ishikawa”或者“因果图”。其特点是简捷实用,深入直观。它看上去有些象鱼骨,问题或缺陷(即后果)标在"鱼头"外。在鱼骨上长出鱼刺,上面按出现机会多寡列出产生生产问题的可能原因,有助于说明各个原因之间如何相互影响。

       这玩意儿就体现了一个什么5w1h的管理方法,经过我将近4周的时间捣鼓。由于原先想实现的过于复杂(主要由于本人数学知识浅薄),后来放弃了原先的两个方案。一是分支斜线扩展;二是分支上下、左右扩展。

       不知道写点个啥,直接上代码吧。

/**
 * Created by numen_huang on 2014/8/6.
 */
$(function () {
    if (!mxClient.isBrowserSupported()) {
        // Displays an error message if the browser is not supported.
        mxUtils.error('Browser is not supported!', 200, false);
    }
    else {
        document.oncontextmenu = function () {
            return false;
        }
        var fish=new fishBone();
        fish.init();
    }
});
var fishBone=function(){};

fishBone.prototype.graph=null;

fishBone.prototype.init=function(){
    this.resetSourceCode();
    this.buildCanvas();
    this.setCanvasStyle();
    this.buildContextMenu();
    this.createFishBone();
};

fishBone.prototype.resetSourceCode=function(){
    var _this=this;
    mxCellRenderer.prototype.createLabel = function(state, value) {
        var graph = state.view.graph;
        var isEdge = graph.getModel().isEdge(state.cell);
        if (state.style[mxConstants.STYLE_FONTSIZE] > 0 || state.style[mxConstants.STYLE_FONTSIZE] == null) {
            var isForceHtml = (graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value))) && graph.dialect == mxConstants.DIALECT_SVG;
            var h='';
            var spacingRight=-30;
            var spacingLeft=1;
            if(state.cell.id=='fishboneHead'){
                h='horizontal';
                spacingRight=spacingLeft=0;
            }
            if(state.cell.level%2==0 && state.cell.id!='fishboneHead'){
                h='horizontal';
                if(_this.getSelfTopParent(state.cell).direction=='bottom') {
                    spacingLeft = -25;
                }else{
                    spacingLeft = 0;
                }
            }
            var background=state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR];
            var borderColor=state.style[mxConstants.STYLE_LABEL_BORDERCOLOR];
            var color=state.style[mxConstants.STYLE_FONTCOLOR];
            if(state.cell.level==1){
                background='#B3D9D9';
                borderColor='#005757';
                color='#336666';
            }else{
                background='#F3F3F3';
                borderColor='#005757';
                color='#336666';
            }
            state.text = new mxText(value, null, (state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER),
                graph.getVerticalAlign(state),color,
                state.style[mxConstants.STYLE_FONTFAMILY], state.style[mxConstants.STYLE_FONTSIZE],
                state.style[mxConstants.STYLE_FONTSTYLE], state.style[mxConstants.STYLE_SPACING],
                spacingLeft, 10,
                spacingRight, 10, h,
                background, borderColor,
                graph.isWrapping(state.cell), graph.isLabelClipped(state.cell), state.style[mxConstants.STYLE_OVERFLOW]);
            state.text.opacity = state.style[mxConstants.STYLE_TEXT_OPACITY];
            state.text.dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML: state.view.graph.dialect;
            this.initializeLabel(state);
            var getState = function(evt) {
                var result = state;
                if (mxClient.IS_TOUCH) {
                    var x = mxEvent.getClientX(evt);
                    var y = mxEvent.getClientY(evt);
                    var pt = mxUtils.convertPoint(graph.container, x, y);
                    result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
                }
                return result;
            };
            var md = (mxClient.IS_TOUCH) ? 'touchstart': 'mousedown';
            var mm = (mxClient.IS_TOUCH) ? 'touchmove': 'mousemove';
            var mu = (mxClient.IS_TOUCH) ? 'touchend': 'mouseup';
            mxEvent.addListener(state.text.node, md, mxUtils.bind(this,
                function(evt) {
                    if (this.isLabelEvent(state, evt)) {
                        graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
                    }
                }));
            mxEvent.addListener(state.text.node, mm, mxUtils.bind(this,
                function(evt) {
                    if (this.isLabelEvent(state, evt)) {
                        graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
                    }
                }));
            mxEvent.addListener(state.text.node, mu, mxUtils.bind(this,
                function(evt) {
                    if (this.isLabelEvent(state, evt)) {
                        graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
                    }
                }));
            mxEvent.addListener(state.text.node, 'dblclick', mxUtils.bind(this,
                function(evt) {
                    if (this.isLabelEvent(state, evt)) {
                        graph.dblClick(evt, state.cell);
                        mxEvent.consume(evt);
                    }
                }));
        }
    };
    mxTriangle.prototype.redrawPath = function(path, x, y, w, h) {
        if (this.direction == mxConstants.DIRECTION_NORTH) {
            path.moveTo(0, h);
            path.lineTo(0.5 * w, 0);
            path.lineTo(w, h);
        } else if (this.direction == mxConstants.DIRECTION_SOUTH) {
            path.moveTo(0, 0);
            path.lineTo(0.5 * w, h);
            path.lineTo(w, 0);
        } else if (this.direction == mxConstants.DIRECTION_WEST) {
            path.moveTo(0, 0);
            path.lineTo(w, 0.5 * h);
            path.lineTo(0, h);
        }
        else if(this.direction == 'east'){
            path.moveTo(0, 0);
            path.quadTo(w, 0.25 * h,w+1,0.5*h);
            path.quadTo(w+1, 0.75 * h,0,h);
        }
        else{
            path.moveTo(0, 0);
            path.lineTo(w, 0.5 * h);
            path.lineTo(0, h);
        }
        path.close();
    };
    mxGraphSelectionModel.prototype.singleSelection = true;
    mxGraph.prototype.foldingEnabled=false;
    mxGraph.prototype.selectCellForEvent = function(cell, evt) {
        if(cell.id!='fishboneEnd' && cell.id!='fishboneBody' && !cell.isEdge()) {
            var isSelected = this.isCellSelected(cell);
            if (this.isToggleEvent(evt)) {
                if (isSelected) {
                    this.removeSelectionCell(cell);
                } else {
                    this.addSelectionCell(cell);
                }
            } else if (!isSelected || this.getSelectionCount() != 1) {
                this.setSelectionCell(cell);
            }
        }
    };
    mxGraph.prototype.moveCells = function(cells, dx, dy, clone, target, evt) {
        if(cells==null || cells.length>1){
            return false;
        }
        var cell=cells[0];

        if (dx != 0 || dy != 0 || clone || target != null) {
            this.getModel().beginUpdate();
            try {
                if (clone) {
                    return false;
                }
                if(cell.level==1){
                    var fishHead=_this.getCellById('fishboneHead');
                    var fishBody=_this.getCellById('fishboneBody');
                    var fishEnd=_this.getCellById('fishboneEnd');
                    if(cell.geometry.x+dx>fishHead.geometry.x-100){
                        cell.geometry.x+=dx;
                        var size=cell.geometry.x-fishHead.geometry.x;
                        fishHead.geometry.x+=size+100;
                        fishBody.geometry.width+=size+100;
                    }
                    else if(cell.geometry.x+dx<fishEnd.geometry.x+fishEnd.geometry.width+100){
                        cell.geometry.x+=dx;
                        var size=cell.geometry.x-(fishEnd.geometry.x+fishEnd.geometry.width);
                        fishBody.geometry.width+=Math.abs(size-100);
                        fishEnd.geometry.x+=size-100;
                        fishBody.geometry.x+=size-100;
                    }
                    else{
                        cell.geometry.x+=dx;
                    }
                }
                else if(cell.id=='fishboneHead'){
                    var fishHead=_this.getCellById('fishboneHead');
                    var fishBody=_this.getCellById('fishboneBody');
                    var children=_this.getChildSubject(fishHead);

                    var maxX=children.length>0?children[0].geometry.x:fishHead.geometry.x-30;
                    for(var i=0;i<children.length;i++){
                        if(children[i].geometry.x>maxX){
                            maxX=children[i].geometry.x;
                        }
                    }
                    var oldX=fishHead.geometry.x;
                    fishHead.geometry.x+=dx;
                    var size=fishHead.geometry.x-oldX;
                    if(fishHead.geometry.x<maxX+100){
                        fishHead.geometry.x=maxX+100;
                        size = fishHead.geometry.x-oldX;
                    }
                    fishBody.geometry.width+=size;
                }
                else if(cell.id=='fishboneEnd'){
                    var fishHead=_this.getCellById('fishboneHead');
                    var fishBody=_this.getCellById('fishboneBody');
                    var fishEnd=_this.getCellById('fishboneEnd');
                    var children=_this.getChildSubject(fishHead);
                    var minX=children.length>0?children[0].geometry.x:fishEnd.geometry.x-100;
                    for(var i=0;i<children.length;i++){
                        if(children[i].geometry.x<minX){
                            minX=children[i].geometry.x;
                        }
                    }
                    var oldX=fishEnd.geometry.x;
                    fishEnd.geometry.x+=dx;
                    var size=fishEnd.geometry.x-oldX;
                    if(fishEnd.geometry.x>minX-100){
                        fishEnd.geometry.x=minX-100;
                        size=fishEnd.geometry.x-oldX;
                    }
                    fishBody.geometry.x=fishEnd.geometry.x+fishEnd.geometry.width;
                    fishBody.geometry.width-=size;
                }
                else if (cell.direction == 'top' || cell.direction == 'bottom') {
                    if(cell.parent.children.length==1){
                        cell.geometry.x=0;
                        cell.parent.geometry.width-=dx;
                        cell.parent.geometry.x+=dx;
                        if(cell.parent.geometry.width<=70){
                            cell.parent.geometry.width=70;
                            cell.parent.geometry.x=-70;
                        }
                    }
                    else{
                        if(cell.geometry.x==0){
                            if(dx<0){
                                for(var i=0;i<cell.parent.children.length;i++){
                                    if(cell.parent.children[i].id!=cell.id){
                                        cell.parent.children[i].geometry.x-=dx;
                                    }
                                }
                                cell.parent.geometry.width-=dx;
                                cell.parent.geometry.x+=dx;
                                cell.geometry.x=0;
                            }
                            else{
                                if(cell.parent.children.length==1){
                                    if(cell.geometry.x+dx<cell.parent.geometry.width-70){
                                        cell.geometry.x+=dx;
                                    }else{
                                        cell.geometry.x=cell.parent.geometry.width-70;
                                    }
                                }
                                else{
                                    var children=cell.parent.children;
                                    var temp1=children[0];//最小y坐标的cell
                                    var t=0;
                                    for (var i = 0; i < children.length; i++) {
                                        if (children[i].geometry.x < temp1.geometry.x) {
                                            temp1 = children[i];
                                        }
                                        if(children[i].geometry.x==0){
                                            t++;
                                        }
                                    }
                                    var temp2=null;//第二小x坐标的cell
                                    for (var i = 0; i < children.length; i++) {
                                        if (children[i].geometry.x > cell.geometry.x && !temp2) {
                                            temp2=children[i];
                                            continue;
                                        }else if(temp2) {
                                            if (children[i].geometry.x < temp2.geometry.x && children[i].id != cell.id) {
                                                temp2 = children[i];
                                            }
                                        }
                                    }
                                    if(temp2 && t==1) {
                                        //没有超过第二小x坐标
                                        if (temp2.geometry.x - dx > 0) {
                                            for (var k = 0; k < cell.parent.children.length; k++) {
                                                if (cell.parent.children[k].id != cell.id) {
                                                    cell.parent.children[k].geometry.x -= dx;
                                                }
                                            }
                                            cell.geometry.x += dx;
                                            cell.parent.geometry.width -= dx;
                                            cell.parent.geometry.x += dx;
                                            cell.geometry.x = 0;
                                        }
                                        //超过第二小x坐标
                                        else {
                                            if(cell.parent.geometry.width-temp2.geometry.x==70){
                                                for (var k = 0; k < cell.parent.children.length; k++) {
                                                    cell.parent.children[k].geometry.x=0;
                                                }
                                                cell.parent.geometry.width=70;
                                                cell.parent.geometry.x=-70;
                                            }else {
                                                var size = dx - temp2.geometry.x;
                                                var pSize = cell.parent.geometry.width - temp2.geometry.x;
                                                for (var k = 0; k < cell.parent.children.length; k++) {
                                                    if (cell.parent.children[k].id != temp2.id && cell.parent.children[k].id != cell.id) {
                                                        cell.parent.children[k].geometry.x -= temp2.geometry.x;
                                                    }
                                                }
                                                cell.parent.geometry.width -= temp2.geometry.x;
                                                cell.parent.geometry.x += temp2.geometry.x;
                                                temp2.geometry.x = 0;
                                                if(size-pSize>0){
                                                    cell.geometry.x = cell.parent.geometry.width-70;
                                                }
                                                else {
                                                    cell.geometry.x = size;
                                                }
                                            }
                                        }
                                    }
                                    else if(t>1){
                                        if(cell.geometry.x+dx<cell.parent.geometry.width-70){
                                            cell.geometry.x+=dx;
                                        }else{
                                            cell.geometry.x=cell.parent.geometry.width-70;
                                        }
                                    }
                                }
                            }
                        }
                        else{
                            if(cell.geometry.x+dx<0){
                                var size = 0-(cell.geometry.x+dx);
                                for (var i = 0; i < cell.parent.children.length; i++) {
                                    if (cell.parent.children[i].id !=cell.id) {
                                        cell.parent.children[i].geometry.x+=size;
                                    }
                                }
                                cell.parent.geometry.x-=size;
                                cell.parent.geometry.width+=size;
                                cell.geometry.x=0;
                            }
                            else if(cell.geometry.x+dx==0){
                                cell.geometry.x=0;
                            }
                            else if(cell.geometry.x+dx>0 && cell.geometry.x+dx<cell.parent.geometry.width-70){
                                cell.geometry.x+=dx;
                            }
                            else{
                                cell.geometry.x=cell.parent.geometry.width-70;
                            }
                        }
                    }
                }
                else if(cell.direction == 'left'){
                    var py = 0;
                    var myCell=cell;
                    if(cell.parent.direction=='bottom') {
                        cell.geometry.y += dy;
                        for (var i = 0; i < cell.parent.children.length; i++) {
                            if (cell.parent.children[i].geometry.y > py)
                                py = cell.parent.children[i].geometry.y;
                        }
                        if(py>70) {
                            cell.parent.geometry.height = py;
                        }else{
                            cell.parent.geometry.height = 70;
                        }
                        if(cell.geometry.y<70){
                            cell.geometry.y=70;
                        }
                    }
                    else{
                        if(cell.geometry.y==0){
                            if(dy<0) {
                                for (var i = 0; i < cell.parent.children.length; i++) {
                                    if (cell.parent.children[i].id != cell.id) {
                                        cell.parent.children[i].geometry.y -= dy;
                                    }
                                }
                                cell.parent.geometry.y += dy;
                                cell.parent.geometry.height -= dy;
                            }
                            else{
                                //只有一个子集的时候
                                if(cell.parent.children.length==1){
                                    var fishBody=_this.getCellById('fishboneBody');
                                    //拖动超过父级label的位置
                                    if(!(cell.geometry.y==0 && cell.parent.geometry.height==70)) {
                                        if (cell.parent.geometry.height - dy < 70) {
                                            if(cell.parent.parent.id==1){
                                                cell.parent.geometry.y = fishBody.geometry.y - 68;
                                            }else{
                                                cell.parent.geometry.y = cell.parent.parent.geometry.y - 70;
                                            }
                                            cell.parent.geometry.height = 70;
                                        } else {
                                            cell.parent.geometry.height -= dy;
                                            cell.parent.geometry.y += dy;
                                        }
                                        cell.geometry.y = 0;
                                    }
                                }
                                else {//超过一个子集
                                    var children=cell.parent.children;
                                    var temp1=children[0];//最小y坐标的cell
                                    var t=0;
                                    for (var i = 0; i < children.length; i++) {
                                        if (children[i].geometry.y < temp1.geometry.y) {
                                            temp1 = children[i];
                                        }
                                        if(children[i].geometry.y==0){
                                            t++;
                                        }
                                    }
                                    var temp2=null;//第二小y坐标的cell
                                    for (var i = 0; i < children.length; i++) {
                                        if (children[i].geometry.y > temp1.geometry.y && !temp2) {
                                            temp2=children[i];
                                            continue;
                                        }else if(temp2) {
                                            if (children[i].geometry.y < temp2.geometry.y && children[i].id != temp1.id) {
                                                temp2 = children[i];
                                            }
                                        }
                                    }
                                    //当没有超过第二小y坐标时。
                                    if(temp2 && t==1) {
                                        if (cell.geometry.y + dy < temp2.geometry.y) {
                                            for (var k = 0; k < cell.parent.children.length; k++) {
                                                if (cell.parent.children[k].id != cell.id) {
                                                    cell.parent.children[k].geometry.y -= dy;
                                                }
                                            }
                                            cell.parent.geometry.height -= dy;
                                            cell.parent.geometry.y += dy;
                                            cell.geometry.y = 0;
                                        }
                                        //当超过第二小y坐标时候
                                        else {
                                            cell.geometry.y += dy;
                                            var py = cell.parent.geometry.height - (cell.parent.geometry.height - temp2.geometry.y);
                                            for (var k = 0; k < cell.parent.children.length; k++) {
                                                if (cell.parent.children[k].id != temp2.id) {
                                                    cell.parent.children[k].geometry.y -= py;
                                                }
                                            }
                                            cell.parent.geometry.height -= temp2.geometry.y;
                                            cell.parent.geometry.y += temp2.geometry.y;
                                            temp2.geometry.y = 0;

                                            for (var j = 0; j < cell.parent.children.length; j++) {
                                                if (cell.parent.geometry.height - cell.parent.children[j].geometry.y < 70) {
                                                    cell.parent.children[j].geometry.y = cell.parent.geometry.height - 70;
                                                }
                                            }
                                        }
                                    }
                                    else if(t>1){
                                        if(cell.parent.geometry.height>70) {
                                            if (cell.parent.geometry.height - dy < 70) {
                                                cell.geometry.y = 70;
                                            } else {
                                                cell.geometry.y = dy;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        else{
                            if(cell.geometry.y+dy>0){
                                if(cell.parent.geometry.height-(cell.geometry.y+dy)<=70){
                                    cell.geometry.y=cell.parent.geometry.height-70;
                                }else {
                                    cell.geometry.y += dy;
                                }
                            }else{
                                for (var i = 0; i < cell.parent.children.length; i++) {
                                    if (cell.parent.children[i].id != cell.id) {
                                        cell.parent.children[i].geometry.y -= (cell.geometry.y+dy);
                                    }
                                }
                                cell.parent.geometry.y += (cell.geometry.y+dy);
                                cell.parent.geometry.height -= cell.geometry.y+dy;
                                cell.geometry.y=0;
                            }
                        }
                    }
                }
            } finally {
                this.getModel().endUpdate();
                this.refresh();
            }
        }
        return cells;
    };

    mxGraph.prototype.dblClick = function(evt, cell) {
        if(cell.id!='fishboneEnd' && cell.id!='fishboneBody' && !cell.isEdge()) {
            var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
            this.fireEvent(mxe);
            if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() && cell != null && this.isCellEditable(cell)) {
                this.startEditingAtCell(cell, evt);
            }
        }
    };
//    mxGraph.prototype.labelChanged = function(cell, value, evt) {
//        this.model.beginUpdate();
//        var geo=cell.geometry;
//        try {
//            this.cellLabelChanged(cell, value, true);
//            this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED, 'cell', cell, 'value', value, 'event', evt));
//            geo.height =geo.height+((value.length-5>0)? (value.length-5)*18:0);
//            cell.geometry=geo;
//            if(cell.level>1){
////                var myEdge = cell.edges[0];
////                var point = myEdge.geometry.points[0];
////                point.x=0;
////                myEdge.geometry.points=[point];
//            }
//        } finally {
//            this.model.endUpdate();
//        }
//        return cell;
//    };
    mxCellEditor.prototype.init = function() {
        this.textarea = document.createElement('input');
        this.textarea.className = 'mxCellEditor';
        this.textarea.style.position = 'absolute';
        this.textarea.style.overflow = 'visible';
        this.textarea.setAttribute('type', 'textbox');
        if (false) {
            this.textarea.style.resize = 'none';
        }
        mxEvent.addListener(this.textarea, 'blur', mxUtils.bind(this,
            function(evt) {
                this.stopEditing(!this.graph.isInvokesStopCellEditing());
            }));
        mxEvent.addListener(this.textarea, 'keydown', mxUtils.bind(this,
            function(evt) {
                if (!mxEvent.isConsumed(evt)) {
                    if (evt.keyCode == 113 || (this.graph.isEnterStopsCellEditing() && evt.keyCode == 13 && !mxEvent.isControlDown(evt) && !mxEvent.isShiftDown(evt))) {
                        this.graph.stopEditing(false);
                        mxEvent.consume(evt);
                    } else if (evt.keyCode == 27) {
                        this.graph.stopEditing(true);
                        mxEvent.consume(evt);
                    } else {
                        if (this.clearOnChange) {
                            this.clearOnChange = false;
                            this.textarea.value = http://www.mamicode.com/'';>



mxGraph实现鱼骨图(因果图)