发“连线题”并没什么难点,这里主要分享的是:实现过程中的一点点布局设计上的小心思。
想要的效果大体长这样(如下图所示):
乍一看似乎需要获取每个元素的位置信息、计算连线的端点坐标,似乎很繁琐,而且每次页面尺寸变化时,都得重新计算。其实,事情可以比想象中简单很多…
1.1 将连线题分为上中下三个区块,其中:
1)上下区块均使用flex(justify-content: space-around)
2)中间区块为canvas,在style属性中将width和height设置为100%
.row-1 { justify-content: space-around; }
.row-2 {
height: 0; flex-grow: 1;
canvas { width: 100%; height: 100%; }
}
.row-3 { justify-content: space-around; }
1.2 这样设置以后,有几点好处:
1)计算直线端点(元素中点)坐标就会变得很轻松,只需要排序百分比与canvas的内容宽度相乘即可。
let x1=(上方元素下标 * 2 + 1) / (上方元素总数 * 2) * canvas.width
let x2=(下方元素下标 * 2 + 1) / (下方元素总数 * 2) * canvas.width
// 连线上方端点坐标: (x1, 0)
// 连线下方端点坐标: (x2, canvas.height)
2)页面resize时无需重新计算,页面也不会乱。当然如果resize前后差异较大,可能连线粗细程度会不美观。
经测,一般不重新绘制也问题不大;如果要求高的话,可以在resize时重新绘制一下。(下图是第一张图在网页resize后的效果,线条经过拉伸变细了)
3)如果你连canvas的尺寸也懒得初始化,也是可以的,只不过效果会差些(线条有点模糊,粗细不美观),Chrome中canvas默认内容尺寸是300*100,效果如下图所示(截图可能视觉效果不明显):
线条绘制的相关代码如下:
Html
<canvas ref="canvas" :width="cvsWidth" :height="cvsHeight"></canvas>
Js
文由ScriptEcho平台提供技术支持
项目地址:传送门
Cola.js是一个JavaScript库,用于绘制交互式网络图。它广泛应用于社交网络、知识图谱、生物网络等领域,帮助用户可视化和探索复杂的数据关系。
本代码实现了使用Cola.js绘制网络图的基本功能,包括:
const loadStyle=(styleUrl)=> { ... }
const loadJavascript=(jsUrl)=> { ... }
使用loadStyle和loadJavascript函数异步加载必要的CSS和JavaScript文件。
d3.json(
'webcola/website/examples/graphdata/miserables.json',
function (error, graph) { ... }
)
使用d3.js读取JSON格式的网络图数据,包括节点和连线信息。
var group=svg
.selectAll('.group')
.data(groups)
.enter()
.append('rect')
.classed('group', true)
...
var link=svg
.selectAll('.link')
.data(graph.links)
.enter()
.append('line')
.attr('class', 'link')
...
var node=svg
.selectAll('.node')
.data(graph.nodes)
.enter()
.append('circle')
.attr('class', 'node')
...
分别创建节点、连线和组的DOM元素,并绑定相关数据。
dcola
.nodes(graph.nodes)
.links(graph.links)
.groups(groups)
.jaccardLinkLengths(40, 0.7)
.avoidOverlaps(true)
.start(50, 0, 50)
配置力导向布局算法的参数,包括节点、连线、组信息,以及连线长度、避免重叠等约束。
dcola.on('tick', function () { ... })
注册一个回调函数,在力导向布局算法迭代时触发,实时更新节点、连线和组的位置。
通过开发这段代码,我们掌握了使用Cola.js绘制交互式网络图的技巧。
经验与收获:
未来拓展与优化:
获取更多Echos
本文由ScriptEcho平台提供技术支持
项目地址:传送门
微信搜索ScriptEcho了解更多
一篇我们学习了如何使用zTree对静态数据进行绑定并显示。但是实际运用中数据都是数据库或者经过计算后得到的,所以这一次我们将上次的Demo改为动态绑定。
数据表如下,其中平pId为0的是根节点
上一次讲过zTree的回调事件中可以有许多函数,那么我们就利用点击节点的回调函数进行动态绑定。但是问题来了,首次加载时网页上并没有节点,那么我们如何实现点击节点加载数据呢?思路是这样的,首次加载时用ajax向后台发送一个空请求,点击节点时ajax发送点击节点的id。后台收到请求,将请求参数取出,传入数据库查询方法,此时进行判断,若参数为空说明是首次加载,则执行的sql语句是select * from demo where pId='0' 即取出根节点,然后返回,而参数不为空则说明是点击节点发出的请求,此时sql语句是select * from demo where pId='"+id+"'" 即取出父节点是我们点击节点的节点。
下面上代码
额,先看效果图吧
前台
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="css/metroStyle.css" type="text/css">
<script type="text/javascript" src="js/jquery-1.4.4.min.js"></script>
<script type="text/javascript" src="js/jquery.ztree.core.js"></script>
</head>
<body align="center" >
<div id="demotree" style="margin-left:230px;margin-top:50px">
<ul id="treeDemo" class="ztree"></ul>
</div>
</body>
<script type="text/javascript">
var treeNodes;
var setting={
//外观
view: {
showIcon: true, //设置是否显示节点图标
showLine: true, //设置是否显示节点与节点之间的连线
showTitle: true, //设置是否显示节点的title提示信息
fontCss : {color:"black",size:30}
},
//异步
async:{
enable: true,//true代表异步
url: "/Demo/DataProcessing",//异步获取数据的地址
autoParam: ["id=id"]//传递id
},
//数据类型
data: {
simpleData: {
enable: true, //设置是否启用简单数据格式(json格式)
idKey: "id", //设置启用简单数据格式时id对应的属性名称(对应json数据中的key)
pidKey: "pId" //设置启用简单数据格式时parentId对应的属性名称,ztree根据id及pid层级关系构建树结构
}
},
//回调事件
callback: {
//点击节点事件
onClick: function(event, treeId, treeNode, clickFlag) {
// 判断是否父节点 ,是父节点则ajax向后台发送节点名称以获取子文件
if(treeNode.isParent){
//判断节点是否折叠,若已折叠则请求子节点的数据,返回后展开
if(treeNode.collapse==true){
//ajax提交请求
$.ajax({
url: "/Demo/DataProcessing",//请求的action路径
type:"post", //提交方式
async:false,//同步
dataType:"json",//数据格式是json
data:{'id':treeNode.id}, //传递被点击节点的id
error: function(){//请求失败后执行的事件
},
//请求成功后执行事件
success:function(data)
{
var jsondata=data;
//json为空说明没有子节点,不用执行操作
if(jsondata==null || jsondata==""){ }
//否则子节点添加到父节点
else{
var treeObj=$.fn.zTree.getZTreeObj("demotree");
var parentZNode=treeObj.getNodeByParam("name",treeNode.name, null);//获取指定父节点
newNode=treeObj.addNodes(parentZNode,jsondata, false);
}
}
});
}
}
}
}
};
//首次加载用ajax请求初始化
$(function(){
$.ajax({
async : false,
cache:false,
type: 'POST',
dataType : "json",
url: "/Demo/DataProcessing",//请求的action路径
error: function () {//请求失败处理函数
alert('请求失败');
},
success:function(data){ //请求成功后处理函数。
treeNodes=data; //把后台封装好的简单Json格式赋给treeNodes
}
});
});
var zTree;
$(document).ready(function(){
$.fn.zTree.init($("#treeDemo"), setting, treeNodes);
});
</script>
</html>
后台servlet
package myServlet;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import Db.DbOperate;
import Model.Demo;
import com.alibaba.fastjson.JSON;
/**
* Servlet implementation class DataProcessing
*/
@WebServlet("/DataProcessing")
public class DataProcessing extends HttpServlet {
private static final long serialVersionUID=1L;
/**
* @see HttpServlet#HttpServlet()
*/
public DataProcessing() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
String id=request.getParameter("id");
List<Demo> list;
try {
list=new DbOperate().getDemo(id);
String jsonArr=JSON.toJSONString(list);
System.out.println(jsonArr);
//response.setContentType("application/json; charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.getWriter().print(jsonArr);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
数据库查询
package Db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import Model.Demo;
public class DbOperate {
private static String url="jdbc:mysql://localhost:3306/dir?rewriteBatchedStatements=true";
private static String user="root";
private static String password="";
public List<Demo> getDemo(String id) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
System.out.print("!!!!!!!!!");
Connection conn=DriverManager.getConnection(url, user, password);
List<Demo> list=new ArrayList<Demo>();
Statement s=null;
String sql;
if(id==null||id==""){
sql="select * from demo where pId='0'";
}
else{
sql="select * from demo where pId='"+id+"'";
}
s=conn.createStatement();
ResultSet rs=s.executeQuery(sql);
while (rs.next()) {
list.add(new Demo(rs.getString("id"), rs.getString("name"), rs.getString("pId"), rs.getInt("isParent")));
}
if (s !=null) {
s.close();
}
if (conn !=null) {
conn.close();
}
return list;
}
}
实体类
package Model;
public class Demo {
private String id;
private String name;
private String pId;
private int isParent;
public Demo(String id, String name, String pId, int isParent) {
super();
this.id=id;
this.name=name;
this.pId=pId;
this.isParent=isParent;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id=id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name=name;
}
public String getpId() {
return pId;
}
public void setpId(String pId) {
this.pId=pId;
}
public int getIsParent() {
return isParent;
}
public void setIsParent(int isParent) {
this.isParent=isParent;
}
}
源码可以私信获取,谢谢支持
*请认真填写需求信息,我们会在24小时内与您取得联系。