File: EdgeSeparation.cpp

package info (click to toggle)
tulip 6.0.1%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 196,224 kB
  • sloc: cpp: 571,851; ansic: 13,983; python: 4,105; sh: 1,555; yacc: 522; xml: 484; makefile: 168; pascal: 148; lex: 55
file content (138 lines) | stat: -rw-r--r-- 4,902 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138

#include <tulip/LayoutProperty.h>
#include <tulip/SimpleTest.h>
#include <tulip/SizeProperty.h>
#include <tulip/StaticProperty.h>
#include <stdexcept>

using namespace tlp;
using namespace std;

static const char *paramHelp[] = {
    // gap
    "The gap between multiple edges.",
    // size
    "The property holding the size of edges."};

class EdgeSeparation : public tlp::LayoutAlgorithm {
  vector<edge> multipleEdges;

public:
  PLUGININFORMATION("Multiple Edges Separation", "Tulip dev team", "16/11/2022",
                    "This plugin separates multiple edges existing between each pair of "
                    "nodes. Since, by default, multiple edges are drawn one on top of "
                    "the other, this plugin separates their drawing by adding bends.",
                    "1.0", "");

  EdgeSeparation(const tlp::PluginContext *context)
      // set second parameter of the constructor below to true because
      // result needs to be an inout parameter
      // in order to preserve the original values of non targeted elements
      : LayoutAlgorithm(context, true) {
    addInParameter<double>("gap", paramHelp[0], "0.5");
    addInParameter<SizeProperty>("edge size", paramHelp[1], "viewSize");

    // result needs to be an inout parameter
    // in order to preserve the original values of non targeted elements
    parameters.setDirection("result", INOUT_PARAM);
  }

  bool check(string &err) override {
    // get multiples edges
    SimpleTest::simpleTest(graph, &multipleEdges, nullptr, false);

    if (multipleEdges.empty()) {
      err = "The graph has no multiple edges between any pair of nodes.\nNothing to do.";
      return false;
    }

    return true;
  }

  bool run() override {
    double gap = 0.5;
    SizeProperty *size = graph->getProperty<SizeProperty>("viewSize");
    pluginProgress->showPreview(false);
    if (dataSet != nullptr) {
      dataSet->get("gap", gap);
      dataSet->get("edge size", size);
    }

    int nbMultiples = multipleEdges.size();
    for (int i = 0; i < nbMultiples; ++i) {
      edge curMe = multipleEdges[i];
      // curMe can be invalid, if i is the offset
      // of an edge already treated.
      // cf the end of the loop
      if (curMe.isValid() == false)
        continue;
      auto ends = graph->ends(curMe);
      // do nothing if curMe is a loop
      if ((ends.first == ends.second))
        continue;
      // we get the coordinates of the current node and its opposite
      Coord sCoord = result->getNodeValue(ends.first);
      Coord tCoord = result->getNodeValue(ends.second);
      // we normalize this vector, so we will be able to use the scalar product
      // to determine the ways the edges will go
      Coord vect = sCoord - tCoord;
      vect /= vect.norm();
      Coord normal(-vect.getY(), vect.getX(), vect.getZ());

      // initialize how far from the original edge the edges will go
      const tlp::Size &s = size->getEdgeValue(curMe);
      float distance = s.getW() + gap;
      float xPlus = distance;
      float xMinus = -distance;

      auto edges = graph->getEdges(ends.first, ends.second, false);
      // for each edge linking these two ends nodes, we separate the edges
      for (auto e : edges) {
        bool isInEdge = graph->target(e) == ends.first;
        // if this edge is an in edge, it will go "up"
        // else it will go "down" of the current edge.
        float x = isInEdge ? xPlus : xMinus;
        std::vector<Coord> bends(2);

        // we add control points, or waypoints to the edge at 1/4 of its length
        // and another one at 3/4 (so we will see more clearly then
        // if we used only one control point) this is prettier when
        // using bezier curves
        Coord cp1_4(sCoord + (tCoord - sCoord) / 4.0f + normal * x);
        Coord cp3_4(sCoord + (tCoord - sCoord) * 3.0f / 4.0f + normal * x);

        // depending which kind of edge we encountered, we increment the distance the next edge
        // will be from the original edge.
        if (isInEdge)
          xPlus += 0.5;
        else
          xMinus -= 0.5;

        // we add the control points in the right order
        // so that we don't get an edge going to
        // the 3/4 waypoint, then the 1/4 waypoint, then to the opposite node.
        if (isInEdge) {
          bends[0] = cp3_4;
          bends[1] = cp1_4;
        } else {
          bends[0] = cp1_4;
          bends[1] = cp3_4;
        }
        // now we can finally set the value on the edge
        result->setEdgeValue(e, bends);
        if (e != curMe) {
          // as e can be found in multipleEdges
          // replace it by an invalid edge
          for (int j = i + 1; j < nbMultiples; ++j)
            if (e == multipleEdges[j]) {
              multipleEdges[j] = edge();
              break;
            }
        }
      }
    }
    return true;
  }
};

PLUGIN(EdgeSeparation)