{"action":"created","comment":{"url":"https://api.github.com/repos/oppia/oppia/pulls/comments/48073202","id":48073202,"diff_hunk":"@@ -187,362 +191,468 @@ oppia.directive('graphViz', function() {\n       canEditOptions: '=',\n     },\n     templateUrl: 'graphViz/graphVizSvg',\n-    controller: ['$scope', '$element', '$attrs', '$document', 'focusService', 'graphDetailService', 'GRAPH_INPUT_LEFT_MARGIN',\n-    function($scope, $element, $attrs, $document, focusService, graphDetailService, GRAPH_INPUT_LEFT_MARGIN) {\n-      var _MODES = {\n-        MOVE: 0,\n-        ADD_EDGE: 1,\n-        ADD_VERTEX: 2,\n-        DELETE: 3\n-      };\n-\n-      // The current state of the UI and stuff like that\n-      $scope.state = {\n-        currentMode: _MODES.MOVE,\n-        // Vertex, edge, mode button, label currently being hovered over\n-        hoveredVertex: null,\n-        hoveredEdge: null,\n-        hoveredModeButton: null,\n-        // If in ADD_EDGE mode, source vertex of the new edge, if it exists\n-        addEdgeVertex: null,\n-        // Currently dragged vertex\n-        currentlyDraggedVertex: null,\n-        // Selected vertex for editing label\n-        selectedVertex: null,\n-        // Selected edge for editing weight\n-        selectedEdge: null,\n-        // Mouse position in SVG coordinates\n-        mouseX: 0,\n-        mouseY: 0,\n-        // Original position of dragged vertex\n-        vertexDragStartX: 0,\n-        vertexDragStartY: 0,\n-        // Original position of mouse when dragging started\n-        mouseDragStartX: 0,\n-        mouseDragStartY: 0,\n-      };\n+    controller: ['$scope', '$element', '$attrs', '$document', 'focusService',\n+                 'graphDetailService', 'GRAPH_INPUT_LEFT_MARGIN',\n+      function($scope, $element, $attrs, $document, focusService,\n+          graphDetailService, GRAPH_INPUT_LEFT_MARGIN) {\n+        var _MODES = {\n+          MOVE: 0,\n+          ADD_EDGE: 1,\n+          ADD_VERTEX: 2,\n+          DELETE: 3\n+        };\n \n-      $scope.VERTEX_RADIUS = graphDetailService.VERTEX_RADIUS;\n-      $scope.EDGE_WIDTH = graphDetailService.EDGE_WIDTH;\n-\n-      var vizContainer = $($element).find('.oppia-graph-viz-svg');\n-      $scope.vizWidth = vizContainer.width();\n-      $scope.mousemoveGraphSVG = function(event) {\n-        $scope.state.mouseX = event.pageX - vizContainer.offset().left;\n-        $scope.state.mouseY = event.pageY - vizContainer.offset().top;\n-        // vertexDragStartX/Y and mouseDragStartX/Y are to make mouse-dragging\n-        // by label more natural, by moving the vertex according to the difference\n-        // from the original position. Otherwise, mouse-dragging by label will make\n-        // the vertex awkwardly jump to the mouse.\n-        if ($scope.state.currentlyDraggedVertex !== null && ($scope.state.mouseX > GRAPH_INPUT_LEFT_MARGIN)) {\n-          $scope.graph.vertices[$scope.state.currentlyDraggedVertex].x = $scope.state.vertexDragStartX + ($scope.state.mouseX - $scope.state.mouseDragStartX);\n-          $scope.graph.vertices[$scope.state.currentlyDraggedVertex].y = $scope.state.vertexDragStartY + ($scope.state.mouseY - $scope.state.mouseDragStartY);\n-        }\n-      };\n+        // The current state of the UI and stuff like that\n+        $scope.state = {\n+          currentMode: _MODES.MOVE,\n+          // Vertex, edge, mode button, label currently being hovered over\n+          hoveredVertex: null,\n+          hoveredEdge: null,\n+          hoveredModeButton: null,\n+          // If in ADD_EDGE mode, source vertex of the new edge, if it exists\n+          addEdgeVertex: null,\n+          // Currently dragged vertex\n+          currentlyDraggedVertex: null,\n+          // Selected vertex for editing label\n+          selectedVertex: null,\n+          // Selected edge for editing weight\n+          selectedEdge: null,\n+          // Mouse position in SVG coordinates\n+          mouseX: 0,\n+          mouseY: 0,\n+          // Original position of dragged vertex\n+          vertexDragStartX: 0,\n+          vertexDragStartY: 0,\n+          // Original position of mouse when dragging started\n+          mouseDragStartX: 0,\n+          mouseDragStartY: 0,\n+        };\n \n-      $scope.onClickGraphSVG = function(event) {\n-        if ($scope.state.currentMode === _MODES.ADD_VERTEX && $scope.canAddVertex) {\n-          $scope.graph.vertices.push({\n-            x: $scope.state.mouseX,\n-            y: $scope.state.mouseY,\n-            label: ''\n-          });\n-        }\n-        if ($scope.state.hoveredVertex === null) {\n-          $scope.state.selectedVertex = null;\n-        }\n-        if ($scope.state.hoveredEdge === null) {\n-          $scope.state.selectedEdge = null;\n-        }\n-      };\n+        $scope.VERTEX_RADIUS = graphDetailService.VERTEX_RADIUS;\n+        $scope.EDGE_WIDTH = graphDetailService.EDGE_WIDTH;\n \n-      $scope.init = function() {\n-        initButtons();\n-        $scope.state.currentMode = $scope.buttons[0].mode;\n-      };\n-      $scope.init();\n-\n-      function initButtons() {\n-        $scope.buttons = [];\n-        if ($scope.canMoveVertex) {\n-          $scope.buttons.push({\n-            text: '\\uE068',\n-            description: 'Move',\n-            mode: _MODES.MOVE\n-          });\n-        }\n-        if ($scope.canAddEdge) {\n-          $scope.buttons.push({\n-            text: '\\uE144',\n-            description: 'Add Edge',\n-            mode: _MODES.ADD_EDGE\n-          });\n-        }\n-        if ($scope.canAddVertex) {\n-          $scope.buttons.push({\n-            text: '\\u002B',\n-            description: 'Add Node',\n-            mode: _MODES.ADD_VERTEX\n-          });\n-        }\n-        if ($scope.canDeleteVertex || $scope.canDeleteEdge) {\n-          $scope.buttons.push({\n-            text: '\\u2212',\n-            description: 'Delete',\n-            mode: _MODES.DELETE\n-          });\n-        }\n-      }\n+        var vizContainer = $($element).find('.oppia-graph-viz-svg');\n+        $scope.vizWidth = vizContainer.width();\n+        $scope.mousemoveGraphSVG = function(event) {\n+          $scope.state.mouseX = event.pageX - vizContainer.offset().left;\n+          $scope.state.mouseY = event.pageY - vizContainer.offset().top;\n+          // vertexDragStartX/Y and mouseDragStartX/Y are to make mouse-dragging\n+          // by label more natural, by moving the vertex according to the\n+          // difference from the original position. Otherwise, mouse-dragging by\n+          // label will make the vertex awkwardly jump to the mouse.\n+          if ($scope.state.currentlyDraggedVertex !== null &&\n+              ($scope.state.mouseX > GRAPH_INPUT_LEFT_MARGIN)) {\n+            $scope.graph.vertices[$scope.state.currentlyDraggedVertex].x =\n+              $scope.state.vertexDragStartX + ($scope.state.mouseX - $scope.state.mouseDragStartX);\n+            $scope.graph.vertices[$scope.state.currentlyDraggedVertex].y =\n+              $scope.state.vertexDragStartY + ($scope.state.mouseY - $scope.state.mouseDragStartY);\n+          }\n+        };\n \n-      $scope.graphOptions = [{\n-        text: 'Labeled',\n-        option: 'isLabeled'\n-      },\n-      {\n-        text: 'Directed',\n-        option: 'isDirected'\n-      },\n-      {\n-        text: 'Weighted',\n-        option: 'isWeighted'\n-      }];\n-      $scope.toggleGraphOption = function(option) {\n-        // Handle the case when we have two edges s -> d and d -> s\n-        if (option === 'isDirected' && $scope.graph[option]) {\n-          _deleteRepeatedUndirectedEdges();\n-        }\n-        $scope.graph[option] = !$scope.graph[option];\n-      };\n-      $scope.setMode = function(mode, $event) {\n-        // Prevents new vertex from being added in add edge mode\n-        $event.preventDefault();\n-        $event.stopPropagation();\n-        $scope.state.currentMode = mode;\n-        $scope.state.addEdgeVertex = null;\n-        $scope.state.selectedVertex = null;\n-        $scope.state.selectedEdge = null;\n-      };\n+        $scope.onClickGraphSVG = function(event) {\n+          if ($scope.state.currentMode === _MODES.ADD_VERTEX && $scope.canAddVertex) {\n+            $scope.graph.vertices.push({\n+              x: $scope.state.mouseX,\n+              y: $scope.state.mouseY,\n+              label: ''\n+            });\n+          }\n+          if ($scope.state.hoveredVertex === null) {\n+            $scope.state.selectedVertex = null;\n+          }\n+          if ($scope.state.hoveredEdge === null) {\n+            $scope.state.selectedEdge = null;\n+          }\n+        };\n \n-      // TODO(czx): Consider if there's a neat way to write a reset()\n-      // function to clear bits of $scope.state (e.g. currentlyDraggedVertex, addEdgeVertex)\n+        $scope.init = function() {\n+          initButtons();\n+          $scope.state.currentMode = $scope.buttons[0].mode;\n+        };\n+        $scope.init();\n \n-      // Vertex events\n-      $scope.onClickVertex = function(index) {\n-        if ($scope.state.currentMode === _MODES.DELETE) {\n-          if ($scope.canDeleteVertex) {\n-            deleteVertex(index);\n+        function initButtons() {\n+          $scope.buttons = [];\n+          if ($scope.canMoveVertex) {\n+            $scope.buttons.push({\n+              text: '\\uE068',\n+              description: 'Move',\n+              mode: _MODES.MOVE\n+            });\n           }\n-        }\n-        if ($scope.state.currentMode !== _MODES.DELETE &&\n-            $scope.graph.isLabeled &&\n-            $scope.canEditVertexLabel) {\n-          beginEditVertexLabel(index);\n-        }\n-      };\n-      $scope.onMousedownVertex = function(index) {\n-        if ($scope.state.currentMode === _MODES.ADD_EDGE) {\n           if ($scope.canAddEdge) {\n-            beginAddEdge(index);\n+            $scope.buttons.push({\n+              text: '\\uE144',\n+              description: 'Add Edge',\n+              mode: _MODES.ADD_EDGE\n+            });\n           }\n-        } else if ($scope.state.currentMode === _MODES.MOVE) {\n-          if ($scope.canMoveVertex) {\n-            beginDragVertex(index);\n+          if ($scope.canAddVertex) {\n+            $scope.buttons.push({\n+              text: '\\u002B',\n+              description: 'Add Node',\n+              mode: _MODES.ADD_VERTEX\n+            });\n+          }\n+          if ($scope.canDeleteVertex || $scope.canDeleteEdge) {\n+            $scope.buttons.push({\n+              text: '\\u2212',\n+              description: 'Delete',\n+              mode: _MODES.DELETE\n+            });\n           }\n         }\n-      };\n \n-      $scope.onClickVertexLabel = function(index) {\n-        if ($scope.graph.isLabeled && $scope.canEditVertexLabel) {\n-          beginEditVertexLabel(index);\n-        }\n-      };\n+        $scope.graphOptions = [{\n+          text: 'Labeled',\n+          option: 'isLabeled'\n+        },\n+        {\n+          text: 'Directed',\n+          option: 'isDirected'\n+        },\n+        {\n+          text: 'Weighted',\n+          option: 'isWeighted'\n+        }];\n+        $scope.toggleGraphOption = function(option) {\n+          // Handle the case when we have two edges s -> d and d -> s\n+          if (option === 'isDirected' && $scope.graph[option]) {\n+            _deleteRepeatedUndirectedEdges();\n+          }\n+          $scope.graph[option] = !$scope.graph[option];\n+        };\n+        $scope.setMode = function(mode, $event) {\n+          // Prevents new vertex from being added in add edge mode\n+          $event.preventDefault();\n+          $event.stopPropagation();\n+          $scope.state.currentMode = mode;\n+          $scope.state.addEdgeVertex = null;\n+          $scope.state.selectedVertex = null;\n+          $scope.state.selectedEdge = null;\n+        };\n \n-      // Edge events\n-      $scope.onClickEdge = function(index) {\n-        if ($scope.state.currentMode === _MODES.DELETE) {\n-          if ($scope.canDeleteEdge) {\n-            deleteEdge(index);\n+        // TODO(czx): Consider if there's a neat way to write a reset()\n+        // function to clear bits of $scope.state\n+        // (e.g. currentlyDraggedVertex, addEdgeVertex)\n+\n+        // Vertex events\n+        $scope.onClickVertex = function(index) {\n+          if ($scope.state.currentMode === _MODES.DELETE) {\n+            if ($scope.canDeleteVertex) {\n+              deleteVertex(index);\n+            }\n           }\n-        }\n-        if ($scope.state.currentMode !== _MODES.DELETE &&\n-            $scope.graph.isWeighted &&\n-            $scope.canEditEdgeWeight) {\n-          beginEditEdgeWeight(index);\n-        }\n-      };\n-      $scope.onClickEdgeWeight = function(index) {\n-        if ($scope.graph.isWeighted && $scope.canEditEdgeWeight) {\n-          beginEditEdgeWeight(index);\n-        }\n-      };\n+          if ($scope.state.currentMode !== _MODES.DELETE &&\n+              $scope.graph.isLabeled &&\n+              $scope.canEditVertexLabel) {\n+            beginEditVertexLabel(index);\n+          }\n+        };\n+        $scope.onMousedownVertex = function(index) {\n+          if ($scope.state.currentMode === _MODES.ADD_EDGE) {\n+            if ($scope.canAddEdge) {\n+              beginAddEdge(index);\n+            }\n+          } else if ($scope.state.currentMode === _MODES.MOVE) {\n+            if ($scope.canMoveVertex) {\n+              beginDragVertex(index);\n+            }\n+          }\n+        };\n \n-      // Document event\n-      $scope.onMouseupDocument = function() {\n-        if ($scope.state.currentMode === _MODES.ADD_EDGE) {\n-          if ($scope.state.hoveredVertex !== null) {\n-            tryAddEdge($scope.state.addEdgeVertex, $scope.state.hoveredVertex);\n+        $scope.onClickVertexLabel = function(index) {\n+          if ($scope.graph.isLabeled && $scope.canEditVertexLabel) {\n+            beginEditVertexLabel(index);\n           }\n-          endAddEdge();\n-        } else if ($scope.state.currentMode === _MODES.MOVE) {\n-          if ($scope.state.currentlyDraggedVertex !== null) {\n-            endDragVertex();\n+        };\n+\n+        // Edge events\n+        $scope.onClickEdge = function(index) {\n+          if ($scope.state.currentMode === _MODES.DELETE) {\n+            if ($scope.canDeleteEdge) {\n+              deleteEdge(index);\n+            }\n           }\n-        }\n-      };\n-      $document.on(\"mouseup\", $scope.onMouseupDocument);\n+          if ($scope.state.currentMode !== _MODES.DELETE &&\n+              $scope.graph.isWeighted &&\n+              $scope.canEditEdgeWeight) {\n+            beginEditEdgeWeight(index);\n+          }\n+        };\n+        $scope.onClickEdgeWeight = function(index) {\n+          if ($scope.graph.isWeighted && $scope.canEditEdgeWeight) {\n+            beginEditEdgeWeight(index);\n+          }\n+        };\n \n-      // Actions\n-      function beginAddEdge(startIndex) {\n-        $scope.state.addEdgeVertex = startIndex;\n-      }\n-      function endAddEdge() {\n-        $scope.state.addEdgeVertex = null;\n-      }\n-      function tryAddEdge(startIndex, endIndex) {\n-        if (\n-            startIndex === null ||\n-            endIndex === null ||\n-            startIndex === endIndex ||\n-            startIndex < 0 ||\n-            endIndex < 0 ||\n-            startIndex >= $scope.graph.vertices.length ||\n-            endIndex >= $scope.graph.vertices.length) {\n-          return;\n+        // Document event\n+        $scope.onMouseupDocument = function() {\n+          if ($scope.state.currentMode === _MODES.ADD_EDGE) {\n+            if ($scope.state.hoveredVertex !== null) {\n+              tryAddEdge(\n+                $scope.state.addEdgeVertex, $scope.state.hoveredVertex);\n+            }\n+            endAddEdge();\n+          } else if ($scope.state.currentMode === _MODES.MOVE) {\n+            if ($scope.state.currentlyDraggedVertex !== null) {\n+              endDragVertex();\n+            }\n+          }\n+        };\n+        $document.on('mouseup', $scope.onMouseupDocument);\n+\n+        // Actions\n+        function beginAddEdge(startIndex) {\n+          $scope.state.addEdgeVertex = startIndex;\n+        }\n+        function endAddEdge() {\n+          $scope.state.addEdgeVertex = null;\n         }\n-        for (var i = 0; i < $scope.graph.edges.length; i++) {\n-          if (startIndex === $scope.graph.edges[i].src && endIndex === $scope.graph.edges[i].dst) {\n+        function tryAddEdge(startIndex, endIndex) {\n+          if (\n+              startIndex === null ||\n+              endIndex === null ||\n+              startIndex === endIndex ||\n+              startIndex < 0 ||\n+              endIndex < 0 ||\n+              startIndex >= $scope.graph.vertices.length ||\n+              endIndex >= $scope.graph.vertices.length) {\n             return;\n           }\n-          if (!$scope.graph.isDirected) {\n-            if (startIndex === $scope.graph.edges[i].dst && endIndex === $scope.graph.edges[i].src) {\n+          for (var i = 0; i < $scope.graph.edges.length; i++) {\n+            if (startIndex === $scope.graph.edges[i].src &&\n+                endIndex === $scope.graph.edges[i].dst) {\n               return;\n             }\n+            if (!$scope.graph.isDirected) {\n+              if (startIndex === $scope.graph.edges[i].dst &&\n+                  endIndex === $scope.graph.edges[i].src) {\n+                return;\n+              }\n+            }\n           }\n+          $scope.graph.edges.push({\n+            src: startIndex,\n+            dst: endIndex,\n+            weight: 1\n+          });\n+          return;\n+        }\n+        function beginDragVertex(index) {\n+          $scope.state.currentlyDraggedVertex = index;\n+          $scope.state.vertexDragStartX = $scope.graph.vertices[index].x;\n+          $scope.state.vertexDragStartY = $scope.graph.vertices[index].y;\n+          $scope.state.mouseDragStartX = $scope.state.mouseX;\n+          $scope.state.mouseDragStartY = $scope.state.mouseY;\n+        }\n+        function endDragVertex() {\n+          $scope.state.currentlyDraggedVertex = null;\n+          $scope.state.vertexDragStartX = 0;\n+          $scope.state.vertexDragStartY = 0;\n+          $scope.state.mouseDragStartX = 0;\n+          $scope.state.mouseDragStartY = 0;\n+        }\n+        function beginEditVertexLabel(index) {\n+          $scope.state.selectedVertex = index;\n+          focusService.setFocus('vertexLabelEditBegun');\n         }\n-        $scope.graph.edges.push({\n-          src: startIndex,\n-          dst: endIndex,\n-          weight: 1\n-        });\n-        return;\n-      }\n-      function beginDragVertex(index) {\n-        $scope.state.currentlyDraggedVertex = index;\n-        $scope.state.vertexDragStartX = $scope.graph.vertices[index].x;\n-        $scope.state.vertexDragStartY = $scope.graph.vertices[index].y;\n-        $scope.state.mouseDragStartX = $scope.state.mouseX;\n-        $scope.state.mouseDragStartY = $scope.state.mouseY;\n-      }\n-      function endDragVertex() {\n-        $scope.state.currentlyDraggedVertex = null;\n-        $scope.state.vertexDragStartX = 0;\n-        $scope.state.vertexDragStartY = 0;\n-        $scope.state.mouseDragStartX = 0;\n-        $scope.state.mouseDragStartY = 0;\n-      }\n-      function beginEditVertexLabel(index) {\n-        $scope.state.selectedVertex = index;\n-        focusService.setFocus('vertexLabelEditBegun');\n-      }\n \n-      function beginEditEdgeWeight(index) {\n-        $scope.state.selectedEdge = index;\n-        focusService.setFocus('edgeWeightEditBegun');\n-      }\n-      function deleteEdge(index) {\n-        $scope.graph.edges.splice(index,1);\n-        $scope.state.hoveredEdge = null;\n-      }\n-      function _deleteRepeatedUndirectedEdges() {\n-        for (var i = 0; i < $scope.graph.edges.length; i++) {\n-          var edge1 = $scope.graph.edges[i];\n-          for (var j = i + 1; j < $scope.graph.edges.length; j++) {\n-            var edge2 = $scope.graph.edges[j];\n-            if ((edge1.src === edge2.src && edge1.dst === edge2.dst) ||\n-                (edge1.src === edge2.dst && edge1.dst === edge2.src)) {\n-              deleteEdge(j);\n-              j--;\n+        function beginEditEdgeWeight(index) {\n+          $scope.state.selectedEdge = index;\n+          focusService.setFocus('edgeWeightEditBegun');\n+        }\n+        function deleteEdge(index) {\n+          $scope.graph.edges.splice(index, 1);\n+          $scope.state.hoveredEdge = null;\n+        }\n+        function _deleteRepeatedUndirectedEdges() {\n+          for (var i = 0; i < $scope.graph.edges.length; i++) {\n+            var edge1 = $scope.graph.edges[i];\n+            for (var j = i + 1; j < $scope.graph.edges.length; j++) {\n+              var edge2 = $scope.graph.edges[j];\n+              if ((edge1.src === edge2.src && edge1.dst === edge2.dst) ||\n+                  (edge1.src === edge2.dst && edge1.dst === edge2.src)) {\n+                deleteEdge(j);\n+                j--;\n+              }\n             }\n           }\n         }\n-      }\n-      function deleteVertex(index) {\n-        // Using jQuery's map instead of normal array.map because\n-        // it removes elements for which the callback returns null\n-        $scope.graph.edges = $.map($scope.graph.edges, function(edge) {\n-          if (edge.src === index || edge.dst === index) {\n-            return null;\n+        function deleteVertex(index) {\n+          // Using jQuery's map instead of normal array.map because\n+          // it removes elements for which the callback returns null\n+          $scope.graph.edges = $.map($scope.graph.edges, function(edge) {\n+            if (edge.src === index || edge.dst === index) {\n+              return null;\n+            }\n+            if (edge.src > index) {\n+              edge.src--;\n+            }\n+            if (edge.dst > index) {\n+              edge.dst--;\n+            }\n+            return edge;\n+          });\n+          $scope.graph.vertices.splice(index, 1);\n+          $scope.state.hoveredVertex = null;\n+        }\n+        $scope.selectedVertexLabelGetterSetter = function(label) {\n+          if ($scope.state.selectedVertex === null) {\n+            return '';\n           }\n-          if (edge.src > index) {\n-            edge.src--;\n+          if (angular.isDefined(label)) {\n+            $scope.graph.vertices[$scope.state.selectedVertex].label = label;\n           }\n-          if (edge.dst > index) {\n-            edge.dst--;\n+          return $scope.graph.vertices[$scope.state.selectedVertex].label;\n+        };\n+        $scope.selectedEdgeWeight = function(weight) {\n+          if ($scope.state.selectedEdge === null) {\n+            return '';\n           }\n-          return edge;\n-        });\n-        $scope.graph.vertices.splice(index, 1);\n-        $scope.state.hoveredVertex = null;\n-      }\n-      $scope.selectedVertexLabelGetterSetter = function(label) {\n-        if ($scope.state.selectedVertex === null) {\n-          return '';\n-        }\n-        if (angular.isDefined(label)) {\n-          $scope.graph.vertices[$scope.state.selectedVertex].label = label;\n+          if (angular.isDefined(weight) && angular.isNumber(weight)) {\n+            $scope.graph.edges[$scope.state.selectedEdge].weight = weight;\n+          }\n+          return $scope.graph.edges[$scope.state.selectedEdge].weight;\n+        };\n+\n+\n+        // Styling functions\n+        var DELETE_COLOR = 'red';\n+        var HOVER_COLOR = 'aqua';\n+        var SELECT_COLOR = 'orange';\n+        var DEFAULT_COLOR = 'black';\n+        $scope.getEdgeColor = function(index) {\n+          if ($scope.state.currentMode === _MODES.DELETE &&\n+              index === $scope.state.hoveredEdge &&\n+              $scope.canDeleteEdge) {\n+            return DELETE_COLOR;\n+          } else if (index === $scope.state.hoveredEdge) {\n+            return HOVER_COLOR;\n+          } else if ($scope.state.selectedEdge === index) {\n+            return SELECT_COLOR;\n+          } else {\n+            return DEFAULT_COLOR;\n+          }\n+        };\n+        $scope.getVertexColor = function(index) {\n+          if ($scope.state.currentMode === _MODES.DELETE &&\n+              index === $scope.state.hoveredVertex &&\n+              $scope.canDeleteVertex) {\n+            return DELETE_COLOR;\n+          } else if (index === $scope.state.currentlyDraggedVertex) {\n+            return HOVER_COLOR;\n+          } else if (index === $scope.state.hoveredVertex) {\n+            return HOVER_COLOR;\n+          } else if ($scope.state.selectedVertex === index) {\n+            return SELECT_COLOR;\n+          } else {\n+            return DEFAULT_COLOR;\n+          }\n+        };\n+        $scope.getDirectedEdgeArrowPoints = function(index) {\n+          return graphDetailService.getDirectedEdgeArrowPoints(\n+            $scope.graph, index);\n+        };\n+        $scope.getEdgeCentre = function(index) {\n+          return graphDetailService.getEdgeCentre($scope.graph, index);\n+        };\n+      }]\n+  };\n+});\n+\n+oppia.factory('graphInputRulesService', [function() {\n+  var isIsomophic = function(graph1, graph2) {\n+    if (graph1.vertices.length != graph2.vertices.length) {\n+      return false;\n+    }\n+\n+    var constructAdjacencyMatrix = function(graph) {\n+      var adjMatrix = [];\n+      graph.vertices.map(function() {\n+        adjMatrix.push([]);\n+      });\n+      graph.edges.map(function(edge) {\n+        var weight = graph.isWeighted ? edge.weight : 1;\n+        adjMatrix[edge.src][edge.dst] = weight;\n+        if (!graph.isDirected) {\n+          adjMatrix[edge.dst][edge.src] = weight;\n         }\n-        return $scope.graph.vertices[$scope.state.selectedVertex].label;\n-      };\n-      $scope.selectedEdgeWeight = function(weight) {\n-        if ($scope.state.selectedEdge === null) {\n-          return '';\n+      });\n+      return adjMatrix;\n+    };\n+\n+    var adj1 = constructAdjacencyMatrix(graph1);\n+    var adj2 = constructAdjacencyMatrix(graph2);\n+\n+    var nextPermutation = function(permutation) {\n+      // Generates (in place) the next lexicographical permuation.\n+      // permutation is a permutation of [0...permutation.length]\n+\n+      // Find the pivot and successor\n+      var pivot = null;\n+      var successor = null;\n+      permutation.reduce(function(previousValue, currentValue, currentIndex) {\n+        if (previousValue < currentValue) {\n+          pivot = currentIndex - 1;\n         }\n-        if (angular.isDefined(weight) && angular.isNumber(weight)) {\n-          $scope.graph.edges[$scope.state.selectedEdge].weight = weight;\n+        if (pivot !== null && currentValue > permutation[pivot]) {\n+          successor = currentIndex;\n         }\n-        return $scope.graph.edges[$scope.state.selectedEdge].weight;\n-      };\n+        return currentValue;\n+      });\n+      if (pivot === null) return null;\n+      var tmp = permutation[pivot];\n+      permutation[pivot] = permutation[successor];\n+      permutation[successor] = tmp;\n+      permutation = permutation.concat(permutation.splice(pivot + 1).reverse());\n+      return permutation;\n+    };\n \n+    // Check against every permutation of vectices.\n+    // The new index of vertex i in graph2 is permutation[i].\n+    var numVertices = graph2.vertices.length;\n+    var permutation = [];\n+    for (var i = 0; i < numVertices; i++) {\n+      permutation.push(i);\n+    }\n+    while (permutation !== null) {\n+      var foundIsomorphism = true;\n+      if (graph1.isLabeled && graph2.vertices.some(function(vertex, index) {\n+        return vertex.label != graph1.vertices[permutation[index]].label;\n+      })) {\n+        foundIsomorphism = false;","path":"extensions/interactions/GraphInput/GraphInput.js","position":763,"original_position":763,"commit_id":"2db35fd0e4a524e61ab9257f7f1e7a61610d92a6","original_commit_id":"2db35fd0e4a524e61ab9257f7f1e7a61610d92a6","user":{"login":"seanlip","id":10575562,"avatar_url":"https://avatars.githubusercontent.com/u/10575562?v=3","gravatar_id":"","url":"https://api.github.com/users/seanlip","html_url":"https://github.com/seanlip","followers_url":"https://api.github.com/users/seanlip/followers","following_url":"https://api.github.com/users/seanlip/following{/other_user}","gists_url":"https://api.github.com/users/seanlip/gists{/gist_id}","starred_url":"https://api.github.com/users/seanlip/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seanlip/subscriptions","organizations_url":"https://api.github.com/users/seanlip/orgs","repos_url":"https://api.github.com/users/seanlip/repos","events_url":"https://api.github.com/users/seanlip/events{/privacy}","received_events_url":"https://api.github.com/users/seanlip/received_events","type":"User","site_admin":false},"body":"Mm, good point. You could perhaps jiggle it so that the nextPermutation call is at the beginning, but then you'll miss the starting permutation. The do/while loop is nice, so it might be worth investigating why it's not behaving as expected.\r\n\r\nOne way around that is to have something in the beginning that says: does `permutation` exist? If not, create it as [0, 1, 2, ..., n-1]. Otherwise, run nextPermutation(), and break if the result is null. But that introduces complexity of its own.\r\n\r\nYou could also pull the equality check below into a separate function, which would probably reduce the cyclomatic complexity of this part of the code.\r\n\r\nAll that being said, if you don't like what you end up with, it's probably fine to leave it as is. I think figuring out what's going on with the do/while, or moving the inner logic to a separate method, are probably the most promising approaches.","created_at":"2015-12-18T21:42:16Z","updated_at":"2015-12-18T21:42:16Z","html_url":"https://github.com/oppia/oppia/pull/1263#discussion_r48073202","pull_request_url":"https://api.github.com/repos/oppia/oppia/pulls/1263","_links":{"self":{"href":"https://api.github.com/repos/oppia/oppia/pulls/comments/48073202"},"html":{"href":"https://github.com/oppia/oppia/pull/1263#discussion_r48073202"},"pull_request":{"href":"https://api.github.com/repos/oppia/oppia/pulls/1263"}}},"pull_request":{"url":"https://api.github.com/repos/oppia/oppia/pulls/1263","id":54076770,"html_url":"https://github.com/oppia/oppia/pull/1263","diff_url":"https://github.com/oppia/oppia/pull/1263.diff","patch_url":"https://github.com/oppia/oppia/pull/1263.patch","issue_url":"https://api.github.com/repos/oppia/oppia/issues/1263","number":1263,"state":"open","locked":false,"title":"Migrate graph input rules to frontend","user":{"login":"wxyxinyu","id":5061134,"avatar_url":"https://avatars.githubusercontent.com/u/5061134?v=3","gravatar_id":"","url":"https://api.github.com/users/wxyxinyu","html_url":"https://github.com/wxyxinyu","followers_url":"https://api.github.com/users/wxyxinyu/followers","following_url":"https://api.github.com/users/wxyxinyu/following{/other_user}","gists_url":"https://api.github.com/users/wxyxinyu/gists{/gist_id}","starred_url":"https://api.github.com/users/wxyxinyu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wxyxinyu/subscriptions","organizations_url":"https://api.github.com/users/wxyxinyu/orgs","repos_url":"https://api.github.com/users/wxyxinyu/repos","events_url":"https://api.github.com/users/wxyxinyu/events{/privacy}","received_events_url":"https://api.github.com/users/wxyxinyu/received_events","type":"User","site_admin":false},"body":"I also fixed a bunch of lint errors within the graph input directive (didn't make changes to the logic). ","created_at":"2015-12-18T08:35:11Z","updated_at":"2015-12-18T21:42:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"eb3c329a3e7d7fa6a45e1fd589a0bb745d474247","assignee":{"login":"seanlip","id":10575562,"avatar_url":"https://avatars.githubusercontent.com/u/10575562?v=3","gravatar_id":"","url":"https://api.github.com/users/seanlip","html_url":"https://github.com/seanlip","followers_url":"https://api.github.com/users/seanlip/followers","following_url":"https://api.github.com/users/seanlip/following{/other_user}","gists_url":"https://api.github.com/users/seanlip/gists{/gist_id}","starred_url":"https://api.github.com/users/seanlip/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seanlip/subscriptions","organizations_url":"https://api.github.com/users/seanlip/orgs","repos_url":"https://api.github.com/users/seanlip/repos","events_url":"https://api.github.com/users/seanlip/events{/privacy}","received_events_url":"https://api.github.com/users/seanlip/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/oppia/oppia/pulls/1263/commits","review_comments_url":"https://api.github.com/repos/oppia/oppia/pulls/1263/comments","review_comment_url":"https://api.github.com/repos/oppia/oppia/pulls/comments{/number}","comments_url":"https://api.github.com/repos/oppia/oppia/issues/1263/comments","statuses_url":"https://api.github.com/repos/oppia/oppia/statuses/2db35fd0e4a524e61ab9257f7f1e7a61610d92a6","head":{"label":"wxyxinyu:graph-input-frontend-rules","ref":"graph-input-frontend-rules","sha":"2db35fd0e4a524e61ab9257f7f1e7a61610d92a6","user":{"login":"wxyxinyu","id":5061134,"avatar_url":"https://avatars.githubusercontent.com/u/5061134?v=3","gravatar_id":"","url":"https://api.github.com/users/wxyxinyu","html_url":"https://github.com/wxyxinyu","followers_url":"https://api.github.com/users/wxyxinyu/followers","following_url":"https://api.github.com/users/wxyxinyu/following{/other_user}","gists_url":"https://api.github.com/users/wxyxinyu/gists{/gist_id}","starred_url":"https://api.github.com/users/wxyxinyu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wxyxinyu/subscriptions","organizations_url":"https://api.github.com/users/wxyxinyu/orgs","repos_url":"https://api.github.com/users/wxyxinyu/repos","events_url":"https://api.github.com/users/wxyxinyu/events{/privacy}","received_events_url":"https://api.github.com/users/wxyxinyu/received_events","type":"User","site_admin":false},"repo":{"id":43987891,"name":"oppia","full_name":"wxyxinyu/oppia","owner":{"login":"wxyxinyu","id":5061134,"avatar_url":"https://avatars.githubusercontent.com/u/5061134?v=3","gravatar_id":"","url":"https://api.github.com/users/wxyxinyu","html_url":"https://github.com/wxyxinyu","followers_url":"https://api.github.com/users/wxyxinyu/followers","following_url":"https://api.github.com/users/wxyxinyu/following{/other_user}","gists_url":"https://api.github.com/users/wxyxinyu/gists{/gist_id}","starred_url":"https://api.github.com/users/wxyxinyu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wxyxinyu/subscriptions","organizations_url":"https://api.github.com/users/wxyxinyu/orgs","repos_url":"https://api.github.com/users/wxyxinyu/repos","events_url":"https://api.github.com/users/wxyxinyu/events{/privacy}","received_events_url":"https://api.github.com/users/wxyxinyu/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/wxyxinyu/oppia","description":"Tool for collaboratively building interactive tutors.","fork":true,"url":"https://api.github.com/repos/wxyxinyu/oppia","forks_url":"https://api.github.com/repos/wxyxinyu/oppia/forks","keys_url":"https://api.github.com/repos/wxyxinyu/oppia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wxyxinyu/oppia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wxyxinyu/oppia/teams","hooks_url":"https://api.github.com/repos/wxyxinyu/oppia/hooks","issue_events_url":"https://api.github.com/repos/wxyxinyu/oppia/issues/events{/number}","events_url":"https://api.github.com/repos/wxyxinyu/oppia/events","assignees_url":"https://api.github.com/repos/wxyxinyu/oppia/assignees{/user}","branches_url":"https://api.github.com/repos/wxyxinyu/oppia/branches{/branch}","tags_url":"https://api.github.com/repos/wxyxinyu/oppia/tags","blobs_url":"https://api.github.com/repos/wxyxinyu/oppia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wxyxinyu/oppia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wxyxinyu/oppia/git/refs{/sha}","trees_url":"https://api.github.com/repos/wxyxinyu/oppia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wxyxinyu/oppia/statuses/{sha}","languages_url":"https://api.github.com/repos/wxyxinyu/oppia/languages","stargazers_url":"https://api.github.com/repos/wxyxinyu/oppia/stargazers","contributors_url":"https://api.github.com/repos/wxyxinyu/oppia/contributors","subscribers_url":"https://api.github.com/repos/wxyxinyu/oppia/subscribers","subscription_url":"https://api.github.com/repos/wxyxinyu/oppia/subscription","commits_url":"https://api.github.com/repos/wxyxinyu/oppia/commits{/sha}","git_commits_url":"https://api.github.com/repos/wxyxinyu/oppia/git/commits{/sha}","comments_url":"https://api.github.com/repos/wxyxinyu/oppia/comments{/number}","issue_comment_url":"https://api.github.com/repos/wxyxinyu/oppia/issues/comments{/number}","contents_url":"https://api.github.com/repos/wxyxinyu/oppia/contents/{+path}","compare_url":"https://api.github.com/repos/wxyxinyu/oppia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wxyxinyu/oppia/merges","archive_url":"https://api.github.com/repos/wxyxinyu/oppia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wxyxinyu/oppia/downloads","issues_url":"https://api.github.com/repos/wxyxinyu/oppia/issues{/number}","pulls_url":"https://api.github.com/repos/wxyxinyu/oppia/pulls{/number}","milestones_url":"https://api.github.com/repos/wxyxinyu/oppia/milestones{/number}","notifications_url":"https://api.github.com/repos/wxyxinyu/oppia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wxyxinyu/oppia/labels{/name}","releases_url":"https://api.github.com/repos/wxyxinyu/oppia/releases{/id}","created_at":"2015-10-10T01:23:33Z","updated_at":"2015-10-10T01:23:38Z","pushed_at":"2015-12-18T08:32:02Z","git_url":"git://github.com/wxyxinyu/oppia.git","ssh_url":"git@github.com:wxyxinyu/oppia.git","clone_url":"https://github.com/wxyxinyu/oppia.git","svn_url":"https://github.com/wxyxinyu/oppia","homepage":"https://www.oppia.org","size":38972,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"oppia:develop","ref":"develop","sha":"e18ce468f4cfa2e92ae96be75cea69f2d1688fba","user":{"login":"oppia","id":11620230,"avatar_url":"https://avatars.githubusercontent.com/u/11620230?v=3","gravatar_id":"","url":"https://api.github.com/users/oppia","html_url":"https://github.com/oppia","followers_url":"https://api.github.com/users/oppia/followers","following_url":"https://api.github.com/users/oppia/following{/other_user}","gists_url":"https://api.github.com/users/oppia/gists{/gist_id}","starred_url":"https://api.github.com/users/oppia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oppia/subscriptions","organizations_url":"https://api.github.com/users/oppia/orgs","repos_url":"https://api.github.com/users/oppia/repos","events_url":"https://api.github.com/users/oppia/events{/privacy}","received_events_url":"https://api.github.com/users/oppia/received_events","type":"Organization","site_admin":false},"repo":{"id":40687563,"name":"oppia","full_name":"oppia/oppia","owner":{"login":"oppia","id":11620230,"avatar_url":"https://avatars.githubusercontent.com/u/11620230?v=3","gravatar_id":"","url":"https://api.github.com/users/oppia","html_url":"https://github.com/oppia","followers_url":"https://api.github.com/users/oppia/followers","following_url":"https://api.github.com/users/oppia/following{/other_user}","gists_url":"https://api.github.com/users/oppia/gists{/gist_id}","starred_url":"https://api.github.com/users/oppia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oppia/subscriptions","organizations_url":"https://api.github.com/users/oppia/orgs","repos_url":"https://api.github.com/users/oppia/repos","events_url":"https://api.github.com/users/oppia/events{/privacy}","received_events_url":"https://api.github.com/users/oppia/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/oppia/oppia","description":"Tool for collaboratively building interactive tutors.","fork":false,"url":"https://api.github.com/repos/oppia/oppia","forks_url":"https://api.github.com/repos/oppia/oppia/forks","keys_url":"https://api.github.com/repos/oppia/oppia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/oppia/oppia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/oppia/oppia/teams","hooks_url":"https://api.github.com/repos/oppia/oppia/hooks","issue_events_url":"https://api.github.com/repos/oppia/oppia/issues/events{/number}","events_url":"https://api.github.com/repos/oppia/oppia/events","assignees_url":"https://api.github.com/repos/oppia/oppia/assignees{/user}","branches_url":"https://api.github.com/repos/oppia/oppia/branches{/branch}","tags_url":"https://api.github.com/repos/oppia/oppia/tags","blobs_url":"https://api.github.com/repos/oppia/oppia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/oppia/oppia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/oppia/oppia/git/refs{/sha}","trees_url":"https://api.github.com/repos/oppia/oppia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/oppia/oppia/statuses/{sha}","languages_url":"https://api.github.com/repos/oppia/oppia/languages","stargazers_url":"https://api.github.com/repos/oppia/oppia/stargazers","contributors_url":"https://api.github.com/repos/oppia/oppia/contributors","subscribers_url":"https://api.github.com/repos/oppia/oppia/subscribers","subscription_url":"https://api.github.com/repos/oppia/oppia/subscription","commits_url":"https://api.github.com/repos/oppia/oppia/commits{/sha}","git_commits_url":"https://api.github.com/repos/oppia/oppia/git/commits{/sha}","comments_url":"https://api.github.com/repos/oppia/oppia/comments{/number}","issue_comment_url":"https://api.github.com/repos/oppia/oppia/issues/comments{/number}","contents_url":"https://api.github.com/repos/oppia/oppia/contents/{+path}","compare_url":"https://api.github.com/repos/oppia/oppia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/oppia/oppia/merges","archive_url":"https://api.github.com/repos/oppia/oppia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/oppia/oppia/downloads","issues_url":"https://api.github.com/repos/oppia/oppia/issues{/number}","pulls_url":"https://api.github.com/repos/oppia/oppia/pulls{/number}","milestones_url":"https://api.github.com/repos/oppia/oppia/milestones{/number}","notifications_url":"https://api.github.com/repos/oppia/oppia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/oppia/oppia/labels{/name}","releases_url":"https://api.github.com/repos/oppia/oppia/releases{/id}","created_at":"2015-08-14T00:16:14Z","updated_at":"2015-12-17T03:15:21Z","pushed_at":"2015-12-18T10:54:08Z","git_url":"git://github.com/oppia/oppia.git","ssh_url":"git@github.com:oppia/oppia.git","clone_url":"https://github.com/oppia/oppia.git","svn_url":"https://github.com/oppia/oppia","homepage":"https://www.oppia.org","size":44526,"stargazers_count":65,"watchers_count":65,"language":"Python","has_issues":true,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":85,"mirror_url":null,"open_issues_count":234,"forks":85,"open_issues":234,"watchers":65,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/oppia/oppia/pulls/1263"},"html":{"href":"https://github.com/oppia/oppia/pull/1263"},"issue":{"href":"https://api.github.com/repos/oppia/oppia/issues/1263"},"comments":{"href":"https://api.github.com/repos/oppia/oppia/issues/1263/comments"},"review_comments":{"href":"https://api.github.com/repos/oppia/oppia/pulls/1263/comments"},"review_comment":{"href":"https://api.github.com/repos/oppia/oppia/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/oppia/oppia/pulls/1263/commits"},"statuses":{"href":"https://api.github.com/repos/oppia/oppia/statuses/2db35fd0e4a524e61ab9257f7f1e7a61610d92a6"}}}}