fix: drag group node

This commit is contained in:
zhanning.bzn 2019-09-03 14:51:52 +08:00
parent a367e01878
commit 862357583c
16 changed files with 2450 additions and 43 deletions

194
demos/area-chart-node.html Normal file
View File

@ -0,0 +1,194 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>面积图节点</title>
<style>
#mountNode {
background:#001528;
}
</style>
</head>
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script>
/**
* 该案例演示如何使用G6自定义面积图节点
* by 镜曦
*
*/
// 自定义面积图节点
G6.registerNode('area', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
const baseR = 30;
let nowAngle = 0;
// Ref line
let refR = baseR;
const refInc = 10;
for(let i = 0; i< 6; i++){
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: refR += refInc,
stroke:'rgba(255,255,255,0.4)',
lineDash:[4, 4],
}
});
}
const everyIncAngle = 2 * Math.PI * (360 / 5 ) / 360;
const tempIncValues = [baseR, baseR, baseR, baseR, baseR];
const allRs = [];
cfg.details.forEach(cat =>{
const oneRs = [];
cat.values.forEach((v, i) =>{
const R = tempIncValues[i] + v * 0.4;
oneRs.push(R);
tempIncValues[i] = R;
});
allRs.push(oneRs);
});
const strokeColors = [
'rgba(37,203,253,1)',
'rgba(254,255,123,1)',
'rgba(254,171,58,1)',
'rgba(254,87,102,1)',
'rgba(22,193,118,1)',
];
const fillColors = [
'rgba(37,203,253,0.5)',
'rgba(254,255,123,0.5)',
'rgba(254,171,58,0.5)',
'rgba(254,87,102,0.5)',
'rgba(22,193,118,0.5)',
];
allRs.reverse().forEach((Rs, index) =>{
let curAngle = 0;
const poss = [];
Rs.forEach(r=>{
const xPos = r * Math.cos(curAngle);
const yPos = r * Math.sin(curAngle);
curAngle += everyIncAngle;
poss.push([xPos, yPos]);
});
const Ls = poss.map((p, i)=>{
if( i === 0 ){
return ["M", ...p]
}
return ["L", ...p]
});
console.log('Ls', ...Ls);
const shape = group.addShape('path', {
attrs: {
path: [
...Ls,
['Z'] // 封闭
],
stroke:strokeColors[index] ,
fill:fillColors[index],
}
});
});
let nowAngle2 = 0;
const everyIncAngleCat = 2 * Math.PI * (360 / 5 ) / 360;
for(let i = 0; i < 5; i++){
const r = 30 + 60;
const xPos = r * Math.cos(nowAngle2);
const yPos = r * Math.sin(nowAngle2);
const shape = group.addShape('path', {
attrs: {
path: [
['M', 0, 0 ],
['L', xPos, yPos],
],
lineDash:[4, 4],
stroke: 'darkgray' // 颜色应用到边上,如果应用到填充,则使用 fill: cfg.color
}
});
nowAngle2 += everyIncAngleCat;
}
// 添加一个和背景色相同的圆形
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold'
}
});
}
return group;
}
});
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500
})
const data = {
nodes: [
{
id: 'nodeD',
x: 150,
y: 200,
label: 'Area',
shape:'area',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,40,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,40,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,20,20,20], color:"#16c176"},
],
centerColor:'#0066FF',
}
]
}
graph.data(data)
graph.render()
</script>
</body>
</html>

141
demos/bar-chart-node.html Normal file
View File

@ -0,0 +1,141 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>环形柱状图节点</title>
<style>
#mountNode {
background:#001528;
}
</style>
</head>
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script>
/**
* 该案例演示如何自定义一个类似南丁格尔玫瑰一样的节点
* by 镜曦
*/
/**
* 注册一个类似南丁格尔玫瑰一样的节点
*/
G6.registerNode('circleBar', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
/*
G
Fan
x: 扇形圆心的 x 坐标
y: 扇形圆心的 y 坐标
rs: 内圈半径
re: 外圈半径
startAngle: 起点弧度
endAngle: 终点弧度
clockwise: 为true时顺时针渲染为false时逆时针渲染
*/
const baseR = 30;
let nowAngle = 0;
const everyIncAngle = 2 * Math.PI * (360 / 5 / 5) / 360;
cfg.details.forEach(cat =>{
cat.values.forEach(item =>{
const re = item+baseR;
const fan = group.addShape('fan', {
attrs:{
x:0,
y:0,
rs:baseR,
re:item+baseR,
startAngle:nowAngle,
endAngle: nowAngle += everyIncAngle,
clockwise:false,
stroke: 'darkgray',
fill:cat.color,
}
});
// 加上交互动画
fan.on('mouseenter', function(evt) {
fan.animate({
re: re + 8,
repeat: false
}, 300);
});
fan.on('mouseleave', function(evt) {
fan.animate({
re:re,
repeat: false
}, 300);
});
// 设置class
fan.set("className", 'littleCircle');
});
});
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold',
}
});
}
return group;
}
});
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500
})
const data = {
nodes: [
{
id: 'nodeA',
x: 150,
y: 150,
label: 'Bar',
shape:'circleBar',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,40,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,40,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,20,20,20], color:"#16c176"},
],
centerColor:'#0066FF',
}
]
}
graph.data(data)
graph.render()
</script>
</body>
</html>

936
demos/chart-node.html Normal file
View File

@ -0,0 +1,936 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>统计图表节点</title>
<style>
#mountNode {
background:#001528;
}
.graph-tooltip {
position: absolute;
top: 0;
left: 0;
border: 1px solid #e2e2e2;
border-radius: 4px;
font-size: 12px;
color: #545454;
background-color: rgba(255, 255, 255, 0.9);
padding: 10px 8px;
box-shadow: rgb(174, 174, 174) 0px 0px 10px;
}
</style>
</head>
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script>
/**
* 该案例演示如何使用G6自定义面积图节点
* by 镜曦
*
*/
/**
* 注册一个类似南丁格尔玫瑰一样的节点
*/
G6.registerNode('circleBar', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
/*
G
Fan
x: 扇形圆心的 x 坐标
y: 扇形圆心的 y 坐标
rs: 内圈半径
re: 外圈半径
startAngle: 起点弧度
endAngle: 终点弧度
clockwise: 为true时顺时针渲染为false时逆时针渲染
*/
const baseR = 30;
let nowAngle = 0;
const everyIncAngle = 2 * Math.PI * (360 / 5 / 5) / 360;
cfg.details.forEach(cat =>{
cat.values.forEach(item =>{
const re = item+baseR;
const fan = group.addShape('fan', {
attrs:{
x:0,
y:0,
rs:baseR,
re:item+baseR,
startAngle:nowAngle,
endAngle: nowAngle += everyIncAngle,
clockwise:false,
stroke: 'darkgray',
fill:cat.color,
}
});
// 加上交互动画
fan.on('mouseenter', function(evt) {
fan.animate({
re: re + 8,
repeat: false
}, 300);
});
fan.on('mouseleave', function(evt) {
fan.animate({
re:re,
repeat: false
}, 300);
});
// 设置class
fan.set("className", 'littleCircle');
});
});
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold',
}
});
}
return group;
}
});
/**
* 注册一个 分布在圆周上的折线图
*/
G6.registerNode('circleLine', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
const baseR = 30;
let nowAngle = 0;
// Ref line
let refR = baseR;
const refInc = 10;
for(let i = 0; i< 5; i++){
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: refR += refInc,
stroke:'rgba(255,255,255,0.4)',
lineDash:[4, 4],
}
});
}
const everyIncAngle = 2 * Math.PI * (360 / 5 / 5) / 360;
cfg.details.forEach(cat =>{
// 计算一系列点的位置
const postions = [];
cat.values.forEach((item, index) =>{
const r = baseR + item;
const xPos = r * Math.cos(nowAngle);
const yPos = r * Math.sin(nowAngle);
nowAngle += everyIncAngle;
postions.push([xPos, yPos]);
if(index === 4){
const r = baseR + item;
const xPos = r * Math.cos(nowAngle );
const yPos = r * Math.sin(nowAngle );
postions.push([xPos, yPos]);
}
});
const pathArrayL = postions.map(item =>(["L", ...item]));
// 添加连线
const shape = group.addShape('path', {
attrs: {
path: [
['M', 0, 0 ], // 上部顶点
...pathArrayL,
['Z'] // 封闭
],
stroke: cat.color // 颜色应用到边上,如果应用到填充,则使用 fill: cfg.color
}
});
// 添加标注点
postions.forEach(( pos, index )=>{
if(index !== 5){
const littleCircle = group.addShape('circle', {
// attrs: style
attrs: {
x: pos[0], // 居中
y: pos[1],
r: 2,
fill: 'black',
stroke:cat.color,
cursor: "pointer",
}
});
// 加上交互动画
littleCircle.on('mouseenter', function(evt) {
littleCircle.animate({
r: 5,
repeat: false
}, 200);
});
littleCircle.on('mouseleave', function(evt) {
littleCircle.animate({
r: 2,
repeat: false
}, 200);
});
// 设置class
littleCircle.set("className", 'littleCircle');
}
})
/*
const shape = group.addShape('path', {
attrs: {
path: [
['M', 0, 0 ], // 上部顶点
['L', width / 2, 0], // 右侧点
['L', 0, height / 2], // 下部
['L', - width / 2, 0], // 左侧
['Z'] // 封闭
],
stroke: cfg.color // 颜色应用到边上,如果应用到填充,则使用 fill: cfg.color
}
});
*/
});
// 添加一个和背景色相同的圆形
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold',
}
});
}
return group;
}
});
/**
* 注册一个 只有标注点
*/
G6.registerNode('justPoints', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
const baseR = 30;
let nowAngle = 0;
// Ref line
let refR = baseR;
const refInc = 10;
for(let i = 0; i< 5; i++){
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: refR += refInc,
stroke:'rgba(255,255,255,0.4)',
lineDash:[4, 4],
}
});
}
const everyIncAngle = 2 * Math.PI * (360 / 5 / 5) / 360;
nowAngle = nowAngle + everyIncAngle / 2;
cfg.details.forEach(cat =>{
// 计算一系列点的位置
const postions = [];
cat.values.forEach((item, index) =>{
const r = baseR + item;
const xPos = r * Math.cos(nowAngle);
const yPos = r * Math.sin(nowAngle);
nowAngle += everyIncAngle;
postions.push([xPos, yPos]);
if(index === 4){
const r = baseR + item;
const xPos = r * Math.cos(nowAngle );
const yPos = r * Math.sin(nowAngle );
postions.push([xPos, yPos]);
}
});
// 添加标注点
postions.forEach(( pos, index )=>{
if(index !== 5){
group.addShape('circle', {
// attrs: style
attrs: {
x: pos[0], // 居中
y: pos[1],
r: 2,
fill: 'black',
stroke:cat.color,
}
});
}
})
/*
const shape = group.addShape('path', {
attrs: {
path: [
['M', 0, 0 ], // 上部顶点
['L', width / 2, 0], // 右侧点
['L', 0, height / 2], // 下部
['L', - width / 2, 0], // 左侧
['Z'] // 封闭
],
stroke: cfg.color // 颜色应用到边上,如果应用到填充,则使用 fill: cfg.color
}
});
*/
});
let nowAngle2 = 0;
const everyIncAngleCat = 2 * Math.PI * (360 / 5 ) / 360;
for(let i = 0; i < 5; i++){
const r = 30 + 50;
const xPos = r * Math.cos(nowAngle2);
const yPos = r * Math.sin(nowAngle2);
const shape = group.addShape('path', {
attrs: {
path: [
['M', 0, 0 ],
['L', xPos, yPos],
],
lineDash:[4, 4],
stroke: 'darkgray' // 颜色应用到边上,如果应用到填充,则使用 fill: cfg.color
}
});
nowAngle2 += everyIncAngleCat;
}
// 添加一个和背景色相同的圆形
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold',
}
});
}
return group;
}
});
/**
* 注册一个 面积图节点
*/
G6.registerNode('area', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
const baseR = 30;
let nowAngle = 0;
// Ref line
let refR = baseR;
const refInc = 10;
for(let i = 0; i< 6; i++){
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: refR += refInc,
stroke:'rgba(255,255,255,0.4)',
lineDash:[4, 4],
}
});
}
const everyIncAngle = 2 * Math.PI * (360 / 5 ) / 360;
const tempIncValues = [baseR, baseR, baseR, baseR, baseR];
const allRs = [];
cfg.details.forEach(cat =>{
const oneRs = [];
cat.values.forEach((v, i) =>{
const R = tempIncValues[i] + v * 0.4;
oneRs.push(R);
tempIncValues[i] = R;
});
allRs.push(oneRs);
});
const strokeColors = [
'rgba(37,203,253,1)',
'rgba(254,255,123,1)',
'rgba(254,171,58,1)',
'rgba(254,87,102,1)',
'rgba(22,193,118,1)',
];
const fillColors = [
'rgba(37,203,253,0.5)',
'rgba(254,255,123,0.5)',
'rgba(254,171,58,0.5)',
'rgba(254,87,102,0.5)',
'rgba(22,193,118,0.5)',
];
allRs.reverse().forEach((Rs, index) =>{
let curAngle = 0;
const poss = [];
Rs.forEach(r=>{
const xPos = r * Math.cos(curAngle);
const yPos = r * Math.sin(curAngle);
curAngle += everyIncAngle;
poss.push([xPos, yPos]);
});
const Ls = poss.map((p, i)=>{
if( i === 0 ){
return ["M", ...p]
}
return ["L", ...p]
});
console.log('Ls', ...Ls);
const shape = group.addShape('path', {
attrs: {
path: [
...Ls,
['Z'] // 封闭
],
stroke:strokeColors[index] ,
fill:fillColors[index],
}
});
});
let nowAngle2 = 0;
const everyIncAngleCat = 2 * Math.PI * (360 / 5 ) / 360;
for(let i = 0; i < 5; i++){
const r = 30 + 60;
const xPos = r * Math.cos(nowAngle2);
const yPos = r * Math.sin(nowAngle2);
const shape = group.addShape('path', {
attrs: {
path: [
['M', 0, 0 ],
['L', xPos, yPos],
],
lineDash:[4, 4],
stroke: 'darkgray' // 颜色应用到边上,如果应用到填充,则使用 fill: cfg.color
}
});
nowAngle2 += everyIncAngleCat;
}
// 添加一个和背景色相同的圆形
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold'
}
});
}
return group;
}
});
/**
环 1
*/
G6.registerNode('rings1', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
/*
G
Fan
x: 扇形圆心的 x 坐标
y: 扇形圆心的 y 坐标
rs: 内圈半径
re: 外圈半径
startAngle: 起点弧度
endAngle: 终点弧度
clockwise: 为true时顺时针渲染为false时逆时针渲染
*/
const baseR = 30;
let nowAngle = 0;
const everyIncAngle = 2 * Math.PI * (360 / 5 / 5) / 360;
cfg.details.forEach(cat =>{
cat.values.forEach(item =>{
const baseNbr = Math.ceil(item / 10);
const baseIncR = 7;
let nowStartR = baseR;
const last = item % 10;
const endAngle = nowAngle + everyIncAngle;
for (let i = 0; i < baseNbr ; i ++ ) {
const fan = group.addShape('fan', {
attrs:{
x:0,
y:0,
rs:nowStartR,
re:nowStartR + baseIncR,
startAngle:nowAngle,
endAngle:endAngle,
clockwise:false,
stroke: 'darkgray',
fill:cat.color,
}
});
nowStartR = nowStartR + baseIncR + 2
if(i === baseNbr -1 && last !== 0){
const fan = group.addShape('fan', {
attrs:{
x:0,
y:0,
rs:nowStartR,
re:nowStartR + baseIncR * last / 10,
startAngle:nowAngle,
endAngle:endAngle,
clockwise:false,
stroke: 'darkgray',
fill:cat.color,
}
});
}
}
nowAngle = endAngle
});
});
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold',
}
});
}
return group;
}
});
/**
* 注册一个 面积图节点
*/
G6.registerNode('rings2', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
const baseR = 30;
let nowAngle = 0;
// Ref line
let refR = baseR;
const refInc = 10;
for(let i = 0; i< 6; i++){
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: refR += refInc,
stroke:'rgba(255,255,255,0.4)',
lineDash:[4, 4],
}
});
}
const everyIncAngle = 2 * Math.PI * (360 / 5 ) / 360;
const tempIncValues = [baseR, baseR, baseR, baseR, baseR];
const allRs = [];
cfg.details.forEach(cat =>{
const oneRs = [];
cat.values.forEach((v, i) =>{
const R = tempIncValues[i] + v * 0.4;
oneRs.push(R);
tempIncValues[i] = R;
});
allRs.push(oneRs);
});
const strokeColors = [
'rgba(37,203,253,1)',
'rgba(254,255,123,1)',
'rgba(254,171,58,1)',
'rgba(254,87,102,1)',
'rgba(22,193,118,1)',
];
const fillColors = [
'rgba(37,203,253,0.5)',
'rgba(254,255,123,0.5)',
'rgba(254,171,58,0.5)',
'rgba(254,87,102,0.5)',
'rgba(22,193,118,0.5)',
];
allRs.reverse().forEach((Rs, index) =>{
let curAngle = 0;
const poss = [];
Rs.forEach(r=>{
const baseNbr = Math.ceil(r / 10);
const baseIncR = 7;
let nowStartR = baseR;
const last = r % 10;
for(let i = 0; i < baseNbr; i++){
const endAngle = nowAngle + everyIncAngle;
const fan = group.addShape('fan', {
attrs:{
x:0,
y:0,
rs:nowStartR,
re:nowStartR + baseIncR,
startAngle:nowAngle,
endAngle:endAngle,
clockwise:false,
stroke: 'darkgray',
fill:strokeColors[index],
}
});
nowStartR = nowStartR + baseIncR + 2
if(i === baseNbr -1 && last !== 0){
const fan = group.addShape('fan', {
attrs:{
x:0,
y:0,
rs:nowStartR,
re:nowStartR + baseIncR * last / 10,
startAngle:nowAngle,
endAngle:endAngle,
clockwise:false,
stroke: 'darkgray',
fill:strokeColors[index],
}
});
}
nowAngle = endAngle
}
});
});
// 添加一个和背景色相同的圆形
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold'
}
});
}
return group;
}
});
/** 数据 */
const data = {
nodes: [{
id: 'nodeA',
x: 150,
y: 150,
label: 'Bar',
shape:'circleBar',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,40,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,40,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,20,20,20], color:"#16c176"},
],
centerColor:'#0066FF',
},
{
id: 'nodeB',
x: 400,
y: 150,
label: 'Line',
shape:'circleLine',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,40,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,40,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,20,20,20], color:"#16c176"},
],
centerColor:'#0066FF',
},
{
id: 'nodeC',
x: 650,
y: 150,
label: 'Point',
shape:'justPoints',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,40,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,40,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,20,20,20], color:"#16c176"},
],
centerColor:'#0066FF',
},
{
id: 'nodeD',
x: 150,
y: 400,
label: 'Area',
shape:'area',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,40,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,40,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,20,20,20], color:"#16c176"},
],
centerColor:'#0066FF',
},
{
id: 'nodeF',
x: 400,
y: 400,
label: 'Rings1',
shape:'rings1',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,48,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,4,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,25,20,20], color:"#16c176"},
],
centerColor:'#0066FF',
},
],
edges: [
//{source:'nodeA', target:'nodeB', color:"rgba(0, 255, 255, 0.5)"},
//{source:'nodeB', target:'nodeC', color:"rgba(0, 255, 255, 0.5)"},
]
};
const graph = new G6.Graph({
container: 'mountNode',
width: 1000,
height: 600
});
graph.on("node:mouseenter", function(event) {
var node = event.item;
var nodeId = node.get("model").id;
var shape = event.target;
if (shape.get("className") === "littleCircle") {
// 如果点击是发生在节点里面的小圆上显示tooltip
console.log('x', event);
console.log('Y', event);
showTooltip("tooltip for " + nodeId, {
x: event.x,
y: event.y
});
} else {
// 否则隐藏tooltip
hideTooltip();
}
});
graph.on("node:mouseleave", function(event) {
hideTooltip();
});
graph.data(data);
graph.render();
var tooltipEl = null;
// 在指定的位置显示tooltip
function showTooltip(message, position) {
const offSetX = 50;
if (!tooltipEl) {
var container = document.getElementById("mountNode");
tooltipEl = document.createElement("div");
tooltipEl.setAttribute("class", "graph-tooltip");
container.appendChild(tooltipEl);
}
tooltipEl.textContent = message;
// tooltip是相对于画布canvas element绝对定位所以position的xy必须是相对于画布的坐标
tooltipEl.style.left = position.x + offSetX+ "px";
tooltipEl.style.top = position.y + "px";
tooltipEl.style.display = "block";
}
// 隐藏tooltip
function hideTooltip() {
if (!tooltipEl) {
return;
}
tooltipEl.style.display = "none";
}
</script>
</body>
</html>

View File

@ -40,6 +40,33 @@
}, },
modes: { modes: {
default: [ 'drag-canvas', 'zoom-canvas', 'collapse-expand-group' ] default: [ 'drag-canvas', 'zoom-canvas', 'collapse-expand-group' ]
},
groupStyle: {
default: {
lineWidth: 2,
stroke: '#A3B1BF',
radius: 10,
lineDash: [ 5, 5 ],
strokeOpacity: 0.9,
fill: '#F3F9FF',
fillOpacity: 0.8,
opacity: 0.8
},
hover: {
stroke: '#faad14',
fill: '#ffe58f',
fillOpacity: 0.3,
opacity: 0.3,
lineWidth: 3
},
// 收起状态样式
collapseStyle: {
r: 50,
// lineDash: [ 5, 5 ],
stroke: '#ffa39e',
lineWidth: 3,
fill: '#ffccc7'
}
} }
}); });

View File

@ -0,0 +1,587 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script>
/**
* 该案例演示如何交互复杂的列表组件
* 内网可以参考https://riddle.alibaba-inc.com/riddles/3acae792
*
*/
const COLLAPSE_ICON = (x, y, r) => {
return [
['M', x, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x + 2, y],
['L', x + 2 * r - 2, y],
];
};
const EXPAND_ICON = (x, y, r) => {
return [
['M', x, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x + 2, y],
['L', x + 2 * r - 2, y],
['M', x + r, y - r + 2],
['L', x + r, y + r - 2],
];
};
//注册边
G6.registerEdge('hvh', {
draw(cfg, group) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const shape = group.addShape('path', {
attrs: {
endArrow: true,
endArrow: {
path: 'M 10,0 L -10,-10 L -10,10 Z',
d: 10,
},
stroke: '#A3B1BF',
path: [
['M', startPoint.x, startPoint.y],
['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y],
['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y],
['L', endPoint.x, endPoint.y],
],
},
});
return shape;
},
});
//root节点
G6.registerNode(
'tree-node',
{
drawShape: (cfg, group) => {
const rect = group.addShape('rect', {
attrs: {
fill: '#0096e0',
},
});
const content = cfg.name;
const text = group.addShape('text', {
attrs: {
text: content,
x: 0,
y: 0,
textAlign: 'left',
textBaseline: 'middle',
fill: '#fff',
},
});
const bbox = text.getBBox();
const hasChildren = cfg.children && cfg.children.length > 0;
if (hasChildren) {
group.addShape('marker', {
attrs: {
x: bbox.maxX + 6,
y: bbox.minX + bbox.height / 2 - 6,
r: 6,
symbol: COLLAPSE_ICON,
stroke: '#fff',
lineWidth: 2,
},
className: 'collapse-icon',
});
}
//节点高度(getVGap值保持一致)
let height = 50;
//根节点高度:子节点高度+节点间距
let rootHeight = height * count + height * (count - 1);
//节点Y坐标
let nodeY = bbox.minY - 6;
//根节点Y坐标节点Y坐标上移节点高度*count
let rootY = nodeY - height * (count - 1) + 12.5;
//console.log('rootHeight');
//console.log(rootHeight);
//console.log('rootY');
//console.log(rootY);
rect.attr({
x: bbox.minX - 4,
y: hasChildren ? rootY : nodeY,
width: bbox.width + (hasChildren ? 26 : 8),
height: hasChildren ? rootHeight : height,
});
return rect;
},
},
'single-shape',
);
//子节点
G6.registerNode('expandNode', {
draw: function draw(cfg, group) {
var mainGroup = group.addGroup({
id: 'main-group',
});
var keyShape = mainGroup.addShape('rect', {
attrs: {
x: 0,
y: 0,
width: 100 + 60 * cfg.values.length,
height: 50,
fill: '#f5f5f5',
},
});
// name text
mainGroup.addShape('text', {
attrs: {
text: cfg.name,
fill: '#000',
width: 130,
x: 10,
y: 32,
},
});
var subGroup = group.addGroup({
id: 'sub-group',
});
cfg.values.forEach(function(data, index) {
subGroup.addShape('rect', {
attrs: {
x: 110 + index * 60,
y: 0,
width: 50,
height: 50,
},
});
subGroup.addShape('text', {
attrs: {
text: data.key,
fill: '#000',
x: 130 + index * 60,
y: 20,
fontSize: 10,
textBaseline: 'middle',
className: 'sub-group-text',
},
});
subGroup.addShape('text', {
attrs: {
text: data.value,
fill: '#000',
x: 130 + index * 60,
y: 30,
fontSize: 10,
textBaseline: 'middle',
textAlign: 'left',
className: 'sub-group-text',
},
});
});
var listGroup = group.addGroup({
id: 'detail-list-group',
});
listGroup.addShape('rect', {
attrs: {
width: 100 + 60 * cfg.values.length - 70,
height: 30 * cfg.properties.length + 20,
fill: '#fff',
x: 70,
y: 30,
},
});
var rectWidth = 100 + 60 * cfg.values.length - 80;
cfg.properties.forEach(function(property, index) {
listGroup.addShape('rect', {
attrs: {
width: rectWidth,
height: 30,
fill: '#e8e8e8',
x: 80,
y: 40 * index + 40,
},
});
var count = 0;
for (var p in property) {
// 每个rect中添加5个文本
listGroup.addShape('text', {
attrs: {
text: property[p],
fill: '#000',
x: 85 + count * (rectWidth / cfg.values.length) - count * 10,
y: 40 * index + 40 + 15,
fontSize: 10,
textBaseline: 'middle',
textAlign: 'left',
},
});
count++;
}
});
listGroup.hide();
return keyShape;
},
});
//graph
const graph = new G6.TreeGraph({
container: 'mountNode',
width: window.innerWidth - 100,
height: window.innerHeight - 100,
modes: {
default: [
{
type: 'collapse-expand',
onChange: (item, collapsed) => {
const data = item.get('model').data;
const icon = item.get('group').findByClassName('collapse-icon');
if (collapsed) {
icon.attr('symbol', EXPAND_ICON);
} else {
icon.attr('symbol', COLLAPSE_ICON);
}
data.collapsed = collapsed;
return true;
},
},
'drag-canvas',
'zoom-canvas',
],
},
defaultNode: {
shape: 'tree-node',
anchorPoints: [[0, 0.5], [1, 0.5]],
},
defaultEdge: {
shape: 'hvh',
},
edgeStyle: {
default: {
stroke: '#A3B1BF',
},
},
layout: {
type: 'compactBox',
direction: 'LR',
getId: d => {
return d.id;
},
getHeight: () => {
return 0;
},
getWidth: () => {
return 16;
},
getVGap: d => {
return 50
},
getHGap: () => {
return 80;
}
}
});
const data = {
id: 'root',
name: 'root',
children: [
{
id: 'shape2',
//x: 0,
//y: 50,
shape: 'expandNode',
name: '网站引流1',
values: [
{
key: '曝光率',
value: '1938.33w',
},
{
key: '流入UV',
value: '1938.33w',
},
{
key: '点击率',
value: '99.9%',
},
{
key: '占比',
value: '99.9%',
},
],
properties: [
{
name: '宫格',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '更多应用',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '搜索',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '扫一扫',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '我的Tab',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
],
},
{
id: 'shape30',
//x: 0,
//y: 50,
shape: 'expandNode',
name: '网站引流',
values: [
{
key: '曝光率',
value: '19.09',
},
{
key: '流入UV',
value: '910',
},
{
key: '点击率',
value: '90',
},
{
key: '占比',
value: '90',
},
],
properties: [
{
name: '宫格',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '更多应用',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '搜索',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '扫一扫',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '我的Tab',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
],
},
{
id: 'shape3',
//x: 0,
//y: 50,
shape: 'expandNode',
name: '网站引流',
values: [
{
key: '曝光率',
value: '19.09',
},
{
key: '流入UV',
value: '910',
},
{
key: '点击率',
value: '90',
},
{
key: '占比',
value: '90',
},
],
properties: [
{
name: '宫格',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '更多应用',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '搜索',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '扫一扫',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
{
name: '我的Tab',
value1: '1938.33w',
value2: '1938.33w',
value3: '99.9%',
value4: '99.9%',
},
],
},
],
};
//统计子节点个数
let count = -1;
G6.Util.traverseTree(data, e => {
count++;
});
graph.data(data);
graph.render();
graph.fitView();
// 点击node展开详情
graph.on('node:click', function(evt) {
console.log(graph);
var target = evt.target;
var parentGroup = target.get('parent').get('parent');
var detailGroup = parentGroup.findById('detail-list-group');
// 将sub-group中的内容网上移动一段距离
var subGroup = parentGroup.findById('sub-group');
var keyTexts = subGroup.findAll(function(item) {
return item.attr('className') === 'sub-group-text';
});
var isVisible = detailGroup.get('visible');
if (isVisible) {
detailGroup.hide();
keyTexts.forEach(function(text) {
var top = text.attr('y');
text.attr('y', top + 10);
});
const layout = {
type: 'compactBox',
direction: 'LR',
getId: d => {
return d.id;
},
getHeight: () => {
return 0;
},
getWidth: () => {
return 16;
},
getVGap: d => {
return 50
},
getHGap: () => {
return 80;
},
}
graph.changeLayout(layout)
} else {
keyTexts.forEach(function(text) {
var top = text.attr('y');
text.attr('y', top - 10);
});
detailGroup.show();
const layout = {
type: 'compactBox',
direction: 'LR',
getId: d => {
return d.id;
},
getHeight: () => {
return 0;
},
getWidth: () => {
return 16;
},
getVGap: d => {
console.log('ok', d, evt);
const id = evt.item.get('id');
if (d.id === id) {
return 120;
} else {
return 50;
}
},
getHGap: () => {
return 80;
},
}
graph.changeLayout(layout)
}
//graph.paint();
});
</script>
</body>
</html>

View File

@ -60,7 +60,6 @@
document.getElementById('changeView').addEventListener('click', (evt) => { document.getElementById('changeView').addEventListener('click', (evt) => {
const edge=graph.findById('edge1') const edge=graph.findById('edge1')
const nodeGroup = graph.get('nodeGroup') const nodeGroup = graph.get('nodeGroup')
const edgeGroup = graph.get('edgeGroup')
const edge1G = edge.get('group') const edge1G = edge.get('group')
edge1G.toFront() edge1G.toFront()
nodeGroup.toBack(); nodeGroup.toBack();

194
demos/line-chart-node.html Normal file
View File

@ -0,0 +1,194 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>折线图节点</title>
<style>
#mountNode {
background:#001528;
}
</style>
</head>
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script>
/**
* 该案例演示如何自定义一个折线图节点
* by 镜曦
*
*/
// 自定义折线图节点
G6.registerNode('circleLine', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
const baseR = 30;
let nowAngle = 0;
// Ref line
let refR = baseR;
const refInc = 10;
for(let i = 0; i< 5; i++){
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: refR += refInc,
stroke:'rgba(255,255,255,0.4)',
lineDash:[4, 4],
}
});
}
const everyIncAngle = 2 * Math.PI * (360 / 5 / 5) / 360;
cfg.details.forEach(cat =>{
// 计算一系列点的位置
const postions = [];
cat.values.forEach((item, index) =>{
const r = baseR + item;
const xPos = r * Math.cos(nowAngle);
const yPos = r * Math.sin(nowAngle);
nowAngle += everyIncAngle;
postions.push([xPos, yPos]);
if(index === 4){
const r = baseR + item;
const xPos = r * Math.cos(nowAngle );
const yPos = r * Math.sin(nowAngle );
postions.push([xPos, yPos]);
}
});
const pathArrayL = postions.map(item =>(["L", ...item]));
// 添加连线
const shape = group.addShape('path', {
attrs: {
path: [
['M', 0, 0 ], // 上部顶点
...pathArrayL,
['Z'] // 封闭
],
stroke: cat.color // 颜色应用到边上,如果应用到填充,则使用 fill: cfg.color
}
});
// 添加标注点
postions.forEach(( pos, index )=>{
if(index !== 5){
const littleCircle = group.addShape('circle', {
// attrs: style
attrs: {
x: pos[0], // 居中
y: pos[1],
r: 2,
fill: 'black',
stroke:cat.color,
cursor: "pointer",
}
});
// 加上交互动画
littleCircle.on('mouseenter', function(evt) {
littleCircle.animate({
r: 5,
repeat: false
}, 200);
});
littleCircle.on('mouseleave', function(evt) {
littleCircle.animate({
r: 2,
repeat: false
}, 200);
});
// 设置class
littleCircle.set("className", 'littleCircle');
}
})
/*
const shape = group.addShape('path', {
attrs: {
path: [
['M', 0, 0 ], // 上部顶点
['L', width / 2, 0], // 右侧点
['L', 0, height / 2], // 下部
['L', - width / 2, 0], // 左侧
['Z'] // 封闭
],
stroke: cfg.color // 颜色应用到边上,如果应用到填充,则使用 fill: cfg.color
}
});
*/
});
// 添加一个和背景色相同的圆形
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold',
}
});
}
return group;
}
});
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500
})
const data = {
nodes: [
{
id: 'nodeB',
x: 400,
y: 150,
label: 'Line',
shape:'circleLine',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,40,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,40,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,20,20,20], color:"#16c176"},
],
centerColor:'#0066FF',
}
]
}
graph.data(data)
graph.render()
</script>
</body>
</html>

173
demos/point-chart-node.html Normal file
View File

@ -0,0 +1,173 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>标注图节点</title>
<style>
#mountNode {
background:#001528;
}
</style>
</head>
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script>
/**
* 该案例演示如何自定义一个标注点节点
* by 镜曦
*
*/
// 自定义标注点节点
G6.registerNode('justPoints', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
const baseR = 30;
let nowAngle = 0;
// Ref line
let refR = baseR;
const refInc = 10;
for(let i = 0; i< 5; i++){
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: refR += refInc,
stroke:'rgba(255,255,255,0.4)',
lineDash:[4, 4],
}
});
}
const everyIncAngle = 2 * Math.PI * (360 / 5 / 5) / 360;
nowAngle = nowAngle + everyIncAngle / 2;
cfg.details.forEach(cat =>{
// 计算一系列点的位置
const postions = [];
cat.values.forEach((item, index) =>{
const r = baseR + item;
const xPos = r * Math.cos(nowAngle);
const yPos = r * Math.sin(nowAngle);
nowAngle += everyIncAngle;
postions.push([xPos, yPos]);
if(index === 4){
const r = baseR + item;
const xPos = r * Math.cos(nowAngle );
const yPos = r * Math.sin(nowAngle );
postions.push([xPos, yPos]);
}
});
// 添加标注点
postions.forEach(( pos, index )=>{
if(index !== 5){
group.addShape('circle', {
// attrs: style
attrs: {
x: pos[0], // 居中
y: pos[1],
r: 2,
fill: 'black',
stroke:cat.color,
}
});
}
})
});
let nowAngle2 = 0;
const everyIncAngleCat = 2 * Math.PI * (360 / 5 ) / 360;
for(let i = 0; i < 5; i++){
const r = 30 + 50;
const xPos = r * Math.cos(nowAngle2);
const yPos = r * Math.sin(nowAngle2);
const shape = group.addShape('path', {
attrs: {
path: [
['M', 0, 0 ],
['L', xPos, yPos],
],
lineDash:[4, 4],
stroke: 'darkgray' // 颜色应用到边上,如果应用到填充,则使用 fill: cfg.color
}
});
nowAngle2 += everyIncAngleCat;
}
// 添加一个和背景色相同的圆形
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold',
}
});
}
return group;
}
});
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500
})
const data = {
nodes: [
{
id: 'nodeC',
x: 250,
y: 150,
label: 'Point',
shape:'justPoints',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,40,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,40,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,20,20,20], color:"#16c176"},
],
centerColor:'#0066FF',
}
]
}
graph.data(data)
graph.render()
</script>
</body>
</html>

View File

@ -0,0 +1,146 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>堆叠柱状图节点</title>
<style>
#mountNode {
background:#001528;
}
</style>
</head>
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script>
/**
* 该案例演示如何自定义一个堆叠柱状图节点
* by 镜曦
*
*/
// 自定义标注点节点
G6.registerNode('stacked-bar-node', {
draw(cfg, group) {
const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小
const width = size[0];
const height = size[1];
/*
G
Fan
x: 扇形圆心的 x 坐标
y: 扇形圆心的 y 坐标
rs: 内圈半径
re: 外圈半径
startAngle: 起点弧度
endAngle: 终点弧度
clockwise: 为true时顺时针渲染为false时逆时针渲染
*/
const baseR = 30;
let nowAngle = 0;
const everyIncAngle = 2 * Math.PI * (360 / 5 / 5) / 360;
cfg.details.forEach(cat =>{
cat.values.forEach(item =>{
const baseNbr = Math.ceil(item / 10);
const baseIncR = 7;
let nowStartR = baseR;
const last = item % 10;
const endAngle = nowAngle + everyIncAngle;
for (let i = 0; i < baseNbr ; i ++ ) {
const fan = group.addShape('fan', {
attrs:{
x:0,
y:0,
rs:nowStartR,
re:nowStartR + baseIncR,
startAngle:nowAngle,
endAngle:endAngle,
clockwise:false,
stroke: 'darkgray',
fill:cat.color,
}
});
nowStartR = nowStartR + baseIncR + 2
if(i === baseNbr -1 && last !== 0){
const fan = group.addShape('fan', {
attrs:{
x:0,
y:0,
rs:nowStartR,
re:nowStartR + baseIncR * last / 10,
startAngle:nowAngle,
endAngle:endAngle,
clockwise:false,
stroke: 'darkgray',
fill:cat.color,
}
});
}
}
nowAngle = endAngle
});
});
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke:'darkgray',
}
});
if(cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle:'bold',
}
});
}
return group;
}
});
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500
})
const data = {
nodes: [
{
id: 'nodeF',
x: 100,
y: 100,
label: 'StackedBar',
shape:'stacked-bar-node',
anchorPoints: [
[0, 0.5], [1, 0.5]
],
details:[
{cat:'pv', values:[20,30,48,30,30], color:"#25cbfd"},
{cat:'dal', values:[40,30,20,30,50], color:"#feff7b"},
{cat:'uv', values:[40,30,30,4,40], color:"#feab3a"},
{cat:'sal', values:[20,30,50,20,20], color:"#fe5766"},
{cat:'cal', values:[10,10,25,20,20], color:"#16c176"},
],
centerColor:'#0066FF'
}]
}
graph.data(data)
graph.render()
</script>
</body>
</html>

View File

@ -98,7 +98,7 @@
"screenshot": "node ./bin/screenshot.js", "screenshot": "node ./bin/screenshot.js",
"start": "npm run dev", "start": "npm run dev",
"test": "torch --compile --renderer --opts test/mocha.opts --recursive ./test/unit", "test": "torch --compile --renderer --opts test/mocha.opts --recursive ./test/unit",
"test-live": "torch --compile --interactive --watch --opts test/mocha.opts --recursive ./test/unit/behavior/drag-group-spec.js", "test-live": "torch --compile --interactive --watch --opts test/mocha.opts --recursive ./test/unit/graph/controller/custom-group-spec.js",
"test-live-tree": "torch --compile --interactive --watch --opts test/mocha.opts --recursive ./test/unit/graph/tree-graph-spec.js", "test-live-tree": "torch --compile --interactive --watch --opts test/mocha.opts --recursive ./test/unit/graph/tree-graph-spec.js",
"test-bugs": "torch --compile --renderer --recursive ./test/bugs", "test-bugs": "torch --compile --renderer --recursive ./test/bugs",
"test-bugs-live": "torch --compile --interactive --watch --recursive ./test/bugs", "test-bugs-live": "torch --compile --interactive --watch --recursive ./test/bugs",

View File

@ -5,7 +5,7 @@
* @LastEditTime: 2019-08-23 11:13:43 * @LastEditTime: 2019-08-23 11:13:43
* @Description: 拖动群组 * @Description: 拖动群组
*/ */
const { merge } = require('lodash'); const deepMix = require('@antv/util/lib/deep-mix');
const delegateStyle = { const delegateStyle = {
fill: '#F3F9FF', fill: '#F3F9FF',
@ -275,7 +275,7 @@ module.exports = {
height, height,
x, x,
y, y,
...merge({}, delegateStyle, this.delegateStyle) ...deepMix({}, delegateStyle, this.delegateStyle)
}; };
// 如果delegate是circle // 如果delegate是circle
@ -289,7 +289,7 @@ module.exports = {
x: cx, x: cx,
y: cy, y: cy,
r, r,
...merge({}, delegateStyle, this.delegateStyle) ...deepMix({}, delegateStyle, this.delegateStyle)
} }
}); });
self.shapeOrigin = { x: cx, y: cy }; self.shapeOrigin = { x: cx, y: cy };

View File

@ -5,7 +5,7 @@
* @LastEditTime: 2019-08-23 13:54:53 * @LastEditTime: 2019-08-23 13:54:53
* @Description: 有group的情况下拖动节点的Behavior * @Description: 有group的情况下拖动节点的Behavior
*/ */
const { merge } = require('lodash'); const deepMix = require('@antv/util/lib/deep-mix');
const { delegateStyle } = require('../global'); const { delegateStyle } = require('../global');
const body = document.body; const body = document.body;
@ -15,8 +15,8 @@ module.exports = {
updateEdge: true, updateEdge: true,
delegate: true, delegate: true,
delegateStyle: {}, delegateStyle: {},
maxMultiple: 1.2, maxMultiple: 0.9,
minMultiple: 0.8 minMultiple: 1
}; };
}, },
getEvents() { getEvents() {
@ -26,12 +26,16 @@ module.exports = {
'node:dragend': 'onDragEnd', 'node:dragend': 'onDragEnd',
'canvas:mouseleave': 'onOutOfRange', 'canvas:mouseleave': 'onOutOfRange',
mouseenter: 'onMouseEnter', mouseenter: 'onMouseEnter',
mouseout: 'onMouseOut' mouseleave: 'onMouseLeave'
}; };
}, },
onMouseEnter(evt) { onMouseEnter(evt) {
const { target } = evt; const { target } = evt;
const groupId = target.get('groupId'); const groupId = target.get('groupId');
const type = target.get('type');
if (type !== 'circle') {
return;
}
if (groupId && this.origin) { if (groupId && this.origin) {
const graph = this.graph; const graph = this.graph;
const customGroupControll = graph.get('customGroupControll'); const customGroupControll = graph.get('customGroupControll');
@ -47,7 +51,7 @@ module.exports = {
* 拖动节点移除Group时的事件 * 拖动节点移除Group时的事件
* @param {Event} evt 事件句柄 * @param {Event} evt 事件句柄
*/ */
onMouseOut(evt) { onMouseLeave(evt) {
const { target } = evt; const { target } = evt;
const groupId = target.get('groupId'); const groupId = target.get('groupId');
if (groupId && this.origin) { if (groupId && this.origin) {
@ -59,7 +63,10 @@ module.exports = {
customGroupControll.setGroupStyle(keyShape, 'default'); customGroupControll.setGroupStyle(keyShape, 'default');
} }
this.inGroupId = null;
if (!groupId) {
this.inGroupId = null;
}
}, },
onDragStart(e) { onDragStart(e) {
if (!this.shouldBegin.call(this, e)) { if (!this.shouldBegin.call(this, e)) {
@ -93,6 +100,9 @@ module.exports = {
const customGroup = customGroupControll.customGroup; const customGroup = customGroupControll.customGroup;
const currentGroup = customGroup[groupId].nodeGroup; const currentGroup = customGroup[groupId].nodeGroup;
customGroupControll.setGroupStyle(currentGroup.get('keyShape'), 'hover'); customGroupControll.setGroupStyle(currentGroup.get('keyShape'), 'hover');
// 初始拖动时候如果是在当前群组中拖动则赋值为当前groupId
this.inGroupId = groupId;
} }
} else { } else {
// 拖动多个节点 // 拖动多个节点
@ -137,13 +147,8 @@ module.exports = {
const currentGroup = customGroup[groupId].nodeGroup; const currentGroup = customGroup[groupId].nodeGroup;
const keyShape = currentGroup.get('keyShape'); const keyShape = currentGroup.get('keyShape');
const currentGroupBBox = keyShape.getBBox(); // 当前
if (this.inGroupId !== groupId) {
const delegateShape = this.target.get('delegateShape');
const { x, y } = delegateShape.getBBox();
const { minX, minY, maxX, maxY } = currentGroupBBox;
if (x > maxX || x < minX || y > maxY || y < minY) {
customGroupControll.setGroupStyle(keyShape, 'default'); customGroupControll.setGroupStyle(keyShape, 'default');
} else { } else {
customGroupControll.setGroupStyle(keyShape, 'hover'); customGroupControll.setGroupStyle(keyShape, 'hover');
@ -224,11 +229,12 @@ module.exports = {
// 检测操作的群组中是否包括子群组 // 检测操作的群组中是否包括子群组
const groups = graph.get('groups'); const groups = graph.get('groups');
const hasSubGroup = !!groups.filter(g => g.parentId === groupId).length > 0; const hasSubGroup = !!groups.filter(g => g.parentId === groupId).length > 0;
const r = width > height ? width / 2 : height / 2 + (hasSubGroup ? 20 : 0); const addR = hasSubGroup ? 20 : 10;
const r = width > height ? width / 2 : height / 2;
const cx = (width + 2 * x) / 2; const cx = (width + 2 * x) / 2;
const cy = (height + 2 * y) / 2; const cy = (height + 2 * y) / 2;
keyShape.attr({ keyShape.attr({
r: r + groupNodes[groupId].length * 10, r: r + groupNodes[groupId].length * 10 + addR,
x: cx, x: cx,
y: cy y: cy
}); });
@ -247,7 +253,6 @@ module.exports = {
// 节点所在的GroupId // 节点所在的GroupId
const { groupId, id } = model; const { groupId, id } = model;
// console.log(groupId, this.inGroupId)
const customGroupControll = graph.get('customGroupControll'); const customGroupControll = graph.get('customGroupControll');
const customGroup = customGroupControll.customGroup; const customGroup = customGroupControll.customGroup;
const groupNodes = graph.get('groupNodes'); const groupNodes = graph.get('groupNodes');
@ -262,7 +267,11 @@ module.exports = {
const { minX, minY, maxX, maxY } = currentGroupBBox; const { minX, minY, maxX, maxY } = currentGroupBBox;
// 在自己的group中拖动判断是否拖出了自己的group // 在自己的group中拖动判断是否拖出了自己的group
if (!(x < maxX * this.maxMultiple && x > minX * this.minMultiple && y < maxY * this.maxMultiple && y > minY * this.minMultiple)) { // this.inGroupId !== groupId则说明拖出了原来的group拖到了其他group上面
// 则删除item中的groupId字段同时删除group中的nodeID
if (
!(x < maxX * this.maxMultiple && x > minX * this.minMultiple && y < maxY * this.maxMultiple && y > minY * this.minMultiple)
|| this.inGroupId !== groupId) {
// 拖出了group则删除item中的groupId字段同时删除group中的nodeID // 拖出了group则删除item中的groupId字段同时删除group中的nodeID
const currentGroupNodes = groupNodes[groupId]; const currentGroupNodes = groupNodes[groupId];
groupNodes[groupId] = currentGroupNodes.filter(node => node !== id); groupNodes[groupId] = currentGroupNodes.filter(node => node !== id);
@ -274,8 +283,10 @@ module.exports = {
} }
// 拖动到其他的group上面 // 拖动到其他的group上面
if (this.inGroupId !== groupId) { if (this.inGroupId !== groupId) {
// 拖动新的group后更新groupNodes及model中的groupId
const nodeInGroup = customGroup[this.inGroupId].nodeGroup; const nodeInGroup = customGroup[this.inGroupId].nodeGroup;
const keyShape = nodeInGroup.get('keyShape'); const targetKeyShape = nodeInGroup.get('keyShape');
// 将该节点添加到inGroupId中 // 将该节点添加到inGroupId中
if (groupNodes[this.inGroupId].indexOf(id) === -1) { if (groupNodes[this.inGroupId].indexOf(id) === -1) {
groupNodes[this.inGroupId].push(id); groupNodes[this.inGroupId].push(id);
@ -284,7 +295,7 @@ module.exports = {
model.groupId = this.inGroupId; model.groupId = this.inGroupId;
// 拖入节点后,根据最新的节点数量,重新计算群组大小 // 拖入节点后,根据最新的节点数量,重新计算群组大小
this.dynamicChangeGroupSize(evt, nodeInGroup, keyShape); this.dynamicChangeGroupSize(evt, nodeInGroup, targetKeyShape);
} }
customGroupControll.setGroupStyle(keyShape, 'default'); customGroupControll.setGroupStyle(keyShape, 'default');
} else if (this.inGroupId && !groupId) { } else if (this.inGroupId && !groupId) {
@ -372,7 +383,7 @@ module.exports = {
if (!this.shape) { if (!this.shape) {
// 拖动多个 // 拖动多个
const parent = graph.get('group'); const parent = graph.get('group');
const attrs = merge({}, delegateStyle, this.delegateStyle); const attrs = deepMix({}, delegateStyle, this.delegateStyle);
if (this.targets.length > 0) { if (this.targets.length > 0) {
const nodes = graph.findAllByState('node', 'selected'); const nodes = graph.findAllByState('node', 'selected');
if (nodes.length === 0) { if (nodes.length === 0) {

View File

@ -5,7 +5,8 @@
* @LastEditTime: 2019-08-22 18:41:45 * @LastEditTime: 2019-08-22 18:41:45
* @Description: 拖动节点的Behavior * @Description: 拖动节点的Behavior
*/ */
const { merge, isString } = require('lodash'); const isString = require('@antv/util/lib/type/is-string');
const deepMix = require('@antv/util/lib/deep-mix');
const { delegateStyle } = require('../global'); const { delegateStyle } = require('../global');
const body = document.body; const body = document.body;
@ -174,7 +175,7 @@ module.exports = {
if (!this.shape) { if (!this.shape) {
// 拖动多个 // 拖动多个
const parent = this.graph.get('group'); const parent = this.graph.get('group');
const attrs = merge({}, delegateStyle, this.delegateStyle); const attrs = deepMix({}, delegateStyle, this.delegateStyle);
if (this.targets.length > 0) { if (this.targets.length > 0) {
const { x, y, width, height, minX, minY } = this.calculationGroupPosition(); const { x, y, width, height, minX, minY } = this.calculationGroupPosition();
this.originPoint = { x, y, width, height, minX, minY }; this.originPoint = { x, y, width, height, minX, minY };

View File

@ -5,8 +5,8 @@
* @LastEditTime: 2019-08-23 11:44:32 * @LastEditTime: 2019-08-23 11:44:32
* @Description: Group Controller * @Description: Group Controller
*/ */
const { merge, isString } = require('lodash'); const isString = require('@antv/util/lib/type/is-string');
const deepMix = require('@antv/util/lib/deep-mix');
class CustomGroup { class CustomGroup {
getDefaultCfg() { getDefaultCfg() {
return { return {
@ -60,7 +60,8 @@ class CustomGroup {
// const { cfg = {} } = options; // const { cfg = {} } = options;
this.graph = graph; this.graph = graph;
window.graph = graph; window.graph = graph;
this.styles = this.getDefaultCfg(); const groupStyle = graph.get('groupStyle');
this.styles = deepMix({}, this.getDefaultCfg(), groupStyle);
// 创建的群组集合 // 创建的群组集合
this.customGroup = {}; this.customGroup = {};
// 群组初始位置集合 // 群组初始位置集合
@ -154,12 +155,12 @@ class CustomGroup {
const { hover: hoverStyle, default: defaultStyle } = this.styles; const { hover: hoverStyle, default: defaultStyle } = this.styles;
if (isString(style)) { if (isString(style)) {
if (style === 'default') { if (style === 'default') {
styles = merge({}, defaultStyle); styles = deepMix({}, defaultStyle);
} else if (style === 'hover') { } else if (style === 'hover') {
styles = merge({}, hoverStyle); styles = deepMix({}, hoverStyle);
} }
} else { } else {
styles = merge({}, defaultStyle, style); styles = deepMix({}, defaultStyle, style);
} }
for (const s in styles) { for (const s in styles) {
keyShape.attr(s, styles[s]); keyShape.attr(s, styles[s]);
@ -328,7 +329,7 @@ class CustomGroup {
} else { } else {
// 更新时候merge配置项 // 更新时候merge配置项
const { groupStyle } = customGroupStyle; const { groupStyle } = customGroupStyle;
const styles = merge({}, groupStyle, property); const styles = deepMix({}, groupStyle, property);
this.customGroup[groupId] = { this.customGroup[groupId] = {
nodeGroup: deletage, nodeGroup: deletage,
groupStyle: styles groupStyle: styles
@ -536,7 +537,7 @@ class CustomGroup {
}); });
// 缓存群组groupId下的edge和临时生成的node节点 // 缓存群组groupId下的edge和临时生成的node节点
this.delegateInGroup[groupId] = merge({ this.delegateInGroup[groupId] = deepMix({
sourceOutTargetInEdges, sourceOutTargetInEdges,
sourceInTargetOutEdges, sourceInTargetOutEdges,
edgesOuts, edgesOuts,
@ -571,7 +572,7 @@ class CustomGroup {
const { default: defaultStyle } = this.styles; const { default: defaultStyle } = this.styles;
// const styles = merge({}, defaultStyle, { x: cx, y: cy }); // const styles = deepMix({}, defaultStyle, { x: cx, y: cy });
for (const style in defaultStyle) { for (const style in defaultStyle) {
keyShape.attr(style, defaultStyle[style]); keyShape.attr(style, defaultStyle[style]);
} }

View File

@ -3,11 +3,7 @@
* @Date: 2019-06-27 18:12:06 * @Date: 2019-06-27 18:12:06
* @LastEditors: moyee * @LastEditors: moyee
* @LastEditTime: 2019-08-22 11:22:16 * @LastEditTime: 2019-08-22 11:22:16
* @Description: file content * @Description: Graph
*/
/**
* @fileOverview graph
* @author huangtonger@aliyun.com
*/ */
const { groupBy } = require('lodash'); const { groupBy } = require('lodash');
const G = require('@antv/g/lib'); const G = require('@antv/g/lib');
@ -200,7 +196,8 @@ class Graph extends EventEmitter {
/** /**
* 群组的原始数据 * 群组的原始数据
*/ */
groups: [] groups: [],
groupStyle: {}
}; };
} }

View File

@ -594,7 +594,7 @@ describe.only('signle layer group', () => {
expect(isVisible).to.be.true; expect(isVisible).to.be.true;
} }
expect(keyShape.attr('r')).eql(groupStyle.r); expect(keyShape.attr('r')).eql(30);
expect(keyShape.attr('x')).eql(groupStyle.x); expect(keyShape.attr('x')).eql(groupStyle.x);
expect(keyShape.attr('y')).eql(groupStyle.y); expect(keyShape.attr('y')).eql(groupStyle.y);