Пример #1
0
Adapter.prototype.getGraph = function(opts) {
  
  $tm.start("Assembling Graph");
  
  opts = opts || {};

  var view = new ViewAbstraction(opts.view);
  var matches = utils.getMatches(opts.filter
                                 || (view.exists()
                                     && view.getNodeFilter("compiled")));
  var toWL = utils.getArrayValuesAsHashmapKeys(matches);
  var typeWL = (opts.edgeTypeWL
                || (view.exists() && view.getEdgeTypeFilter("whitelist")));
  var neighScope = parseInt(opts.neighbourhoodScope
                            || (view.exists()
                                && view.getConfig("neighbourhood_scope")));
  
  var graph = {
    edges: this.getEdgesForSet(matches, toWL, typeWL),
    nodes: this.selectNodesByReferences(matches, {
      view: view,
      outputType: "hashmap"
    })
  };
  
  if(neighScope) {
    var neighbours = this.getNeighbours(matches, {
      steps: neighScope,
      view: view,
      typeWL: typeWL,
      addProperties: {
        group: "tmap:neighbour"
      }
    });
    
    // merge neighbours (nodes and edges) into graph
    utils.merge(graph, neighbours);
    
    if(view.exists() && view.isEnabled("show_inter_neighbour_edges")) {
      var nodeTRefs = this.getTiddlersById(neighbours.nodes);
      // this time we need a whitelist based on the nodeTRefs
      var toWL = utils.getArrayValuesAsHashmapKeys(nodeTRefs)
      $tw.utils.extend(graph.edges, this.getEdgesForSet(nodeTRefs, toWL));
    }
  }
  
  // this is pure maintainance!
  this._removeObsoleteViewData(graph.nodes, view);
  
  // add styles to nodes
  this.attachStylesToNodes(graph.nodes, view);
  
  $tm.stop("Assembling Graph");
  
  $tm.logger("debug", "Assembled graph:", graph);
  
  return graph;
  
};
Пример #2
0
  /**
   * Create a new tiddler that gets a non-existant title and is opened
   * for edit. If a view is registered, the fields of the tiddler match
   * the current view. If arguments network and position are specified,
   * the node is also inserted directly into the graph at the given
   * position.
   *
   * @TODO: Description is obsolete!
   *
   * @param {object} node A node object to be inserted
   * @param {ViewAbstraction|string} view - used to set positions and register the node to
   * @param {Tiddler} protoTiddler
   */
  insertNode(node = {}, view, protoTiddler) {

    // title might has changed after generateNewTitle()
    node.label = this.wiki.generateNewTitle(node.label || utils.getRandomLabel());

    // add to tiddler store
    const tObj = new $tw.Tiddler(
      { text: '' }, // https://github.com/Jermolene/TiddlyWiki5/issues/2025
      protoTiddler,
      {
        title: node.label, // force title
        'tmap.id': null // force empty id (generated later)
      },
      this.wiki.getModificationFields(),
      this.wiki.getCreationFields()
    );

    this.wiki.addTiddler(tObj);

    node = this.makeNode(tObj, node);

    if (ViewAbstraction.exists(view)) {
      (new ViewAbstraction(view)).addNode(node);
    }

    return node;

  }
Пример #3
0
const removeObsoleteViewData = (nodes, view) => {

  if (!ViewAbstraction.exists(view) || !nodes) {
    return;
  }

  view = new ViewAbstraction(view);

  const data = view.getNodeData();

  let obsoleteDataItems = 0;
  for (let id in data) {
    if (nodes[id] === undefined && data[id] != null) {
      // we only set this to undefined as deletion would
      // slow down V8, however, this necessarily requires
      // a safeguard agains recursion: data[id] != null

      data[id] = undefined;
      obsoleteDataItems++;
    }
  }

  if (obsoleteDataItems) {
    $tm.logger('debug', '[Cleanup]',
      'Removed obsolete node data:',
      view.getLabel(), obsoleteDataItems);
    view.saveNodeData(data);
  }

};
Пример #4
0
  /**
   * This function will assemble a graph object based on the supplied
   * node and edge filters. Optionally, a neighbourhood may be
   * merged into the graph neighbourhood.
   *
   * @param {string|ViewAbstraction} [view] - The view in which
   *     the graph will be displayed.
   * @param {string|ViewAbstraction} [filter] - If supplied,
   *     this will act as node filter that defines which nodes
   *     are to be displayed in the graph; a possible view node filter
   *     would be ignored.
   * @param {Hashmap} [edgeTypeWL] - A whitelist lookup-table
   *     that restricts which edges are travelled to reach a neighbour.
   * @param {number} [neighbourhoodScope] - An integer value that
   *     specifies the scope of the neighbourhood in steps.
   *     See {@link Adapter#getNeighbours}
   * @return {Object} An object of the form:
   *     {
   *       nodes: { *all nodes in the graph* },
   *       edges: { *all edges in the graph* },
   *     }
   *     Neighbours will be receive the 'tmap:neighbour' type.
   */
  getGraph({ view, filter, edgeTypeWL, neighbourhoodScope } = {}) {

    $tm.start('Assembling Graph');

    view = ViewAbstraction.exists(view) ? new ViewAbstraction(view) : null;
    const matches = utils.getMatches(filter || (view && view.getNodeFilter('compiled')));
    const neighScope = parseInt(neighbourhoodScope || (view && view.getConfig('neighbourhood_scope')));
    const typeWL = (edgeTypeWL || (view && view.getEdgeTypeFilter('whitelist')));
    const toWL = utils.getArrayValuesAsHashmapKeys(matches);

    const graph = {
      edges: this.getEdgesForSet(matches, toWL, typeWL),
      nodes: this.selectNodesByReferences(matches, {
        view: view,
        outputType: 'hashmap'
      })
    };

    if (neighScope) {
      const neighbours = this.getNeighbours(matches, {
        steps: neighScope,
        view: view,
        typeWL: typeWL,
        addProperties: {
          group: 'tmap:neighbour'
        }
      });

      // add neighbours (nodes and edges) to graph
      Object.assign(graph.nodes, neighbours.nodes);
      Object.assign(graph.edges, neighbours.edges);

      if (view && view.isEnabled('show_inter_neighbour_edges')) {
        const nodeTRefs = this.getTiddlersByIds(neighbours.nodes);
        // this time we need a whitelist based on the nodeTRefs
        const toWL = utils.getArrayValuesAsHashmapKeys(nodeTRefs);
        Object.assign(graph.edges, this.getEdgesForSet(nodeTRefs, toWL));
      }
    }

    // this is pure maintainance!
    removeObsoleteViewData(graph.nodes, view);

    // add styles to nodes
    this.attachStylesToNodes(graph.nodes, view);

    $tm.stop('Assembling Graph');

    $tm.logger('debug', 'Assembled graph:', graph);

    return graph;

  }
Пример #5
0
  /**
   * Adds styles to nodes.
   *
   * @param {Object<string, Node>} nodes
   * @param {ViewAbstraction|string} view
   */
  attachStylesToNodes(nodes, view) {

    view = ViewAbstraction.exists(view) ? new ViewAbstraction(view) : null;

    const inheritedStyles = this.getInheritedNodeStyles(nodes);
    const viewNodeData = view ? view.getNodeData() : utils.makeHashMap();
    const isStaticMode = view && !view.isEnabled('physics_mode');

    for (let id in nodes) {

      const tRef = this.getTiddlerById(id);
      const tObj = this.wiki.getTiddler(tRef);
      const fields = tObj.fields;
      const node = nodes[id];
      let icon;

      // == group styles ==

      const inheritedStyle = inheritedStyles[tRef];

      if (inheritedStyle) {

        utils.merge(node, inheritedStyle.style);
        icon = getIcon(inheritedStyle['fa-icon'], inheritedStyle['tw-icon']);
      }

      // == global node styles ==

      // background color
      if (fields.color) {
        node.color = fields.color;
      }

      // global node style from vis editor
      if (fields['tmap.style']) {
        utils.merge(node, utils.parseJSON(fields['tmap.style']));
      }

      icon = getIcon(fields['tmap.fa-icon'], fields['icon']) || icon;

      // == local node styles ==

      // local node style and positions

      const nodeData = viewNodeData[id];

      if (nodeData) {

        utils.merge(node, nodeData);
        if (isStaticMode) {
          // fix x if x-position is set; same for y
          node.fixed = {
            x: (node.x != null),
            y: (node.y != null)
          };
        }

        icon = getIcon(nodeData['fa-icon'], nodeData['tw-icon']) || icon;
      }

      // == tweaks ==

      const isColorObject = (node.color !== null && typeof node.color === 'object');
      // color/border-color may be undefined
      const color = (isColorObject ? node.color.background : node.color);

      node.color = {
        background: color,
        border: (isColorObject ? node.color.border : undefined)
      };

      // ATTENTION: this function needs to be called after color is assigned
      addNodeIcon(node, icon);

      // determine font color if not defined via a group- or node-style;
      // in case of global and local default styles, the user is responsible
      // him- or herself to adjust the font
      node.font = node.font || {};

      if (node.shape && !this.visShapesWithTextInside[node.shape]) {
        node.font.color = 'black'; // force a black color
      } else if (!node.font.color && color) {
        node.font.color = getContrastColour(color, color, 'black', 'white');
      }

      if (node.shape === 'icon' && typeof node.icon === 'object') {
        node.icon.color = color;
      }

    }

    if (view) {
      const node = nodes[view.getConfig('central-topic')];
      if (node) {
        utils.merge(node, this.indeces.glNTyById['tmap:central-topic'].style);
      }
    }

  }
Пример #6
0
  /**
   * This function will return all neighbours of a graph denoted by
   * a set of tiddlers.
   *
   * @todo parts of this code may be outsourced into a function to
   * prevent repeating code.
   *
   * @param {Array<TiddlerReference>} matches - The original set that
   *     defines the starting point for the neighbourhood discovery
   * @param {Hashmap} [opts] - An optional options object.
   * @param {Hashmap} [opts.typeWL] - A whitelist lookup-table
   *    that restricts which edges are travelled to reach a neighbour.
   * @param {Hashmap} [opts.edges] - An initial set of edges that is
   *    used in the first step to reach immediate neighbours, if no
   *    set of edges is specified, all exsisting edges will be considered.
   * @param {number} [opts.steps] - An integer value that specifies
   *    the scope of the neighbourhood. A node is considered a neighbour
   *    if it can be reached within the given number of steps starting
   *    from original set of tiddlers returned by the node filter.
   * @param {Hashmap} [opts.addProperties] - a hashmap
   *     containing properties to be added to each node.
   *     For example:
   *     {
   *       group: 'g1',
   *       color: 'red'
   *     }
   * @return {Object} An object of the form:
   *     {
   *       nodes: { *all neighbouring nodes* },
   *       edges: { *all edges connected to neighbours* },
   *     }
   */
  getNeighbours(matches, opts = {}) {

    $tm.start('Get neighbours');

    const { addProperties, toWL, typeWL, steps } = opts;
    const { allETy } = this.indeces;

    // index of all tiddlers have already are been visited, either by
    // having been included in the original set, or by having been
    // recorded as neighbour during the discovery.
    const visited = utils.getArrayValuesAsHashmapKeys(matches);
    const view = ViewAbstraction.exists(opts.view) ? new ViewAbstraction(opts.view) : null;
    const allEdgesLeadingToNeighbours = utils.makeHashMap();
    const allNeighbours = utils.makeHashMap();
    const maxSteps = (parseInt(steps) > 0 ? steps : 1);
    const direction = (opts.direction || (view && view.getConfig('neighbourhood_directions')));
    const isWalkBoth = (!direction || direction === 'both');
    const isWalkIn = (isWalkBoth || direction === 'in');
    const isWalkOut = (isWalkBoth || direction === 'out');

    // in order to apply the node-filter also to neighbours we need to make it
    // include all tiddlers in the filter's source (e.g. a tiddler and a few neighbours)
    // and then apply the filter – which now has the chance to take away tiddlers
    // a few filters from the set
    const neighFilter = view && `[all[]] ${view.getNodeFilter('raw')}`;

    // adjacency receives whitelists through opts
    const adjList = this.getAdjacencyList('to', opts);

    const addAsNeighbour = (edge, role, neighboursOfThisStep) => {
      allEdgesLeadingToNeighbours[edge.id] = edge;
      const tRef = this.getTiddlerById(edge[role]);

      if (
        view
        && utils.isTrue($tm.config.sys.nodeFilterNeighbours)
        && !utils.isMatch(tRef, neighFilter)) {
        return;
      }

      if (!visited[tRef]) {
        visited[tRef] = true;
        const node = this.makeNode(tRef, addProperties);
        if (node) { // saveguard against obsolete edges or other problems
          // record node
          allNeighbours[node.id] = node;
          neighboursOfThisStep.push(tRef);
        }
      }
    };

    // needed later
    let step;

    // loop if still steps to be taken and we have a non-empty starting set
    for (step = 0; step < maxSteps && matches.length; step++) {

      // neighbours that are discovered in the current step;
      // starting off from the current set of matches;
      const neighboursOfThisStep = [];

      // loop over all nodes in the original set
      for (let i = matches.length; i--;) {

        if (utils.isSystemOrDraft(matches[i])) {
          // = this might happen if the user manually created edges
          // that link to a system/draft tiddler or if the original
          // set contained system/draft tiddlers.
          continue;
        }

        // get all outgoing edges
        // = edges originating from the starting set and point outwards
        const outgoing = this.getEdges(matches[i], toWL, typeWL);
        for (let id in outgoing) {

          const t = allETy[outgoing[id].type];
          if (isWalkBoth || isWalkOut && t.toArrow || isWalkIn && t.invertedArrow) {

            addAsNeighbour(outgoing[id], 'to', neighboursOfThisStep);
          }
        }

        // get all incoming edges
        // = edges originating from outside pointing to the starting set
        const incoming = adjList[this.getId(matches[i])];
        if (!incoming) {
          continue;
        }

        for (let j = incoming.length; j--;) {
          const t = allETy[incoming[j].type];
          if (isWalkBoth || isWalkIn && t.toArrow || isWalkOut && t.invertedArrow) {
            addAsNeighbour(incoming[j], 'from', neighboursOfThisStep);
          }
        }
      }

      // the current set of newly discovered neighbours forms the
      // starting point for the next discovery
      matches = neighboursOfThisStep;

    }

    const neighbourhood = {
      nodes: allNeighbours,
      edges: allEdgesLeadingToNeighbours
    };

    $tm.logger('debug', 'Retrieved neighbourhood', neighbourhood, 'steps', step);

    $tm.stop('Get neighbours');

    return neighbourhood;

  }
Пример #7
0
Adapter.prototype.getNeighbours = function(matches, opts) {
  
  $tm.start("Get neighbours");
  
  opts = opts || {};
    
  // index of all tiddlers have already are been visited, either by
  // having been included in the original set, or by having been
  // recorded as neighbour during the discovery.
  var visited = utils.getArrayValuesAsHashmapKeys(matches); 
  var view = new ViewAbstraction(opts.view);
  var protoNode = opts.addProperties;
  var allEdgesLeadingToNeighbours = utils.makeHashMap();
  var allETy = $tm.indeces.allETy;
  var allNeighbours = utils.makeHashMap();
  var toWL = opts.toWL;
  var typeWL = opts.typeWL;
  var tById = $tm.indeces.tById;
  var idByT = $tm.indeces.idByT;
  var maxSteps = (parseInt(opts.steps) > 0 ? opts.steps : 1);
  var direction = (opts.direction
                   || (view.exists()
                       && view.getConfig("neighbourhood_directions")));
  var isWalkBoth = (!direction || direction === "both");
  var isWalkIn = (isWalkBoth || direction === "in");
  var isWalkOut = (isWalkBoth || direction === "out");
  
  // adjacency receives whitelists through opts
  var adjList = this.getAdjacencyList("to", opts);
  
  var addAsNeighbour = function(edge, role) {
    allEdgesLeadingToNeighbours[edge.id] = edge;
    var tRef = tById[edge[role]];
    if(!visited[tRef]) {
      visited[tRef] = true;
      var node = this.makeNode(tRef, protoNode);
      if(node) { // saveguard against obsolete edges or other problems  
        // record node
        allNeighbours[node.id] = node;
        neighboursOfThisStep.push(tRef);              
      }
    }
  }.bind(this);

  // loop if still steps to be taken and we have a non-empty starting set
  for(var step = 0; step < maxSteps && matches.length; step++) {
        
    // neighbours that are discovered in the current step;
    // starting off from the current set of matches;
    var neighboursOfThisStep = []; 
    
    // loop over all nodes in the original set
    for(var i = matches.length; i--;) {
      
      if(utils.isSystemOrDraft(matches[i])) {
        // = this might happen if the user manually created edges
        // that link to a system/draft tiddler or if the original
        // set contained system/draft tiddlers.
        continue;
      }
              
      // get all outgoing edges
      // = edges originating from the starting set and point outwards
      var outgoing = this.getEdges(matches[i], toWL, typeWL);
      for(var id in outgoing) {
        var t = allETy[outgoing[id].type];
        if(isWalkBoth || isWalkOut && t.toArrow || isWalkIn && t.invertedArrow) {
          addAsNeighbour(outgoing[id], "to");
        }
      }
      
      // get all incoming edges
      // = edges originating from outside pointing to the starting set
      var incoming = adjList[idByT[matches[i]]];
      if(!incoming) continue;
      
      for(var j = incoming.length; j--;) {
        var t = allETy[incoming[j].type];
        if(isWalkBoth || isWalkIn && t.toArrow || isWalkOut && t.invertedArrow) {
          addAsNeighbour(incoming[j], "from");
        }
      }
    }
    
    // the current set of newly discovered neighbours forms the
    // starting point for the next discovery
    matches = neighboursOfThisStep;
    
  }
  
  var neighbourhood = {
    nodes: allNeighbours,
    edges: allEdgesLeadingToNeighbours
  };
  
  $tm.logger("debug", "Retrieved neighbourhood", neighbourhood, "steps", step);
  
  $tm.stop("Get neighbours");
  
  return neighbourhood;
  
};