RoundNode.js

285 lines | 8.672 kB Blame History Raw Download
var BaseNode = require("./BaseNode");
var CenteringTextElement = require("../../util/CenteringTextElement");
var drawTools = require("../drawTools")();

module.exports = (function (){
  
  var o = function ( graph ){
    BaseNode.apply(this, arguments);
    
    var that = this,
      collapsible = false,
      radius = 50,
      collapsingGroupElement,
      pinGroupElement,
      haloGroupElement = null,
      rectangularRepresentation = false,
      renderingElement,
      textBlock;
    
    this.setRectangularRepresentation = function ( val ){
      rectangularRepresentation = val;
    };
    this.getRectangularRepresentation = function (){
      return rectangularRepresentation;
    };
    
    this.getHalos = function (){
      return haloGroupElement;
    };
    
    // Properties
    this.collapsible = function ( p ){
      if ( !arguments.length ) return collapsible;
      collapsible = p;
      return this;
    };
    
    this.textBlock = function ( p ){
      if ( !arguments.length ) return textBlock;
      textBlock = p;
      return this;
    };
    
    /**
     * This might not be equal to the actual radius, because the instance count is used for its calculation.
     * @param p
     * @returns {*}
     */
    this.radius = function ( p ){
      if ( !arguments.length ) return radius;
      radius = p;
      return this;
    };
    
    
    // Functions
    this.setHoverHighlighting = function ( enable ){
      that.nodeElement().selectAll("circle").classed("hovered", enable);
    };
    
    this.textWidth = function ( yOffset ){
      var availableWidth = this.actualRadius() * 2;
      
      // if the text is not placed in the center of the circle, it can't have the full width
      if ( yOffset ) {
        var relativeOffset = Math.abs(yOffset) / this.actualRadius();
        var isOffsetInsideOfNode = relativeOffset <= 1;
        
        if ( isOffsetInsideOfNode ) {
          availableWidth = Math.cos(relativeOffset) * availableWidth;
        } else {
          availableWidth = 0;
        }
      }
      
      return availableWidth;
    };
    
    this.toggleFocus = function (){
      that.focused(!that.focused());
      if ( that.nodeElement() )
        that.nodeElement().select("circle").classed("focused", that.focused());
      graph.resetSearchHighlight();
      graph.options().searchMenu().clearText();
      
    };
    
    this.actualRadius = function (){
      if ( !graph.options().scaleNodesByIndividuals() || that.individuals().length <= 0 ) {
        return that.radius();
      } else {
        // we could "listen" for radius and maxIndividualCount changes, but this is easier
        var MULTIPLIER = 8,
          additionalRadius = Math.log(that.individuals().length + 1) * MULTIPLIER + 5;
        
        return that.radius() + additionalRadius;
      }
    };
    
    this.distanceToBorder = function (){
      return that.actualRadius();
    };
    
    this.removeHalo = function (){
      if ( that.halo() ) {
        that.halo(false);
        if ( haloGroupElement ) {
          haloGroupElement.remove();
        }
      }
    };
    
    this.drawHalo = function ( pulseAnimation ){
      that.halo(true);
      if ( rectangularRepresentation === true ) {
        haloGroupElement = drawTools.drawRectHalo(that.nodeElement(), 80, 80, 5);
      } else {
        haloGroupElement = drawTools.drawHalo(that.nodeElement(), that.actualRadius(), this.removeHalo);
      }
      if ( pulseAnimation === false ) {
        var pulseItem = haloGroupElement.selectAll(".searchResultA");
        pulseItem.classed("searchResultA", false);
        pulseItem.classed("searchResultB", true);
        pulseItem.attr("animationRunning", false);
      }
    };
    
    /**
     * Draws the pin on a round node on a position depending on its radius.
     */
    this.drawPin = function (){
      that.pinned(true);
      var dx = (-3.5 / 5) * that.actualRadius(),
        dy = (-7 / 10) * that.actualRadius();
      pinGroupElement = drawTools.drawPin(that.nodeElement(), dx, dy, this.removePin, graph.options().showDraggerObject, graph.options().useAccuracyHelper());
      
      
    };
    
    /**
     * Removes the pin and refreshs the graph to update the force layout.
     */
    this.removePin = function (){
      that.pinned(false);
      if ( pinGroupElement ) {
        pinGroupElement.remove();
      }
      graph.updateStyle();
    };
    
    this.drawCollapsingButton = function (){
      
      collapsingGroupElement = that.nodeElement()
        .append("g")
        .classed("hidden-in-export", true)
        .attr("transform", function (){
          var dx = (-2 / 5) * that.actualRadius(),
            dy = (1 / 2) * that.actualRadius();
          return "translate(" + dx + "," + dy + ")";
        });
      
      collapsingGroupElement.append("rect")
        .classed("class pin feature", true)
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", 40)
        .attr("height", 24);
      
      collapsingGroupElement.append("line")
        .attr("x1", 13)
        .attr("y1", 12)
        .attr("x2", 27)
        .attr("y2", 12);
      
      collapsingGroupElement.append("line")
        .attr("x1", 20)
        .attr("y1", 6)
        .attr("x2", 20)
        .attr("y2", 18);
    };
    
    /**
     * Draws a circular node.
     * @param parentElement the element to which this node will be appended
     * @param [additionalCssClasses] additional css classes
     */
    this.draw = function ( parentElement, additionalCssClasses ){
      var cssClasses = that.collectCssClasses();
      that.nodeElement(parentElement);
      
      var bgColor = that.backgroundColor();
      if ( bgColor === null ) bgColor = undefined;
      if ( that.attributes().indexOf("deprecated") > -1 ) {
        bgColor = undefined;
      }
      if ( additionalCssClasses instanceof Array ) {
        cssClasses = cssClasses.concat(additionalCssClasses);
      }
      if ( rectangularRepresentation === true ) {
        renderingElement = drawTools.appendRectangularClass(parentElement, 80, 80, cssClasses, that.labelForCurrentLanguage(), bgColor);
      } else {
        renderingElement = drawTools.appendCircularClass(parentElement, that.actualRadius(), cssClasses, that.labelForCurrentLanguage(), bgColor);
      }
      that.postDrawActions(parentElement);
    };
    
    this.redrawElement = function (){
      renderingElement.remove();
      textBlock.remove();
      var bgColor = that.backgroundColor();
      if ( that.attributes().indexOf("deprecated") > -1 ) {
        bgColor = undefined;
      }
      
      var cssClasses = that.collectCssClasses();
      
      if ( rectangularRepresentation === true ) {
        renderingElement = drawTools.appendRectangularClass(that.nodeElement(), 80, 80, cssClasses, that.labelForCurrentLanguage(), bgColor);
      } else {
        renderingElement = drawTools.appendCircularClass(that.nodeElement(), that.actualRadius(), cssClasses, that.labelForCurrentLanguage(), bgColor);
      }
      that.postDrawActions(that.nodeElement());
    };
    /**
     * Common actions that should be invoked after drawing a node.
     */
    this.postDrawActions = function (){
      that.textBlock(createTextBlock());
      
      that.addMouseListeners();
      if ( that.pinned() ) {
        that.drawPin();
      }
      if ( that.halo() ) {
        that.drawHalo(false);
      }
      if ( that.collapsible() ) {
        that.drawCollapsingButton();
      }
    };
    
    this.redrawLabelText = function (){
      that.textBlock().remove();
      that.textBlock(createTextBlock());
      renderingElement.select("title").text(that.labelForCurrentLanguage());
    };
    function createTextBlock(){
      var bgColor = that.backgroundColor();
      if ( that.attributes().indexOf("deprecated") > -1 )
        bgColor = undefined;
      
      var textBlock = new CenteringTextElement(that.nodeElement(), bgColor);
      
      var equivalentsString = that.equivalentsString();
      var suffixForFollowingEquivalents = equivalentsString ? "," : "";
      
      textBlock.addText(that.labelForCurrentLanguage(), "", suffixForFollowingEquivalents);
      textBlock.addEquivalents(equivalentsString);
      if ( !graph.options().compactNotation() ) {
        textBlock.addSubText(that.indicationString());
      }
      textBlock.addInstanceCount(that.individuals().length);
      
      return textBlock;
    }
    
    this.equivalentsString = function (){
      var equivalentClasses = that.equivalents();
      if ( !equivalentClasses ) {
        return;
      }
      
      return equivalentClasses
        .map(function ( node ){
          return node.labelForCurrentLanguage();
        })
        .join(", ");
    };
  };
  o.prototype = Object.create(BaseNode.prototype);
  o.prototype.constructor = o;
  
  return o;
}());