首页 > 编程语言 >用JavaScript绘制树状图(具有分支合并功能)的一种方法(其一)

用JavaScript绘制树状图(具有分支合并功能)的一种方法(其一)

时间:2023-06-15 09:01:47浏览次数:61  
标签:树状 JavaScript 无序 列表 坐标 图标 绘制 分支

需求分析

在很多模拟经营游戏中,科技树是一项重要的内容,其为玩家提供了各项技术与其前后置科技间的拓扑关系。这些科技树在表现形式上和普通树状图很相似,但由于其频繁的分支合并,为科技树的绘制带来困难。因此,我们需要一种简单的方法来绘制科技树。

比如,当用户输入:

<!--为了降低用户的学习门槛,这里的语法参考了wikitext的无序列表-->

*四年计划

**西墙防线
***大西洋壁垒
***东方壁垒

**大空间经济
***亲近罗马尼亚
****整合战时经济
***亲近匈牙利
****整合战时经济
*****组建专员辖区

**自给自足
***帝国高速公路
****德国战时经济
*****组建专员辖区
***煤液化
****合成橡胶
*****增加科研槽
***赫尔曼·戈林工厂
****力量来自欢乐牌汽车
*****增加科研槽

我们期望,输出的样式可以类似于这样:

通过以上构想不难发现,从输入到输出大致要经过这几个步骤:

  1. 写出无序列表;
  2. 将这个无序列表加工为普通的树状图;
  3. 找到树状图中需要合并的分支,将其合并;
  4. 渲染出最终的成品。

可以看到,上面的步骤中需要将树状图渲染两次,这势必会增加我们工作的难度,为此我们可以将第2、3步调换,再略作调整,最终得到这样的步骤:

  1. 写出无序列表,将其加工为易于计算机处理的数据;
  2. 找到原始数据中需要合并的分支,并将其合并;
  3. 渲染出最终的成品。

然后我们逐步研究程序的实现细节。

明确实现方法

原始字符串的格式要求

虽然之前我们已经大概用wikitext写了一份草稿,并且很聪明地(?)顺便采用了将无序列表中内容相同的分支标记为需要合并的分支这样的方法,但随着研究的深入,我们逐渐会发现这样的问题:

有些科技树中的项具有复杂的结构,这将导致誊写需要合并的分支时异常麻烦,也为原始字符串的可读性造成负面影响。

比如在游戏《戴森球计划》中,科技树上的『冶炼提纯科技』至少应有以下内容:

***冶炼提纯<br/>研究消耗:{{图标|电磁矩阵|100}},解锁物品:{{图标|硅石}}{{图标|高能石墨}}{{图标|高纯硅块}}

解决起来也不麻烦,我们可以把科技树中项的内容分成 名称 和 正文 两部分,用名称来说明项的身份,这样我们在誊写需要合并的项时就会更轻松些。对此,我们可以要求原始字符串中要用“|”来分割名称和正文,名称在前,正文在后。

还是上面的例子,按我们刚刚设计的新语法的规范,它应该是这样子的:

***冶炼提纯 | 研究消耗:{{图标|电磁矩阵|100}},解锁物品:{{图标|硅石}}{{图标|高能石墨}}{{图标|高纯硅块}}

如果后续有什么指向这个科技的分支,那么在那儿它就可以被写成这样:

***冶炼提纯

另外的问题是用户可能在同名分支(需要合并的分支)后接上不同的子枝,这种届时我们直接报错就好。

树状图的渲染方法

在web网页中进行树状图排版的可能的方案着实不少,可惜大部分囿于本人的能力统统不能实现。在我对此苦恼的时候,无意间发现了缺氧wiki中绘制他们的科技树的解决之道:

使用CSS中的grid属性来进行树状图中框和线的绘制。

这不由得给了我巨大的启发。具体而言,采用CSS的grid属性来绘制树状图有如下优势:

  1. 在网络资料的帮助下,我这半吊子的水平确实可以实现grid的排版;
  2. grid网格中元素的位置可以由一组坐标确定,方便计算机处理;
  3. 大部分用户的浏览器都支持使用grid进行排版。

从无序列表转化为树状图

元素在无序列表中的相对位置可以由一串数字说明(我们可以将其称为“树坐标”),比如某个元素无序列表中的第一个,也就是在树状图的根部,它的坐标可以记为:

(0)

表示它是同辈树枝中的长子,且没有父辈,它自己就是第一代。

假如有多个枝并列,比如:

*<!--长子-->
*<!--次子-->
*<!--幼子-->

它们的坐标可以分别记为:

(0)
(1)
(2)

假如这三个枝分别有两个子代,比如

*<!-- 伯 -->
**<!--长子-->
**<!--次子-->
*<!-- 仲 -->
**<!--长子-->
**<!--次子-->
*<!-- 叔 -->
**<!--长子-->
**<!--次子-->

它们的坐标就能分别记为

(0)
(0,0)
(0,1)

(1)
(1,0)
(1,1)

(2)
(2,0)
(2,1)

通过这样的树坐标,就能很清楚地看到树状图上每一项的继承关系和相对位置。而grid是栅格布局的,通过一个二维坐标便能确定树状图上所有分支和连接线的绝对位置。我们将前者叫做“树坐标”,后者叫做“表坐标”,那么有以下结论是不言而喻的:

存在某种映射关系,能将树坐标映射为表坐标。

我们只要找到这种映射关系,就找到了无序列表与树状图之间的桥梁,要将无序列表转化为树状图也就很容易了。

工作流程总结

最后我们将上文汇总成流程图,就能得到这样的结果:

如果不出意外的话,我们只要按这个流程逐步设计程序,就能实现我们的需求。但愿如此。

标签:树状,JavaScript,无序,列表,坐标,图标,绘制,分支
From: https://www.cnblogs.com/-zyyz-/p/17481121.html

相关文章

  • javascript现代编程系列教程之六——parseInt()整数转换
    在JavaScript中,parseInt()函数会将其参数转换为字符串,然后解析该字符串,并返回一个整数或NaN。如果parseInt()函数的参数是一个非常大的浮点数(如1000000000000000000000.5),那么它首先会被转换为科学记数法的字符串形式(即"1e+21"),然后parseInt()会尝试从这个字符串中解析出......
  • javascript现代编程系列教程之五——正零和负零
    在JavaScript中,正零(+0)和负零(-0)都代表数值0,它们在大多数情况下是等价的。然而,在某些特定的场景下,正零和负零的行为会有所不同。除法操作:当0被用作除数时,正零和负零会产生不同的结果:console.log(42/+0);//输出:Infinityconsole.log(42/-0);//输出:-InfinityObject......
  • JavaScript学习笔记 - 语法篇 - 一句废话没有版
    写在前面:绝不废话!放心食用JavaScript语法很简单,可以直接在控制台调试理解目录1、变量和常量2、数据类型3、字符串3.1模板字符串3.2字符串的部分常用函数4、数组5、对象6、对象数组&&JSON7、if条件&&三目运算7.1if条件7.2三目运算8、switch9、循环9.0准备工作9.1......
  • AD20 绘制定位孔
    板的绘制关键命令步骤1.定原点 定位孔的绘制步骤举例:以M3定位孔为例(可安装M3螺丝或铜柱)步骤1:确定PCB板框后,再按下图,选择Mechanical1层→放置→圆; 步骤2:在Mechanical1层绘制任意圆,双击圆,弹出Properties,设置线条宽度0.127mm,半径1.75mm,网络选择"NoNet"; 步骤3:选择TOP......
  • JavaScript 动态编辑元素某属性值(例如:元素div的class属性)
    元素<divclass="h5-box-search-itemusimglistnodisplay"id="usimglist"></div>(满足条件)动态更新div元素的class属性值://获取目标容器letusimglist=document.getElementById('usimglist');//获取其class的属性值letclassinfo=usimglist.ge......
  • JavaScript中数组(Array)与对象(Object)中的检索方式
    这里只是要说明一点,数组(Array)和对象(Object)都可以用[...]的方式来进行检索[...]中包含的需要是一个表达式,这个表达式的值最终会以字符串的形式被使用因为不论是数组(Array)还是对象(Object),他们都是以键值对的形式存储内容的,而所有的键的数据类型都是字符串(Array好像不是,但是先这样......
  • 书写高质量JavaScript代码的要义(The Essentials of Writing High Quality JavaScript)
    原文:TheEssentialsofWritingHighQualityJavaScript才华横溢的StoyanStefanov,在他写的由O’Reilly初版的新书《JavaScriptPatterns》(JavaScript模式)中,我想要是为我们的读者贡献其摘要,那会是件很美妙的事情。具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使......
  • Canvas_绘制线段、圆形、文本、图像、视频、处理图像数据
    Canvas_绘制线段、圆形、文本、图像、视频、处理图像数据绘制线段varcanvas1=document.querySelector("#canvas1");varctx=canvas1.getContext("2d");//设置开始路径ctx.beginPath()//设置绘制的起始点ctx.moveTo(50,50);//设置经过某个位置ctx.lineTo(50,30......
  • javascript现代编程系列教程之二——IIFE
    IIFE(ImmediatelyInvokedFunctionExpression,立即执行函数表达式)是一个在定义后立即执行的JavaScript函数。它具有以下特点:是一个匿名函数:通常情况下,IIFE是一个没有名字的函数,称为匿名函数。立即执行:这个函数在声明后立即被调用并执行,而无需手动调用。创建局部作用域:它创建......
  • javascript现代编程系列教程之一:区块作用域对VAR不起作用的问题
    在JavaScript中,使用var声明的变量具有函数作用域,而不是块级作用域。这意味着在一个函数内部,使用var声明的变量在整个函数范围内都是可见的,包括嵌套的块(如if语句、for循环等)。为了避免区块对var不起作用的问题,你可以采用以下方法:使用let和const代替var:从ECMAScript2015(ES6)开始,引......