轻量高效的拓扑图组件
zh

使用Qunee组件展示水文观测站点

2014-02-27

中科院的某个客户遇到这样的需求:将区域内所有水文观测站信息按层次结构展现出来。原来用的是mxGraph组件,遇到几个问题:布局难以定制、节点样式难定制、交互有问题,后来改用Qunee组件,顺利解决问题。 这是一种典型应用,主要用到Qunee的布局类TreeLayouter,以及UI和交互的简单定制,下面以示例来做介绍

需求分析

按水文观测站区域与级别分五个等级,最顶层的是主站点,下面分区站点,再往下子站点,最后是实际观测点或者设备,因为站点总数量几千上万,为了便于查看,采用动态加载方式,点击站点节点的加号,动态加载子站点,并执行自动布局 最终的效果类似下面截图 树形布局效果

示例介绍

初始化图形组件

这里将Graph组件直接嵌入到document.body,并设置body尺寸布满整个窗口

var graph, canvas, root, layouter;
function init() {
    canvas = document.body;
    function updateGraphSize() {
        canvas.style.width = window.innerWidth + "px";
        canvas.style.height = window.innerHeight + "px";
        if (graph) {
            graph.moveToCenter();
        }
    }
    window.onresize = updateGraphSize;
    updateGraphSize();
    graph = new Q.Graph(canvas);
}
Q.addEventListener(window, "load", init);

树形布局 考虑到子站点数据较多,利用屏幕宽度大于高度的特点,选择从左向右分布

function init() {
    ...
    layouter = new Q.TreeLayouter(graph);
    layouter.parentChildrenDirection = Q.Consts.DIRECTION_RIGHT;
    layouter.hGap = 50;
    layouter.vGap = 40;
}

设置孩子布局方式与方向

因为第二层节点的孩子数量较多,所以采用两侧布局,其后的节点方向也保持从左向右

if (level == 1) {//设置第二层孩子布局方式
    node.layoutType = Q.Consts.LAYOUT_TYPE_TWO_SIDE;
} else if (level == 2) {//设置第三层布局方向与连线类型
    node.parentChildrenDirection = Q.Consts.DIRECTION_RIGHT;
    edge.edgeType = Q.Consts.EDGE_TYPE_HORIZONTAL_VERTICAL;
}

两侧布局效果 两侧布局

定制节点效果

根据应用需要,通过Q.Node#addUI(…)方法,对节点添加不同的图标,放置在不同位置,并关联相关属性

function createGraphNode(name, icon, canAdd, showDetail) {
    var node = graph.createNode(name);
    node.setStyle(Q.Styles.BACKGROUND_COLOR, Q.toColor(0xEEDDDDDD));
    node.setStyle(Q.Styles.PADDING, new Q.Insets(2, 5));
    node.setStyle(Q.Styles.BACKGROUND_GRADIENT, backgroundGradient);
    node.setStyle(Q.Styles.BORDER, 1);
    node.setStyle(Q.Styles.BORDER_COLOR, Q.toColor(0x555555));
    if (icon) {
        node.image = icon;
        node.setStyle(Q.Styles.LABEL_POSITION, Q.Position.RIGHT_MIDDLE);
        node.setStyle(Q.Styles.LABEL_ANCHOR_POSITION, Q.Position.LEFT_MIDDLE);
        return node;
    }
    node.image = "./empty.png";
    node.setStyle(Q.Styles.LABEL_POSITION, Q.Position.CENTER_TOP);
    node.setStyle(Q.Styles.LABEL_ANCHOR_POSITION, Q.Position.CENTER_BOTTOM);
    if (canAdd) {
        var ui = new Q.ImageUI("./add.png");
        ui.name = "add";
        ui.position = Q.Position.CENTER_BOTTOM;
        if (showDetail) {
            ui.anchorPosition = Q.Position.LEFT_TOP;
            ui.offsetX = 10;
        } else {
            ui.anchorPosition = Q.Position.CENTER_TOP;
        }
        ui.showPointer = false;
        node.addUI(ui, {
            property: "icon1",
            propertyType: Q.Consts.PROPERTY_TYPE_CLIENT,
            bindingProperty: "data"
        });
        node.set("icon1", "./add.png");
    }
    if (showDetail) {
        var ui = new Q.ImageUI("./detail.png");
        ui.name = "showDetail";
        ui.position = Q.Position.CENTER_BOTTOM;
        ui.anchorPosition = Q.Position.CENTER_TOP;
        if (canAdd) {
            ui.anchorPosition = Q.Position.RIGHT_TOP;
            ui.offsetX = -10;
        } else {
            ui.anchorPosition = Q.Position.CENTER_TOP;
        }
        ui.showPointer = false;
        node.addUI(ui);
    }
    return node;
}

呈现效果如下 节点效果

创建连线

创建连线,并指定连线类型为正交类型

function createGraphEdge(from, to) {
    var edge = graph.createEdge(from, to);
    edge.edgeType = Q.Consts.EDGE_TYPE_ORTHOGONAL_HORIZONTAL;
    edge.setStyle(Q.Styles.EDGE_SPLIT_BY_PERCENT, false);
    edge.setStyle(Q.Styles.EDGE_SPLIT_VALUE, 15);
    return edge;
}

连线效果 正交连线 交互

根据点击位置,执行不同动作

    graph.onclick = function (evt) {
        var target = graph.hitTest(evt);
        if (!target || target instanceof Q.ElementUI) {
            return;
        }
        if (target.name == "add") {
            var ui = target.parent;
            if (ui && ui.data instanceof Q.Node && !ui.data.userData.isEnd) {
                loadChildren(onDataLoad, ui.data);
            }
        } else if (target.name == "showDetail") {
            var ui = target.parent;
            if (ui && ui.data instanceof Q.Node) {
                alert(ui.data.userData.fld_name);
            }
        }
    }
    graph.ondblclick = function (evt) {
        var ui = graph.getUIByMouseEvent(evt);
        if (!ui || !(ui.data instanceof Q.Node)) {
            layouter.doLayout();
            graph.zoomToOverview();
            return;
        }
        var node = ui.data;
        if (node.userData.isEnd) {
            return;
        }
        loadChildren(onDataLoad, node);
    }

在线演示

http://demo.qunee.com/waterwsn/ 如需代码请与support@qunee.com联系

Next Prev