日期:2014-05-16  浏览次数:20791 次

使用HTML5技术绘制思维导图

客户常常提到思维导图,喜欢它的结构展示方式,和交互的友好,从图论的角度看,思维导图本质上是一种树,有一个根节点(主题)出发,联想到其他话题,于是分支开花,再分支,有时候也会构成网络结构,由子分支联想到另一个已有分支,但通常不破坏原有结构,常见的思维导图如下

XMind制作的思维导图



使用HTML5制作思维导图应用

这里需要用到一款HTML5图形组件-Qunee for HTML5,Qunee组件本身支持树形布局,所以对于思维导图的大体结构是可以呈现的,到细节的地方就比较多了,包括线条的走向,节点的样式,布局交互,自动吸附,双击编辑等等,本示例没办法面面俱到,所以参照XMind的效果,只实现了部分功能,供用户参考和扩展

连线定制

默认连线为直线,通过定制EdgeUI可以实现曲线效果

function FlexEdgeUI(edge, graph){
    Q.doSuperConstructor(this, FlexEdgeUI, arguments);
}
FlexEdgeUI.prototype = {
    drawEdge: function(path, fromUI, toUI, edgeType, fromBounds, toBounds){
        var from = fromBounds.center;
        var to = toBounds.center;
        var cx = (from.x + to.x) / 2;
        var cy = (from.y + to.y) / 2;
        path.curveTo(from.x, cy, cx, to.y);
    }
}
Q.extend(FlexEdgeUI, Q.EdgeUI);

使用方式

通过图元的#uiClass属性,与定制的EdgeUI相关联

function createEdge(name, from, to){
    var edge = graph.createEdge(name, from, to);
    edge.uiClass = FlexEdgeUI;
}

曲线效果

定制弯曲连线效果



布局定制
Qunee提供的树形布局非常强大,这里不需要太多设置就可以完成类似思维导图的布局,下面的代码设置了布局过滤器,用于控制某些节点不参与布局,代码中的控制逻辑是分离图元不参与布局,这样当图元脱离思维导图树时,将不参与自动布局处理

var layouter = new Q.TreeLayouter(graph);
layouter.isLayoutable = function(node, from){
    return node == ROOT || node.host != null;
}
layouter.vGap = 20;

下面的代码,设置主节点的布局类型为两边分布,方向为居中

ROOT.parentChildrenDirection = Q.Consts.DIRECTION_MIDDLE;
ROOT.layoutType = Q.Consts.LAYOUT_TYPE_TWO_SIDE;

布局效果如下

树形布局效果



交互定制
交互定制相对复杂些,包括拖拽过程中的动态连接以及双击编辑等

动态连接效果

需要判断何时脱离,何时连接,寻找合适的连接对象,下面是拖拽交互代码,监听节点拖拽事件,动态查找就近的连接节点,具体的代码请参看http://demo.qunee.com/#Mind Mapping Demo

graph.interactionDispatcher.addListener(function(evt){
    if(evt.kind == Q.InteractionEvent.ELEMENT_MOVING && evt.data){
        var node = evt.data;
        var host = findNearNode(node);
        if(node.host == host){
            return;
        }
        if(node.host){
            unlinkToParent(node);
        }
        if(host){
            linkToParent(node, host);
        }
    }else if(evt.kind == Q.InteractionEvent.ELEMENT_MOVE_END && evt.data){
        layouter.doLayout();
    }
})

双击编辑

双击编辑用到LabelEditor类,监听Graph组件的双击事件,当双击节点时,显示文本编辑框

graph.ondblclick = function(evt){
    var element = graph.getElementByMouseEvent(evt);
    if(element){
        var xy;
        if(element instanceof Q.Node){
            xy = graph.toCanvas(element.x, element.y);
            xy = localToGlobal(xy.x, xy.y, graph.html);
        }else{
            xy = getPageXY(evt);
        }
        labelEditor.startEdit(xy.x, xy.y, element.name, graph.getStyle(element, Q.Styles.LABEL_FONT_SIZE), function(text){
            element.name = text;
        });
        return;
    }
    var xy = graph.getLogicalPointByMouseEvent(evt);
    var newItem = createText("新项目", xy.x, xy.y);
    graph.selectionModel.select(newItem);
}

编辑效果
双击编辑文本