整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Canvas实现连线题的方案设计

Canvas实现连线题的方案设计

发“连线题”并没什么难点,这里主要分享的是:实现过程中的一点点布局设计上的小心思。

想要的效果大体长这样(如下图所示):

乍一看似乎需要获取每个元素的位置信息、计算连线的端点坐标,似乎很繁琐,而且每次页面尺寸变化时,都得重新计算。其实,事情可以比想象中简单很多…

一、方案设计

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的网络图绘制

应用场景

Cola.js是一个JavaScript库,用于绘制交互式网络图。它广泛应用于社交网络、知识图谱、生物网络等领域,帮助用户可视化和探索复杂的数据关系。

基本功能

本代码实现了使用Cola.js绘制网络图的基本功能,包括:

  • 加载外部JavaScript和CSS资源
  • 从JSON文件中读取网络图数据
  • 创建节点、连线和组
  • 设置力导向布局算法的参数
  • 实时更新网络图布局,响应拖拽操作和力导向算法的迭代

功能实现步骤及关键代码分析

1. 加载外部资源

const loadStyle=(styleUrl)=> { ... }
const loadJavascript=(jsUrl)=> { ... }

使用loadStyle和loadJavascript函数异步加载必要的CSS和JavaScript文件。

2. 读取网络图数据

d3.json(
  'webcola/website/examples/graphdata/miserables.json',
  function (error, graph) { ... }
)

使用d3.js读取JSON格式的网络图数据,包括节点和连线信息。

3. 创建节点、连线和组

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元素,并绑定相关数据。

4. 设置力导向布局算法的参数

dcola
  .nodes(graph.nodes)
  .links(graph.links)
  .groups(groups)
  .jaccardLinkLengths(40, 0.7)
  .avoidOverlaps(true)
  .start(50, 0, 50)

配置力导向布局算法的参数,包括节点、连线、组信息,以及连线长度、避免重叠等约束。

5. 实时更新布局

dcola.on('tick', function () { ... })

注册一个回调函数,在力导向布局算法迭代时触发,实时更新节点、连线和组的位置。

总结与展望

通过开发这段代码,我们掌握了使用Cola.js绘制交互式网络图的技巧。

经验与收获:

  • 深入理解力导向布局算法的原理和参数配置
  • 熟练运用d3.js操作DOM元素
  • 提高JavaScript异步加载和事件处理的能力

未来拓展与优化:

  • 集成其他布局算法,如随机布局、力导向布局的变体
  • 优化交互体验,支持缩放、平移等操作
  • 探索更复杂的数据可视化场景,如多层网络图、时间序列网络图
  • 更多组件:

获取更多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;

}

}

源码可以私信获取,谢谢支持