Sfoglia il codice sorgente

fix: drag group node

zhanning.bzn 6 anni fa
parent
commit
862357583c

+ 194 - 0
demos/area-chart-node.html

@@ -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 - 0
demos/bar-chart-node.html

@@ -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 - 0
demos/chart-node.html

@@ -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的x,y必须是相对于画布的坐标
+      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>

+ 27 - 0
demos/collapse-expand-group.html

@@ -40,6 +40,33 @@
      },
      modes: {
        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'
+      }
      }
    });
  

+ 587 - 0
demos/custom-tree-interactive.html

@@ -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>

+ 0 - 1
demos/front-edge.html

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

+ 194 - 0
demos/line-chart-node.html

@@ -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 - 0
demos/point-chart-node.html

@@ -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>

+ 146 - 0
demos/stacked-bar-chart-node.html

@@ -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>

+ 1 - 1
package.json

@@ -98,7 +98,7 @@
     "screenshot": "node ./bin/screenshot.js",
     "start": "npm run dev",
     "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-bugs": "torch --compile --renderer --recursive ./test/bugs",
     "test-bugs-live": "torch --compile --interactive --watch --recursive ./test/bugs",

+ 3 - 3
src/behavior/drag-group.js

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

+ 31 - 20
src/behavior/drag-node-with-group.js

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

+ 3 - 2
src/behavior/drag-node.js

@@ -5,7 +5,8 @@
  * @LastEditTime: 2019-08-22 18:41:45
  * @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 body = document.body;
 
@@ -174,7 +175,7 @@ module.exports = {
     if (!this.shape) {
       // 拖动多个
       const parent = this.graph.get('group');
-      const attrs = merge({}, delegateStyle, this.delegateStyle);
+      const attrs = deepMix({}, delegateStyle, this.delegateStyle);
       if (this.targets.length > 0) {
         const { x, y, width, height, minX, minY } = this.calculationGroupPosition();
         this.originPoint = { x, y, width, height, minX, minY };

+ 10 - 9
src/graph/controller/customGroup.js

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

+ 3 - 6
src/graph/graph.js

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

+ 1 - 1
test/unit/graph/controller/custom-group-spec.js

@@ -594,7 +594,7 @@ describe.only('signle layer group', () => {
         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('y')).eql(groupStyle.y);