65.9K
CodeProject 正在变化。 阅读更多。
Home

带有布局的图形 JavaScript 树

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (99投票s)

2006年11月1日

CPOL

16分钟阅读

viewsIcon

879322

downloadIcon

13645

一个实现了 J.Q.Walker II 布局算法的 JavaScript 树组件。

Sample with specific colors in Internet Explorer

图 1。 在 Internet Explorer 中使用 VML 渲染的带有特定颜色的示例

Sample with specific colors in Firefox

图 2。 在 Firefox 中使用 canvas 渲染的带有特定颜色的示例

Sample showing collapsed and selected nodes

图 3。 在 Internet Explorer 中使用 VML 渲染的显示折叠和选定节点的示例

目录

引言

本文介绍了一个 JavaScript 组件,该组件使用 Internet Explorer 6+ 中的 VML(矢量标记语言)或 Firefox 1.5+ 中的 <canvas> 元素在屏幕上渲染树。

市面上有很多 JavaScript 树,其中大多数都比这个具有更好的跨浏览器兼容性、更快的速度和更好的优化。此代码的目标是提供 Walker 布局算法的客户端实现(参见参考文献),省略代码优化。简而言之,该组件是在算法之后诞生的,因为我认为一个活生生的示例比抽象的源代码级别的实现要好。

我有时使用的一个经典的树状视图 JavaScript 组件是 Geir Landrö 的 DTree:非常出色且简单。前段时间,CodeProject 上的一篇文章 这里 介绍了一个基于 DTree 的水平 JavaScript 树,它使用 HTML 表格以水平方式渲染树。我想在这里提到它们,因为它们启发我发表了这篇文章。此外,此版本中的许多功能都模仿了那些脚本中的功能。

我希望这个组件能填补项目中需要树形布局但又不需要服务器端代码的空白。当然,任何评论、更正、建议等都非常欢迎。

特点

要了解使用此组件可以实现什么的最佳方法是下载源代码并玩玩包含的示例。下面的 API 参考可以让你更精确地了解可以做什么。但总而言之,功能包括:

  • 节点可以是任何大小、颜色。(通过一些代码调整,可以轻松按照需要进行修改)。
  • 节点可以有一个标题和一个隐藏的关联元数据。
  • 选择模式有:多选、单选和无选择。
  • 节点链接可以是直线(曼哈顿)或贝塞尔曲线。
  • 树布局可以从上到下、从右到左或反向定向。(这要归功于 Walker 的算法,而不是我!)。
  • 节点可以有一个关联的超链接。
  • 子树可以根据需要折叠和展开。
  • 节点搜索可以基于标题和元数据进行。

我想说这是一个进行中的工作。特别是,代码的结构允许不同的渲染技术,在这方面我必须更加努力。

背景

Walker 算法在前人的 Reingold、Tilford、Shannon 等人的工作基础上进行了扩展,提供了一种解决方案,使得树形图占据的空间尽可能少,同时满足以下美学规则:

  1. 同一级别的节点对齐,所有级别的轴平行。
  2. 父节点应在其子节点之上居中。
  3. 树形图及其反序表示的同一树形图应产生互为镜像的布局。无论同一子树出现在树的何处,都必须以相同的方式渲染。

Walker 算法相对于其前代算法的目标是提供一种解决方案,通过在相邻的较大子树之间均匀地分配较小的子树来完全满足第三条规则。

The difference of the apportion routine in Walker's algorithm

图 4。 展示 Walker 算法中划分例程差异的示例

算法的主要概念是:

  • 将每个子树视为一个刚性单元,移动一个节点意味着移动其所有后代。
  • 通过使用每个节点的初始坐标和修饰符来实现这一点。移动一个节点意味着增加/减少其初始坐标和修饰符,但只有修饰符会影响其后代的最终位置。因此,移动子树意味着改变子树的根修饰符。节点的最终位置是通过将节点的初始坐标与其所有祖先的修饰符相加来计算的。

该算法分两次进行。第一次遍历(firstWalk)是后序遍历。不同的子树从下到上、从左到右递归处理,定位构成每个子树的刚性单元,直到它们不相互接触。随着遍历的进行,较小的子树被组合成较大的子树,直到我们到达根节点。在此过程中,apportion 例程会均匀地分配可能 *浮动* 在两个相邻的较大子树之间的内部较小子树(以满足第 3 条规则的对称性)。第二次遍历(secondWalk)是前序遍历,通过将所有祖先的修饰符加到每个节点的初始坐标上来计算最终节点位置。

Gao Chaowei(参见参考文献)对 firstWalk 例程提出了一项优化,即 Walker 算法的增量版本。该版本避免了在树结构发生变化但其值不发生改变时重新计算节点的初始坐标和修饰符。此优化尚未在此组件中实现。

如果布局方向不同(例如,从下到上),算法也会进行调整。不同的用途可能需要不同的视图。在西方,传统上,自上而下的布局意味着权力,就像在组织层级中一样;自下而上的布局意味着进化或增长,就像在生物学中一样;而左右布局可能意味着时间演变。(但这只是一个特殊的考虑)。

值得一提的是,像这样统一的布局算法适用于相对较小的树。拥有大量节点的树可能需要其他的布局和可视化技术,例如缩放、平移和聚焦、折叠部分而非全部子节点、添加交互式搜索等。有许多其他解决方案可以实现相同的目标,包括但不限于双曲树、树图、兴趣度计算等。好奇的读者可以在网上找到大量信息。

使用代码

快速指南:让项目启动和运行

让我们来构建一个示例(所有示例都包含在下载文件中),以了解如何绘制简单的树。然后,借助 API 参考和查看高级示例代码,您可以完全理解如何使用此组件。

首先,您必须在 HTML 页面的 <head> 部分包含组件脚本并链接样式表(请记住根据您的安装设置路径)。

<head>
    <!-- Content goes here... -->
    <script type="text/javascript" src="ECOTree.js"></script>
    <link type="text/css" rel="style-sheet" href="ECOTree.css" />
    <!-- Content goes here... -->
</head>

此外,如果您计划使用 Internet Explorer,则必须将以下几行添加到 HTML 页面的 <head> 部分,以便 VML 正确渲染:

<head>
    <!-- Content goes here... -->
    <xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v"/>
    <style>v\:*{ behavior:url(#default#VML);}</style>         
    <!-- Content goes here... -->
</head>

在您的 HTML 页面中,您必须为树放置一个块容器,例如一个您必须提供给树构造函数的 ID 的 <div>。然后,您必须包含一个 <script> 块来创建树本身,添加一些节点(在实际项目中可能来自数据库),然后绘制树。让我们看一个示例:

<div id="myTreeContainer"></div>
var myTree = new ECOTree("myTree","myTreeContainer");
myTree.add(0,-1,"Apex Node");
myTree.add(1,0,"Left Child");
myTree.add(2,0,"Right Child");
myTree.UpdateTree();

结果将是。

Quick example 1

图 5。 快速示例 1

您必须确保脚本块在页面加载后执行,或者至少在容器加载时执行。因此,您可以选择在容器之后插入脚本,或者在页面或正文的 OnLoad 事件发生时调用的函数中创建树,这通常是这样的。

请注意,该组件几乎所有东西都有默认值,这使得这五行代码可以创建一个您可以随意折叠或展开的树,并且您可以通过单击节点来选择/取消选择多个节点。此外,每个节点默认都有一个超链接 Javascript:void(0);。几乎所有情况下,您都希望更改树的外观和行为,以更好地满足您的需求。这可以通过树的 config 实例成员来完成。让我们修改前面的示例,添加几行:

var myTree = new ECOTree("myTree","myTreeContainer");    
myTree.config.linkType = 'B';
myTree.config.iRootOrientation = ECOTree.RO_BOTTOM;                        
myTree.config.topYAdjustment = -160;
myTree.config.linkColor = "black";
myTree.config.nodeColor = "#FFAAAA";
myTree.config.nodeBorderColor = "black";
myTree.config.useTarget = false;
myTree.config.selectMode = ECOTree.SL_SINGLE;
myTree.add(0,-1,"Apex Node");
myTree.add(1,0,"Left Child");
myTree.add(2,0,"Right Child");
myTree.UpdateTree();

通过这些小的更改,树现在看起来像这样:

Quick example 2

图 6。 快速示例 2

这次,节点没有超链接(useTarget = false),并且您一次只能选择一个节点(selectMode = ECOTree.SL_SINGLE)。

好的,现在基础知识已经讲完,让我们继续探索所有可能性。

API 参考

ECOTree 配置

以下是配置参数及其默认值:

this.config = {
    iMaxDepth : 100,
    iLevelSeparation : 40,
    iSiblingSeparation : 40,
    iSubtreeSeparation : 80,
    iRootOrientation : ECOTree.RO_TOP,
    iNodeJustification : ECOTree.NJ_TOP,
    topXAdjustment : 0,
    topYAdjustment : 0,
    render : "AUTO",
    linkType : "M",
    linkColor : "blue",
    nodeColor : "#CCCCFF",
    nodeFill : ECOTree.NF_GRADIENT,
    nodeBorderColor : "blue",
    nodeSelColor : "#FFFFCC",
    levelColors : ["#5555FF","#8888FF","#AAAAFF","#CCCCFF"],
    levelBorderColors : ["#5555FF","#8888FF","#AAAAFF","#CCCCFF"],
    colorStyle : ECOTree.CS_NODE,
    useTarget : true,
    searchMode : ECOTree.SM_DSC,
    selectMode : ECOTree.SL_MULTIPLE,
    defaultNodeWidth : 80,
    defaultNodeHeight : 40,
    defaultTarget : 'Javascript:void(0);',
    expandedImage : './img/less.gif',
    collapsedImage : './img/plus.gif',
    transImage : './img/trans.gif'
}
  • iMaxDepth:树允许的最大级别数。将其设置为大于预期最大级别的值。
  • iLevelSeparation:级别之间的像素距离。请注意,级别的大小将由该级别的最大节点大小决定。
  • iSiblingSeparation:同级节点之间的最小像素距离。
  • iSubtreeSeparation:相邻节点(不同父节点)之间的最小像素距离。
  • iRootOrientation:布局的方向。可能的值有:
    • ECOTree.RO_TOP:从上到下的布局。
    • ECOTree.RO_BOTTOM:从下到上的布局。
    • ECOTree.RO_RIGHT:从右到左的布局。
    • ECOTree.RO_LEFT:从左到右的布局。
  • iNodeJustification:属于同一级别的节点的对齐方式。可能的值有:
    • ECOTree.NJ_TOP:节点顶部对齐。
    • ECOTree.NJ_CENTER:节点居中对齐。
    • ECOTree.NJ_BOTTOM:节点底部对齐。
  • topXAdjustment:根节点将获得的水平偏移量(像素)。用于在屏幕上居中/平移树。
  • topYAdjustment:根节点将获得的垂直偏移量(像素)。用于在屏幕上居中/平移树。
  • render:渲染类型。可能的值有:
    • "AUTO":自动。这是默认值。代码将自动检测客户端是 Internet Explorer 6+ 还是 Firefox,并将渲染类型设置为 "VML""CANVAS"
    • "VML":矢量标记语言。仅适用于 Internet Explorer。如果您使用它,请记住在 head 部分添加 XML namespace<style>,如文章所示。
    • "CANVAS":渲染将基于 <canvas> HTML 元素。仅适用于 Firefox 1.5+。(但 Firefox 2.0 更快)。
  • linkType:节点链接的样式。可能的值有:
    • "M":曼哈顿。链接是交叉直线。经典。
    • "B":贝塞尔。链接是贝塞尔曲线。在某些情况下更适合。
  • linkColor:用于链接线的颜色。任何 HTML 格式的颜色都有效。
  • nodeColor:用于节点的颜色。有关如何修改节点渲染模式的注释,请参阅文章。任何 HTML 格式的颜色都有效。
  • nodeFill:节点的填充样式。可能的值有:
    • ECOTree.NF_GRADIENT:节点将以节点颜色和“whitesmoke”之间的渐变填充。
    • ECOTree.NF_FLAT:节点将具有实心填充。
  • nodeBorderColor:用于节点边框的颜色。任何 HTML 格式的颜色都有效。
  • nodeSelColor:用于选定节点的颜色。任何 HTML 格式的颜色都有效。
  • levelColors:一个 JavaScript 数组,包含连续级别的颜色。当 colorStyle 选项设置为使用级别颜色时应用。树第一级的节点将使用此数组中的第一种颜色,第二级节点使用第二种颜色,依此类推。如果级别数多于此数组中的元素,则后续级别将重新使用颜色。任何 HTML 格式的颜色都有效。
  • levelBorderColors:与上面相同,但用于边框颜色。
  • colorStyle:指示节点颜色的计算方式。可能的值有:
    • ECOTree.CS_NODE:每个节点将使用其自身的颜色进行渲染。
    • ECOTree.CS_LEVEL:节点将根据其在树中的级别使用不同的颜色,忽略其自身的颜色。
  • useTarget:节点是否显示超链接。可能的值为 truefalse
  • searchMode:表示搜索(参见 API searchNodes)将如何进行。可能的值有:
    • ECOTree.SM_DSC:搜索将在节点标题中进行。
    • ECOTree.SM_META:搜索将在节点元数据中进行。
    • ECOTree.SM_BOTH:搜索将在两个值中进行。
  • selectMode:指示树的选择行为。可能的值有:
    • ECOTree.SL_MULTIPLE:用户可以通过单击节点交互式地选择和取消选择多个节点。
    • ECOTree.SL_SINGLE:用户可以通过单击节点交互式地选择和取消选择单个节点。新节点的选择将取消之前的选择。
    • ECOTree.SL_NONE:用户无法通过单击节点来选择节点。但是,导致节点选择的 API 方法(如树上的搜索)始终有效。
  • defaultNodeWidth:如果添加节点时未显式指定宽度,则节点的宽度(像素)。
  • defaultNodeHeight:如果添加节点时未显式指定高度,则节点的高度(像素)。
  • defaultTarget:节点的超链接(单击标题时),如果添加节点时未显式指定目标。通常在所有或大多数节点具有相同链接时设置。
  • expandedImage:允许折叠子树的减号图标。如果您不喜欢它,可以更改它,或指向您自己的图像文件夹。
  • collapsedImage:允许展开折叠子树的加号图标。如果您不喜欢它,可以更改它,或指向您自己的图像文件夹。
  • transImage:一个透明图标,用于分隔折叠/展开图标和标题。如果您不喜欢它,可以更改它,或指向您自己的图像文件夹。

ECOTree 公共方法

  • UpdateTree():使树刷新。
  • add(id, pid, dsc, w, h, c, bc, target, meta):向树添加新节点。前三个参数是必需的。参数是:
    • id:节点 ID。任何数字或字符串都可以。
    • pid:此节点的父 ID。如果这是根节点,则父 ID 必须是 -1。
    • dsc:节点标题。这将是可见的描述。它也将是节点目标的链接。
    • w:(可选)节点宽度(像素)。
    • c:(可选)节点颜色。
    • bc:(可选)节点边框颜色。
    • h:(可选)节点高度(像素)。
    • target:(可选)节点超链接目标。如果您不提供此值,节点将具有默认值。但是,如果您提供一个空字符串作为目标,您将得到一个没有目标的节点。
    • meta:(可选)节点的元数据。元数据将不可见。您可以根据其内容搜索节点,或者仅将其用作存储您自己的节点数据的容器。如果您使用 JavaScript 对象作为元数据,请在对象的原型中提供一个 toString() 方法以使搜索正常工作。

    在节点添加到树之后,您可以使用其他 API 来修改它们,例如,在第一次调用 UpdateTree() 之前,将其标记为已选择或已折叠(如果它有子节点)。添加节点顺序不强制。

  • searchNodes(str):在树中搜索包含 str 字符串的节点。找到的节点将被选中,如果需要,其祖先将被展开以将找到的节点显示出来。搜索可在节点标题、元数据或两者中进行。搜索不区分大小写。如果 selectMode 设置为 SL_MULTIPLESL_NONE,则所有找到的节点都将被选中。如果 selectMode 设置为 SL_SINGLE,则仅选中第一个节点(按数据库顺序),但后续搜索将从下一个节点开始,因此您可以调用此 API 来模拟搜索下一个功能。遍历完所有节点后,搜索将从头开始。UpdateTree() 会在内部调用,因此您无需刷新树。
  • selectAll():使树中的所有节点都显示为选中状态。如果 selectMode 设置为 SL_NONE,此 API 将在不进行选择的情况下返回。UpdateTree() 会在内部调用,因此您无需刷新树。
  • unselectAll():清除所有选择。无论 selectMode 设置如何都可以使用。应在搜索之间使用以清除选择。UpdateTree() 会在内部调用,因此您无需刷新树。
  • collapseAll():使所有父节点折叠。UpdateTree() 会在内部调用,因此您无需刷新树。
  • expandAll():使所有父节点展开。UpdateTree() 会在内部调用,因此您无需刷新树。
  • collapseNode(nodeid, upd):折叠 ID 为 nodeid 的节点。仅当您将第二个参数提供为 true 时,UpdateTree() 才会内部调用。因此,如果您计划折叠多个节点,则应在完成所有工作后仅刷新一次。
  • selectNode(nodeid, upd):将 ID 为 nodeid 的节点标记为选中。仅当您将第二个参数提供为 true 时,UpdateTree() 才会内部调用。
  • setNodeTitle(nodeid, title, upd):设置 ID 为 nodeid 的节点的标题 title。仅当您将第三个参数提供为 true 时,UpdateTree() 才会内部调用。
  • setNodeMetadata(nodeid, meta, upd):设置 ID 为 nodeid 的节点的元数据 meta。仅当您将第三个参数提供为 true 时,UpdateTree() 才会内部调用。
  • setNodeTarget(nodeid, target, upd):设置 ID 为 nodeid 的节点的超链接 target。仅当您将第三个参数提供为 true 时,UpdateTree() 才会内部调用。
  • setNodeColors(nodeid, color, border, upd):设置 ID 为 nodeid 的节点的背景 colorborder 颜色。仅当您将第四个参数提供为 true 时,UpdateTree() 才会内部调用。
  • getSelectedNodes():返回一个 JavaScript 数组,其中包含具有节点 ID、标题和元数据值的对象,分别名为 "id", "dsc", "meta"。有关如何使用的示例,请参阅示例。如果您在客户端对树进行一些编辑,并使用 XMLHttp 对象将用户选择或搜索的结果发送到服务器,这将非常有用。(XMLHttp 和 AJAX 不在此文章的范围内,但值得庆幸的是 IE7 最终将 XMLHttp 实现为本地内置对象...)。

包含的示例

在下载文件中,您可以找到几个可以玩的小示例。本文中的所有图像都是使用示例中的代码制作的。有一个高级示例,让您可以尝试组件中的几乎所有选项。示例中的数据来自 Wikipedia

参考文献

未来的增强

  • 为组件添加就地(客户端)编辑功能,例如添加、删除、重新排序节点和更改其属性(例如,上下文菜单)。
  • 实现 **Gao Chaowei** 提出的布局算法优化(参见参考文献)。

历史

  • 2006/11/23 - 1.1. 更改包括:

    • 增加了 Firefox 兼容性。现在,如果您将渲染类型设置为 "AUTO",组件将自动检测浏览器并相应地选择渲染类型。我强烈建议使用 Firefox 2.0 来获得更快的 <canvas> 体验,但 Firefox 1.5 也可以正常工作。

  • 2006/10/26 - 1.0. 首次发布。
© . All rights reserved.