- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
通过一段时间的使用和学习,对G6有了更一步的经验,这篇博文主要从以下几个小功能着手介绍,文章最后会给出完整的demo代码.
树图的布局,使用的模板是官网所提供的 紧凑树 模板,在此基础上,进行一些定制化的改造, 官网紧凑树案例 。
属性说明:
graph = new G6.TreeGraph({
container,
width: document.documentElement.clientWidth,
height:document.documentElement.clientHeight,
// .....
layout: {
type: 'compactBox', // 布局类型
direction: 'LR', // 树图布局方向, 从左向右
getHeight: function getHeight() { // 高度
return 16
},
getWidth: function getWidth() { // 宽度
return 16
},
getVGap: function getVGap() { // 节点之间 垂直间距
return 25
},
getHGap: function getHGap() { // 节点之间 水平间距
return 150
}
}
})
大部分在实际项目中,数据都是由后端返回,可能会存在多种类型的数据,需要进行不同的处理和展示,那么此时只在 graph 初始化时,定义defaultNode显然是不够用的,G6支持动态改变节点样式。需要在 graph 实例化之后:
// 以下函数均在下方有实现代码:
graph.node((node)=> {
return {
label: node.label || formatLabel(node),
icon: formatIcon(node),
size: node.size || 40,
labelCfg: { position: setLabelPos(node) }, // label 显示位置
style: {
fill: getNodeColor(),
stroke: getNodeColor()
}
}
})
首先判断当前节点有无子节点,后续会进行label的拼接,显示叶子节点的数量,在进行结束,此书由于数据的原因,案例中数据 label 和 id 使用同一个字段,后续同学们可以自己按实际情况进行调整:我截取的长度是 15 。
// label 过长截断 显示...
const formatLabel = (node) => {
const hasChildren = node.childrenBak?.length || node.children?.length
const ellipsis = node.id.length > 15 ? '...' : ''
return `${node.id.slice(0, 15)}${ellipsis}${hasChildren ? ' (' + hasChildren + ')' : ''}`
}
这个小功能,与上一个label 截断,统一都是处理label的,因此放在同一个函数中返回,其主要实现代码为 这一行:
return `${node.id.slice(0, 15)}${ellipsis}${hasChildren ? ' (' + hasChildren + ')' : ''}`
这个小功能设计的改动比较多,同学们在看的时候,不要看错了哈 。
第一步:首先定义一个函数,返回tooltip,官网的案例中并不是返回的函数,而是直接返回了一个对象,这个在实际使用过程中会存在问题,就是新增的数据无法使用到这个插件,因此通过函数调用的方式 ,可以解决该现象:
// label 显示... 时,显示提示 tip
const treeTooltip = ()=> {
return new G6.Tooltip({
offsetX: 10, // 鼠标偏移量
offsetY: 20,
shouldBegin(e: any) {
return e.item?.get('model')?.label?.includes('...') // label中有...才显示,表示被截断
},
getContent(e: any) {
let outDiv = document.createElement('div') // 设置tip容器和样式
outDiv.innerHTML = `
<p style="max-width:600px;word-wrap:break-word;border-radius:5px;font-size:15px;color:#fff;background:#333;padding:10px">
${e.item.getModel().id}
</p>`
return outDiv
},
itemTypes: ['node'] // 表示触发的元素类型
})
}
第二步:在new 实例化的options中添加 插件使用 。
graph = new G6.TreeGraph({
container,
width: document.documentElement.clientWidth,
height:document.documentElement.clientHeight,
plugins: [treeTooltip()],
// .....
})
第三步:完整以上代码后,基本可以看出 tip 的提示框,但是由于是改写原有的样式,因此还需要 改一下 style, 由于画布操作较多,因此canvas画布,修改鼠标样式,全文只有这里提到了style修改。就写在一起了,实际并不影响 tooltip 功能 。
<style scoped>
#container >>> .g6-component-tooltip {
background:#333;
color:#fff;
padding: 0 8px;
}
canvas {
cursor: pointer !important;
}
</style>
此功能的设计是为了优化,当节点label过长并展开时,父子之间水平间距不够时会出现文案互相重叠等问题,做了一个小优化, 。
首先判断节点是否是展开状态,以及是否有叶子节点,节点有children并展开时在上,其余情况都显示在右边,这是初始化时的代码:
// 根据节点展开收起状态 动态变更 label 显示位置,展开时在上,收起时在右
const setLabelPos = (node) => {
return !node.collapsed && node.children?.length ? 'top' : 'right'
}
因为树图可以监听节点的展开收起状态,因此在切换的时候,也需要进行 label定位的问题:在new 实例化的options中添加 modes 。
graph = new G6.TreeGraph({
container,
width: document.documentElement.clientWidth,
height:document.documentElement.clientHeight,
plugins: [treeTooltip()],
// .....
modes: {
default: [
{
type: 'collapse-expand',
onChange: function onChange(item: any, collapsed) {
const data = item?.get('model')
data.collapsed = collapsed
const model = {
id: data.id,
labelCfg: { position: !collapsed ? 'top' : 'right' }
}
item.update(model)
item.refresh()
return true
}
},
'drag-canvas',
'zoom-canvas'
]
}
})
废话不多话,处理节点的icon图表,图表可以使用图片,也可以使用文字,此处就用文字了,截取的label前两个字符串,并设置背景颜色(随机色可自行定制) 。
// 叶子节点 图标处理 截取ID 前两个字符串
const formatIcon = (node) => {
node.icon = {
text: node.id.slice(0,2),
fill: '#fff',
stroke: '#fff',
textBaseline: 'middle',
fontSize: 20,
width: 25,
height: 25,
show: true
}
}
// 叶子节点 背景颜色随机填充
const getNodeColor = () => {
const colors = ["#8470FF", "#A020F0", "#C0FF3E", "#FF4500", "#66d6d1"];
return colors[Math.floor(Math.random() * colors.length)];
}
当叶子节点很多时,并不想全部展开,而是先只展示一部分,其他节点折叠在 展开按钮中,实现思路,定义一个属性,接受原有全部children, 然后进行截取,在push一个 展开按钮,实现:
第一步:定义一个 childrenBak 属性接受 children 数据 。
// 子还有子,因此需要递归
const splitChild = (node) => {
node.childrenBak = node.children ? [...node.children] : []
let result: any = []
if(node.children){
result = node.children.slice(0, 5)
if (node.children.length > 5) {
result.push({ id: `Expand-${node.id}`, label: ' 展开更多...' })
}
node.children = result
node.children.forEach(child =>{
splitChild(child)
})
}
}
第二步:(接功能小8继续)点击展开更多时,显示被折叠的剩余节点,,定义 node:click 事件 。
思路:找到展开更多 , 找到展开更多节点的 父节点, 更新父节点的 children 。
graph.on('node:click', (evt) => {
const { item } = evt
const node = item?.get('model')
if (node.id.includes('Expand')) { // id 中包含Expand 表示是展开更多
const parentNode = graph.getNeighbors(item, 'source')[0].get('model') // 找到展开更多节点的 父节点
graph.updateChildren(parentNode.childrenBak, parentNode.id) // 使用上一步声明的childrenBak 更新父节点的 children
}
})
小9 说了点击事件,那么点击事件中,还有一个小的优化点,就是将当前点击的节点,移动至画布中心,并赋予高亮选中样式 。
const animateCfg = { duration: 200, easing: 'easeCubic' }
graph.on('node:click', (evt) => {
const { item } = evt
const node = item?.get('model')
// if (node.id.includes('Expand')) { // 功能点 9 代码
// const parentNode = graph.getNeighbors(item, 'source')[0].get('model')
// console.log(parentNode,parentNode.childrenBak);
// graph.updateChildren(parentNode.childrenBak, parentNode.id)
// }
setTimeout(() => {
if (!node.id.includes('Expand')) {
graph.focusItem(item, true, animateCfg)
graph.getNodes().forEach((node) => {
graph.clearItemStates(node) // 先清空其他节点的 高亮样式
})
graph.setItemState(item, 'selected', true) // selected 需要在实例化处进行定义
}
}, 500)
})
selected 表示的是 nodeStateStyles ,也就是节点状态样式 。
graph = new G6.TreeGraph({
container,
width: document.documentElement.clientWidth,
height:document.documentElement.clientHeight,
plugins: [treeTooltip()],
// .....
nodeStateStyles: {
active: { // 这个用在了 鼠标悬浮,可以自行定义
fill: 'l(0) 0:#FF4500 1:#32CD32',
stroke: 'l(0) 0:#FF4500 1:#32CD32',
lineWidth: 5
},
selected: { // 这个用在了 鼠标选中,可以自行定义
fill: 'l(0) 0:#FF4500 1:#32CD32',
stroke: 'l(0) 0:#FF4500 1:#32CD32',
lineWidth: 5
}
}
})
graph.on('node:mouseenter', (evt) => {
const { item } = evt
graph.setItemState(item, 'active', true) // active 与 selected 都是节点状态样式
})
graph.on('node:mouseleave', (evt) => {
const { item } = evt
graph.setItemState(item, 'active', false)
})
关于节点的差不多介绍完了,关于连线,内容就比较少了,动态定义连线样式,及连线上的文字样式
可以根据 link的不同属性自定义 连线颜色和label颜色,因为是测试数据,因此就用一个自增长的数判断奇偶性来进行区分,以便明白其中定制化的方法 。
let selfGrowthNum = 0
graph.edge((edge)=> {
// let {source, target } = edge // 解构连线的 起始节点
selfGrowthNum++
return {
style: {
opacity: 0.5,
stroke: selfGrowthNum % 2 ? '#ADD8E6' : "#FFDEAD",
lineWidth: 2
},
labelCfg: {
position: 'end',
style: {
fontSize: 16,
fill: selfGrowthNum % 2 ? '#ADD8E6' : "#FFDEAD",
}
},
label: selfGrowthNum % 2 ? 'even' : "odd"
}
})
上述代码基本完成了 连线的样式和文案的样式,但此时,线是贯穿文字的,看着比较乱,因此还需要修改连线样式 defaultEdge 。
graph = new G6.TreeGraph({
container,
width: document.documentElement.clientWidth,
height:document.documentElement.clientHeight,
// .....
defaultEdge: {
type: 'cubic-horizontal',
style: { // 如果不定制化,这个就是默认样式
opacity: 0.5,
stroke: '#ccc',
lineWidth: 2
},
labelCfg: {
position: 'end', // 文字显示在线段的哪个位置,
refX: -15,
style: {
fontSize: 16,
background: {
fill: '#ffffff', // 给文字添加背景色,解决文字被横穿的问题
padding: [2, 2, 2, 2]
}
}
}
}
})
G6 4.x 依赖的渲染引擎 @antv/g@4.x 版本支持了局部渲染,带了性能提升的同时,也带来了图形更新时可能存在渲染残影的问题。比如拖拽节点时,节点的文本会留下轨迹。由于目前 @antv/g 正在进行大版本的升级(到 5.x),可能不考虑在 4.x 彻底修复这个问题。当我们遇到这个问题的时候,可以通过 关闭局部渲染 的方法解决,但是这样可能导致性能有所降低.
graph.get('canvas').set('localRefresh', false)。
<template>
<div id="container"></div>
</template>
<script lang="ts" setup>
import G6 from "@antv/g6";
import { onMounted } from "vue";
let graph: any = null;
// 树图初始数据
const treeData = {
id: "Modeling Methods",
color: "",
children: [
{
id: "Classification",
children: [
{ id: "Logistic regression" },
{ id: "Linear discriminant analysis" },
{ id: "Rules" },
{ id: "Decision trees" },
{ id: "Naive Bayes" },
{ id: "Knearest neighbor" },
{ id: "Probabilistic neural network" },
{ id: "Support vector machine" },
],
},
{
id: "Methods",
children: [
{ id: "Classifier selection" },
{ id: "Models diversity" },
{ id: "Classifier fusion" },
],
},
],
};
onMounted(() => {
splitChild(treeData);
drawTreeGraph();
});
function drawTreeGraph() {
if (graph) graph.destroy();
const container = document.getElementById("container") as HTMLElement;
graph = new G6.TreeGraph({
container,
width: document.documentElement.clientWidth - 300,
height: document.documentElement.clientHeight,
fitView: false,
fitViewPadding: [10, 50, 10, 50],
animate: true,
plugins: [treeTooltip()],
defaultNode: {
type: "circle",
size: 40,
collapsed: false,
style: {
fill: "#fff",
lineWidth: 2,
cursor: "pointer",
},
labelCfg: {
position: "right",
offset: 10,
style: {
fill: "#333",
fontSize: 20,
stroke: "#fff",
background: {
fill: "#ffffff",
padding: [2, 2, 2, 2],
},
},
},
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
icon: {
show: true,
width: 25,
height: 25,
},
},
defaultEdge: {
type: "cubic-horizontal",
labelCfg: {
position: "end",
refX: -15,
style: {
fontSize: 16,
background: {
fill: "#ffffff",
padding: [2, 2, 2, 2],
},
},
},
},
modes: {
default: [
{
type: "collapse-expand",
onChange: function onChange(item: any, collapsed) {
const data = item?.get("model");
data.collapsed = collapsed;
const model = {
id: data.id,
labelCfg: { position: !collapsed ? "top" : "right" },
};
item.update(model);
item.refresh();
return true;
},
},
"drag-canvas",
"zoom-canvas",
],
},
layout: {
type: "compactBox",
direction: "LR",
getHeight: function getHeight() {
return 30;
},
getWidth: function getWidth() {
return 16;
},
getVGap: function getVGap() {
return 30;
},
getHGap: function getHGap() {
return 150;
},
},
nodeStateStyles: {
active: {
fill: "l(0) 0:#FF4500 1:#32CD32",
stroke: "l(0) 0:#FF4500 1:#32CD32",
lineWidth: 5,
},
selected: {
fill: "l(0) 0:#FF4500 1:#32CD32",
stroke: "l(0) 0:#FF4500 1:#32CD32",
lineWidth: 5,
},
},
});
graph.node((node: { label: any; size: any }) => {
return {
label: node.label || formatLabel(node),
icon: formatIcon(node),
size: node.size || 40,
labelCfg: { position: setLabelPos(node) },
style: {
fill: getNodeColor(),
stroke: getNodeColor(),
},
};
});
let selfGrowthNum = 0;
graph.edge((edge: any) => {
// let {source, target } = edge // 也可以根据 link的属性不同自定义 连线颜色和label颜色,因为是测试数据,因此就用一个自增长的数判断奇偶性来进行区分,以便明白其中定制化的方法
selfGrowthNum++;
return {
style: {
opacity: 0.5,
stroke: selfGrowthNum % 2 ? "#ADD8E6" : "#FFDEAD",
lineWidth: 2,
},
labelCfg: {
position: "end",
style: {
fontSize: 16,
fill: selfGrowthNum % 2 ? "#ADD8E6" : "#FFDEAD",
},
},
label: selfGrowthNum % 2 ? "even" : "odd",
};
});
graph.on("node:mouseenter", (evt: { item: any }) => {
const { item } = evt;
graph.setItemState(item, "active", true);
});
graph.on("node:mouseleave", (evt: { item: any }) => {
const { item } = evt;
graph.setItemState(item, "active", false);
});
const animateCfg = { duration: 200, easing: "easeCubic" };
graph.on("node:click", (evt: { item: any }) => {
const { item } = evt;
const node = item?.get("model");
if (node.id.includes("expand")) {
const parentNode = graph.getNeighbors(item, "source")[0].get("model");
console.log(parentNode, parentNode.childrenBak);
graph.updateChildren(parentNode.childrenBak, parentNode.id);
}
setTimeout(() => {
if (!node.id.includes("expand")) {
graph.focusItem(item, true, animateCfg);
graph.getNodes().forEach((node: any) => {
graph.clearItemStates(node);
});
graph.setItemState(item, "selected", true);
}
}, 500);
});
graph.on("canvas:click", () => {
graph.getNodes().forEach((node: any) => {
graph.clearItemStates(node);
});
});
graph.data(treeData);
graph.render();
graph.zoom(0.9);
graph.fitCenter();
graph.get("canvas").set("localRefresh", false);
}
// label 过长截断 显示...
const formatLabel = (node) => {
const hasChildren = node.childrenBak?.length || node.children?.length;
const ellipsis = node.id.length > 15 ? "..." : "";
return `${node.id.slice(0, 15)}${ellipsis}${
hasChildren ? " (" + hasChildren + ")" : ""
}`;
};
// 叶子节点 图标处理 截取ID 前两个字符串
const formatIcon = (node) => {
node.icon = {
text: node.id.slice(0, 2),
fill: "#fff",
stroke: "#fff",
textBaseline: "middle",
fontSize: 20,
width: 25,
height: 25,
show: true,
};
};
// 叶子节点 背景颜色随机填充
const getNodeColor = () => {
const colors = ["#8470FF", "#A020F0", "#C0FF3E", "#FF4500", "#66d6d1"];
return colors[Math.floor(Math.random() * colors.length)];
};
// 根据节点展开收起状态 动态变更 label 显示位置,展开时在上,收起时在右
const setLabelPos = (node: { collapsed: any; children: string | any[] }) => {
return !node.collapsed && node.children?.length ? "top" : "right";
};
// label 显示... 时,显示提示 tip
const treeTooltip = () => {
return new G6.Tooltip({
offsetX: 10,
offsetY: 20,
shouldBegin(e: any) {
return e.item?.get("model")?.label?.includes("...");
},
getContent(e: any) {
let outDiv = document.createElement("p");
outDiv.innerHTML = ` ${e.item.getModel().id} `;
return outDiv;
},
itemTypes: ["node"],
});
};
// 叶子节点超过 5(xxx)条, 折叠叶子节点,显示展开更多
const splitChild = (node: any) => {
node.childrenBak = node.children ? [...node.children] : [];
let result: any = [];
if (node.children) {
result = node.children.slice(0, 5);
if (node.children.length > 5) {
result.push({ id: `expand-${node.id}`, label: " 展开更多..." });
}
node.children = result;
node.children.forEach((child: any) => {
splitChild(child);
});
}
};
</script>
<style scoped>
#container >>> .g6-component-tooltip {
background:#333;
color:#fff;
padding: 0 8px;
}
canvas {
cursor: pointer !important;
}
</style>
最后此篇关于可视化—AntVG6紧凑树实现节点与边动态样式、超过X条展示更多等实用小功能的文章就讲到这里了,如果你想了解更多关于可视化—AntVG6紧凑树实现节点与边动态样式、超过X条展示更多等实用小功能的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
问题故障解决记录 -- Java RMI Connection refused to host: x.x.x.x .... 在学习JavaRMI时,我遇到了以下情况 问题原因:可
我正在玩 Rank-N-type 并尝试输入 x x .但我发现这两个函数可以以相同的方式输入,这很不直观。 f :: (forall a b. a -> b) -> c f x = x x g ::
这个问题已经有答案了: How do you compare two version Strings in Java? (31 个回答) 已关闭 8 年前。 有谁知道如何在Java中比较两个版本字符串
这个问题已经有答案了: How do the post increment (i++) and pre increment (++i) operators work in Java? (14 个回答)
下面是带有 -n 和 -r 选项的 netstat 命令的输出,其中目标字段显示压缩地址 (127.1/16)。我想知道 netstat 命令是否有任何方法或选项可以显示整个目标 IP (127.1.
我知道要证明 : (¬ ∀ x, p x) → (∃ x, ¬ p x) 证明是: theorem : (¬ ∀ x, p x) → (∃ x, ¬ p x) := begin intro n
x * x 如何通过将其存储在“auto 变量”中来更改?我认为它应该仍然是相同的,并且我的测试表明类型、大小和值显然都是相同的。 但即使 x * x == (xx = x * x) 也是错误的。什么
假设,我们这样表达: someIQueryable.Where(x => x.SomeBoolProperty) someIQueryable.Where(x => !x.SomeBoolProper
我有一个字符串 1234X5678 我使用这个正则表达式来匹配模式 .X|..X|X. 我得到了 34X 问题是为什么我没有得到 4X 或 X5? 为什么正则表达式选择执行第二种模式? 最佳答案 这里
我的一个 friend 在面试时遇到了这个问题 找到使该函数返回真值的 x 值 function f(x) { return (x++ !== x) && (x++ === x); } 面试官
这个问题在这里已经有了答案: 10年前关闭。 Possible Duplicate: Isn't it easier to work with foo when it is represented b
我是 android 的新手,我一直在练习开发一个针对 2.2 版本的应用程序,我需要帮助了解如何将我的应用程序扩展到其他版本,即 1.x、2.3.x、3 .x 和 4.x.x,以及一些针对屏幕分辨率
为什么案例 1 给我们 :error: TypeError: x is undefined on line... //case 1 var x; x.push(x); console.log(x);
代码优先: # CASE 01 def test1(x): x += x print x l = [100] test1(l) print l CASE01 输出: [100, 100
我正在努力温习我的大计算。如果我有将所有项目移至 'i' 2 个空格右侧的函数,我有一个如下所示的公式: (n -1) + (n - 2) + (n - 3) ... (n - n) 第一次迭代我必须
给定 IP 字符串(如 x.x.x.x/x),我如何或将如何计算 IP 的范围最常见的情况可能是 198.162.1.1/24但可以是任何东西,因为法律允许的任何东西。 我要带198.162.1.1/
在我作为初学者努力编写干净的 Javascript 代码时,我最近阅读了 this article当我偶然发现这一段时,关于 JavaScript 中的命名空间: The code at the ve
我正在编写一个脚本,我希望避免污染 DOM 的其余部分,它将是一个用于收集一些基本访问者分析数据的第 3 方脚本。 我通常使用以下内容创建一个伪“命名空间”: var x = x || {}; 我正在
我尝试运行我的test_container_services.py套件,但遇到了以下问题: docker.errors.APIError:500服务器错误:内部服务器错误(“ b'{” message
是否存在这两个 if 语句会产生不同结果的情况? if(x as X != null) { // Do something } if(x is X) { // Do something } 编
我是一名优秀的程序员,十分优秀!