在每个节点上应用 onmouseover - GOjs 库 - 泳道

Posted

技术标签:

【中文标题】在每个节点上应用 onmouseover - GOjs 库 - 泳道【英文标题】:Apply onmouseover on each node - GOjs library - swimlane 【发布时间】:2014-05-05 19:43:56 【问题描述】:

我已经完成了first part,在每个节点上添加了颜色。下一步是如何在每个节点上添加 onmouseover,然后在属于悬停节点的子图(包括连接它的链接/线)上突出显示/应用样式。

有人知道吗?

这是我目前的代码:

        <?php
    $prospectus = array(
    'subjects' => array(
        'First Year' => array(
            array('course_no' => 'CS 110'       , 'color' => '#21ff1c'),
            array('course_no' => 'CS 111'       , 'color' => 'white'),
            array('course_no' => 'MATH 1'       , 'color' => 'white'),
            array('course_no' => 'MATH 2'       , 'color' => 'white'),
            array('course_no' => 'ENGL 1'       , 'color' => 'white'),
            array('course_no' => 'REED 1'       , 'color' => 'white'),
            array('course_no' => 'PE 1'         , 'color' => 'white'),
            array('course_no' => 'CWTS/ROTC 11' , 'color' => 'white'),
            array('course_no' => 'GUIDANCE 1'   , 'color' => 'white'),
            array('course_no' => 'CS 120'       , 'color' => 'white'),
            array('course_no' => 'CS 121'       , 'color' => 'white'),
            array('course_no' => 'CS 122'       , 'color' => 'white'),
            array('course_no' => 'MATH 4'       , 'color' => 'white'),
            array('course_no' => 'ENGL 2'       , 'color' => 'white'),
            array('course_no' => 'REED 2'       , 'color' => 'white'),
            array('course_no' => 'PE 2'         , 'color' => 'white'),
            array('course_no' => 'CWTS/ROTC 12' , 'color' => 'white'),
            array('course_no' => 'GUIDANCE 2'   , 'color' => 'white')
        ),
        'Second Year' => array(
            array('course_no' => 'CS 211'       , 'color' => 'white'),
            array('course_no' => 'CS 212'       , 'color' => 'white'),
            array('course_no' => 'CS 213'       , 'color' => 'white'),
            array('course_no' => 'ENGL 3'       , 'color' => 'white'),
            array('course_no' => 'MATH 6'       , 'color' => 'white'),
            array('course_no' => 'REED 3'       , 'color' => 'white'),
            array('course_no' => 'PE 3'         , 'color' => 'white'),
            array('course_no' => 'CS 221'       , 'color' => 'white'),
            array('course_no' => 'CS 222'       , 'color' => 'white'),
            array('course_no' => 'CS 223'       , 'color' => 'white'),
            array('course_no' => 'CS 224'       , 'color' => 'white'),
            array('course_no' => 'ENGL 4'       , 'color' => 'white'),
            array('course_no' => 'REED 4'       , 'color' => 'white'),
            array('course_no' => 'PE 4'         , 'color' => 'white'),
            array('course_no' => 'HUMANITIES 1' , 'color' => 'white'),
            array('course_no' => 'MATH 7'       , 'color' => 'white')
        )
    ),
    'preqs'    => array(
        array('subject' => 'CS 120'      , 'preq' => 'CS 110'),
        array('subject' => 'CS 121'      , 'preq' => 'CS 111'),
        array('subject' => 'CS 122'      , 'preq' => 'MATH 1'),
        array('subject' => 'MATH 4'      , 'preq' => 'MATH 1'),
        array('subject' => 'MATH 4'      , 'preq' => 'MATH 2'),
        array('subject' => 'ENGL 2'      , 'preq' => 'ENGL 1'),
        array('subject' => 'REED 2'      , 'preq' => 'REED 1'),
        array('subject' => 'PE 2'        , 'preq' => 'PE 1'  ),
        array('subject' => 'CWTS/ROTC 12', 'preq' => 'CWTS/ROTC 11'),
        array('subject' => 'GUIDANCE 2'  , 'preq' => 'GUIDANCE 1'),

        array('subject' => 'CS 211'        , 'preq' => 'CS 121'),
        array('subject' => 'CS 212'        , 'preq' => 'CS 110'),
        array('subject' => 'CS 213'        , 'preq' => 'CS 122'),
        array('subject' => 'ENGL 3'        , 'preq' => 'ENGL 2'),
        array('subject' => 'MATH 6'        , 'preq' => 'MATH 4'),
        array('subject' => 'REED 3'        , 'preq' => 'REED 2'),
        array('subject' => 'PE 3'          , 'preq' => 'PE 2'),

        array('subject' => 'CS 221'        , 'preq' => 'CS 122'),
        array('subject' => 'CS 221'        , 'preq' => 'CS 211'),
        array('subject' => 'CS 222'        , 'preq' => 'CS 211'),
        array('subject' => 'CS 223'        , 'preq' => 'CS 211'),
        array('subject' => 'CS 224'        , 'preq' => 'CS 121'),
        array('subject' => 'ENGL 4'        , 'preq' => 'ENGL 2'),
        array('subject' => 'REED 4'        , 'preq' => 'REED 3'),
        array('subject' => 'PE 4'          , 'preq' => 'PE 3'),
    )
);
?>

<!DOCTYPE html>
<html>
<head>
<title>Swimlane</title>
<!-- Copyright 1998-2014 by Northwoods Software Corporation. -->
<link href="goSamples.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="go.js"></script>
<script id="code">

function highlightNode(node, color) 
    if (node === null) return;
    var shape = node.findMainElement();
    if (shape === null) return;
    if (color !== undefined) 
        if (!shape.previousStroke) shape.previousStroke = shape.stroke;
        shape.stroke = color;
     else   // restore previous color
        shape.stroke = shape.previousStroke;
    


function highlightLink(link, color) 
    if (link === null) return;
    var shape = link.findMainElement();
    if (shape === null) return;
    if (color !== undefined) 
        if (!shape.previousStroke) shape.previousStroke = shape.stroke;
        shape.stroke = color;
     else   // restore previous color
        shape.stroke = shape.previousStroke;
    


function highlightConnectedNodes(node, color) 
    if (node === null) return;

    var lit = node.findLinksOutOf();

    while (lit.next()) 
        highlightLink(lit.value, color);
    

    var nit = node.findNodesOutOf();

    while (nit.next()) 
        highlightNode(nit.value, color);
        highlightConnectedNodes(nit.value, color);
    


// These parameters need to be set before defining the templates.
// this controls whether the swimlanes are horizontal stacked vertically, or the other way:
var HORIZONTAL = false;
// this controls the minimum length of any swimlane
var MINLENGTH = 500;
// this controls the minimum breadth of any swimlane
var MINBREADTH = 360;


// compute the minimum length needed to hold all of the subgraphs
function computeMinPlaceholderSize(diagram) 

    var len = MINLENGTH;

    for (var it = diagram.nodes; it.next(); ) 
        var group = it.value;

        if (!(group instanceof go.Group))
            continue;

        var holder = group.placeholder;

        if (holder !== null) 
            var sz = holder.actualBounds;
            len = Math.max(len, (HORIZONTAL ? sz.width : sz.height));
        
    

    return (HORIZONTAL ? new go.Size(len, NaN) : new go.Size(NaN, len));


// get the minimum placeholder size for a particular Group;
// when group is null, return the minimum size
function computePlaceholderSize(group) 

    if (group instanceof go.Group) 
        var holder = group.placeholder;

        if (holder !== null) 
            return holder.actualBounds.size;
        
    

    return (HORIZONTAL ? new go.Size(MINLENGTH, MINBREADTH) : new go.Size(MINBREADTH, MINLENGTH));


// define a custom grid layout that makes sure the length of each lane is the same
// and that each lane is broad enough to hold its subgraph
function StackLayout() 
    go.GridLayout.call(this);

go.Diagram.inherit(StackLayout, go.GridLayout);

StackLayout.prototype.doLayout = function(coll) 
    var diagram = this.diagram;

    if (diagram === null)
        return;

    diagram.startTransaction("StackLayout");

    // make sure all of the Group Shapes are big enough
    var minsize = computeMinPlaceholderSize(diagram);

    for (var it = diagram.nodes; it.next(); ) 
        var group = it.value;

        if (!(group instanceof go.Group))
            continue;

        var shape = group.findObject("SHAPE");

        if (shape !== null)   // change the desiredSize to be big enough in both directions
            var sz = computePlaceholderSize(group);

            if (HORIZONTAL) 
                shape.width = (isNaN(shape.width) ? minsize.width : Math.max(shape.width, minsize.width));

                if (!isNaN(shape.height))
                    shape.height = Math.max(shape.height, sz.height);
             else 
                if (!isNaN(shape.width))
                    shape.width = Math.max(shape.width, sz.width);
                shape.height = (isNaN(shape.height) ? minsize.height : Math.max(shape.height, minsize.height));
            

            var cell = group.resizeCellSize;

            if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0)
                shape.width = Math.ceil(shape.width / cell.width) * cell.width;
            if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0)
                shape.height = Math.ceil(shape.height / cell.height) * cell.height;
        
    

    // now do all of the usual stuff, according to whatever properties have been set on this GridLayout
    go.GridLayout.prototype.doLayout.call(this, coll);
    diagram.commitTransaction("StackLayout");
;
// end StackLayout class

function init() 

    var $ = go.GraphObject.make;
    myDiagram =
        $(go.Diagram, "myDiagram",
            
                initialContentAlignment: go.Spot.Center,
                layout:
                    $(StackLayout,
                        
                            cellSize: new go.Size(1, 1),
                            spacing: new go.Size(0, 0),
                            wrappingColumn: (HORIZONTAL ? 1 : Infinity),
                            wrappingWidth: Infinity,
                            isViewportSized: false
                        )
            );

    myDiagram.nodeTemplate =
        $(go.Node, "Auto",
            $(go.Shape, "Rectangle",
                 fill: "lightblue", portId: "", cursor: "pointer", fromLinkable: true, toLinkable: true ,
                new go.Binding("fill", "color")),
            $(go.TextBlock,  margin: 5 ,
                new go.Binding("text", "key")),
            // limit dragging of Nodes to stay within the containing Group, defined above
            
                mouseEnter: function(e, obj, prev) 
                    var shape = obj.findMainElement();
                    if (shape)
                        shape.previousFill = shape.fill || "lightblue";
                        shape.fill = "white";
                    
                    highlightConnectedNodes(obj, "red");
                ,
                mouseLeave: function(e, obj, next) 
                    var shape = obj.findMainElement();
                    var original_color = obj.wl.color;

                    if (shape) shape.fill = shape.previousFill;

                    highlightConnectedNodes(obj);
                

            
        );

    // each Group is a "swimlane" with a header on the left and a resizable lane on the right
    myDiagram.groupTemplate =
        $(go.Group, HORIZONTAL ? "Horizontal" : "Vertical",
            
                movable: false, copyable: false, deletable: false,  // can't move or copy or delete lanes
                avoidable: false,
                selectionObjectName: "SHAPE",  // selecting a lane causes the body of the lane to be highlit, not the label
                resizable: true, resizeObjectName: "SHAPE",  // allow lanes to be resized, but the custom resizeAdornmentTemplate only permits one kind of resizing
                layout: $(go.LayeredDigraphLayout,  // automatically lay out the lane's subgraph
                     direction: HORIZONTAL ? 90 : 0, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource ),
                computesBoundsAfterDrag: false,  // needed to prevent recomputing Group.placeholder bounds too soon
                computesBoundsIncludingLinks: false,
                computesBoundsIncludingLocation: true
            ,

            // the lane header consisting of a Shape and a TextBlock
            $(go.Panel, "Horizontal",
                
                    angle: HORIZONTAL ? 270 : 0,  // maybe rotate the header to read sideways going up
                    alignment: go.Spot.Center
                ,

                $(go.Shape, "Diamond",
                     width: 0, height: 0 ,
                    new go.Binding("fill", "color")),

                $(go.TextBlock,  // the lane label
                     font: "bold 14pt Century Gothic" ,
                    new go.Binding("text", "key"))
            ),
            // end Horizontal Panel

            $(go.Panel, "Auto",  // the lane consisting of a background Shape and a Placeholder representing the subgraph
                $(go.Shape, "Rectangle",
                     name: "SHAPE", fill: "white", minSize: computePlaceholderSize(null) ,
                    new go.Binding("fill", "color")),
                $(go.Placeholder,
                     padding: 10, alignment: go.Spot.TopLeft )
            )
            // end Auto Panel
        );
    // end Group

    // define a custom resize adornment that only has a single resize handle
    myDiagram.groupTemplate.resizeAdornmentTemplate =
        $(go.Adornment, "Spot",
            $(go.Placeholder),
            $(go.Shape,  // for changing the length of a lane
                
                    alignment: HORIZONTAL ? go.Spot.Right: go.Spot.Bottom,
                    desiredSize: HORIZONTAL ? new go.Size(7, 50) : new go.Size(50, 7),
                    fill: "lightblue", stroke: "dodgerblue",
                    cursor: HORIZONTAL ? "col-resize" : "row-resize"
                ),
            $(go.Shape,  // for changing the breadth of a lane
                
                    alignment: HORIZONTAL ? go.Spot.Bottom : go.Spot.Right,
                    desiredSize: HORIZONTAL ? new go.Size(50, 7) : new go.Size(7, 50),
                    fill: "lightblue", stroke: "dodgerblue",
                    cursor: HORIZONTAL ? "row-resize" : "col-resize"
                )
        );

    myDiagram.linkTemplate =
        $(go.Link,
             routing: go.Link.AvoidsNodes, corner: 5 ,
             relinkableFrom: true, relinkableTo: true ,
            $(go.Shape),
            $(go.Shape,  toArrow: "Standard" ),
            
                layoutConditions: go.Part.LayoutAdded
            
        );

    // define some sample graphs in some of the lanes
    myDiagram.model = new go.GraphLinksModel(
        [ // node data
             key: "First Year"   , isGroup: true, color: "#84ff9e" ,
             key: "Second Year"  , isGroup: true, color: "#dafb69" ,
             key: "Third Year"   , isGroup: true, color: "#fd5c91" ,
             key: "Fourth Year"  , isGroup: true, color: "#6f5cfd" ,

            <?php
                if(isset($prospectus['subjects']))
                    if(!empty($prospectus['subjects']))
                        foreach($prospectus['subjects'] as $year => $subjects)
                            foreach($subjects as $subject)
                                echo 'key: "' . $subject['course_no'] . '", group: "' . $year . '", color: "' . $subject['color'] . '",' ;
                            
                        
                    
                
            ?>
        ],
        [ // link data
            <?php
                if(isset($prospectus['preqs']))
                    if(!empty($prospectus['preqs']))
                        foreach($prospectus['preqs'] as $preq)
                            echo "from: '" . $preq['preq'] . "', to: '" . $preq['subject'] . "'," ;
                        
                    
                
            ?>
        ]);

    myDiagram.isReadOnly = true;

</script>
</head>
<body onload="init()" style="font-family: Century Gothic">
<div id="sample" style="margin-left: 0px">
    <div id="myDiagram" style="border: solid 1px blue; width:100%; height:750px;">
    </div>
</div>
</body>
</html>

【问题讨论】:

【参考方案1】:

这里是包含鼠标进入和离开功能的节点模板部分:

     myDiagram.nodeTemplate =
      $(go.Node, "Auto",
        $(go.Shape, "Rectangle",
           fill: "lightblue", portId: "", cursor: "pointer", fromLinkable: true, toLinkable: true ,
          new go.Binding("fill", "color")),
        $(go.TextBlock,  margin: 5 ,
          new go.Binding("text", "key")),
        // limit dragging of Nodes to stay within the containing Group, defined above
        
          dragComputation: stayInGroup,
          mouseDrop: function (e, node)   // dropping a copy of some Nodes and Links onto this Node adds them to this Node's Group
            if (!e.shift && !e.control) return;  // cannot change groups with an unmodified drag-and-drop
            var grp = node.containingGroup;
            if (grp !== null) 
              var ok = grp.addMembers(node.diagram.selection, true);
              if (!ok) grp.diagram.currentTool.doCancel();
            
          ,
          layoutConditions: go.Part.LayoutAdded | go.Part.LayoutNodeSized,

            mouseEnter: function(e, obj, prev) 
                var shape = obj.findMainElement();
                if (shape)
                    shape.previousFill = shape.fill || "lightblue";
                    shape.fill = "white";
                
                highlightConnectedNodes(obj, "red");
            ,
            mouseLeave: function(e, obj, next) 
                var shape = obj.findMainElement();
                var original_color = obj.wl.color;

                if (shape) shape.fill = shape.previousFill;

                highlightConnectedNodes(obj);
            

        
      );

这两个函数都调用 highlightConnectedNodes 函数,该函数又调用另外两个函数。一个用于节点,一个用于链接。

   function highlightNode(node, color) 
     if (node === null) return;
     var shape = node.findMainElement();
     if (shape === null) return;
     if (color !== undefined) 
       if (!shape.previousStroke) shape.previousStroke = shape.stroke;
       shape.stroke = color;
      else   // restore previous color
       shape.stroke = shape.previousStroke;
     
   

   function highlightLink(link, color) 
     if (link === null) return;
     var shape = link.findMainElement();
     if (shape === null) return;
     if (color !== undefined) 
       if (!shape.previousStroke) shape.previousStroke = shape.stroke;
       shape.stroke = color;
      else   // restore previous color
       shape.stroke = shape.previousStroke;
     
   

   function highlightConnectedNodes(node, color) 
     if (node === null) return;

     var lit = node.findLinksInto();

     while (lit.next())            
       highlightLink(lit.value, color);
     

     var nit = node.findNodesInto();

     while (nit.next())    
       highlightNode(nit.value, color);
       highlightConnectedNodes(nit.value, color);
     
   

当您将鼠标悬停在某个节点上时,与其相连的所有节点和链接都会改变颜色(向后移动)。但是,如果您只希望悬停节点前面的节点和链接更改颜色,则可以更改

findNodesInto
findLinksInto

findNodesOutOf 
findLinksOutOf 

希望对您有所帮助。

【讨论】:

非常感谢@James 先生!我希望你很快还在外面,因为对于我们的最终项目,我仍然有很多与这个库有关的问题。 我只需要在这里发布这个作为后续问题。 先生,你能用你的代码绑定this吗?我已经实现了上面的代码并且运行良好(我喜欢它,谢谢!我只需要加厚链接,更改颜色等)。我真正想要添加的功能是当a节点悬停时,它还会显示其详细信息。 P.S.先生是否可以保留悬停节点的颜色?因为每当我将鼠标悬停在非白色节点上时,它就会在悬停时变为白色。【参考方案2】:

如果您想在将鼠标悬停在节点上时突出显示节点,则很可能您真的想使用mouseEntermouseLeave。这是一个简单的例子:

  var $ = go.GraphObject.make; // for conciseness in defining templates

  var diagram = $(go.Diagram, "myDiagram", // create a Diagram for the DIV HTML element
     initialContentAlignment: go.Spot.Center  ); // center the content

  diagram.initialContentAlignment = go.Spot.Center;

  function mouseEnter(e, obj) 
    var shape = obj.findObject('SHAPE');
    shape.fill = "#6DAB80";
    shape.stroke = "#A6E6A1";
    var text = obj.findObject('TEXT');
    text.stroke = "white";
  ;

  function mouseLeave(e, obj) 
    var shape = obj.findObject('SHAPE');
    // Return the Shape's fill and stroke to the defaults
    shape.fill = obj.data.color;
    shape.stroke = null;
    // Return the TextBlock's stroke to its default
    var text = obj.findObject('TEXT');
    text.stroke = "black";
  ;

  diagram.nodeTemplate =
    $(go.Node, "Auto",
      
        mouseEnter: mouseEnter,
        mouseLeave: mouseLeave
      ,
      $(go.Shape, "Rectangle",
         strokeWidth: 2, stroke: null, name: 'SHAPE' ,
        new go.Binding("fill", "color")),
      $(go.TextBlock,
         margin: 10, font: "bold 18px Verdana", name: 'TEXT' ,
        new go.Binding("text", "key"))
    );

  diagram.model = new go.GraphLinksModel(
  [
     key: "Alpha", color: "#96D6D9" ,
     key: "Beta",  color: "#96D6D9" ,
     key: "Gamma", color: "#EFEBCA" ,
     key: "Delta", color: "#EFEBCA" 
  ],
  [
     from: "Alpha", to: "Beta" ,
     from: "Alpha", to: "Gamma" ,
     from: "Beta", to: "Beta" ,
     from: "Gamma", to: "Delta" ,
     from: "Delta", to: "Alpha" 
  ]);

现场示例:http://jsfiddle.net/Y5sMN/

【讨论】:

谢谢先生,我知道这段代码会派上用场,我只需要用我的你来实现它。嗯……先生,可以像this这样吗?它表示显示的详细信息是有关悬停节点的信息。我还没有传递信息(来自控制器,因为我正在使用 CI 框架),所以如果你创建一个你的虚拟对象就可以了。 谢谢先生,但如果您能帮我用我的程序实现它,我将不胜感激 =)。先生? 先生,我在处理数据可视化代码时遇到了问题。因为据我所知,模型是通过细节本身创建的。我想要的是,除了我拥有的模型之外,我还想为细节创建另一个实体。 T_T 也许你有什么想法?

以上是关于在每个节点上应用 onmouseover - GOjs 库 - 泳道的主要内容,如果未能解决你的问题,请参考以下文章

跟着动画学 Go 数据结构之二叉树

BigQuery 流式插入在 GKE 上失败

Go数据结构与算法—链表

Go数据结构与算法—链表

Go 语言学习数据结构:双链表

200行Go代码实现自己的区块链——区块生成与网络通信